Oauth2.0认证模式

Oauth2.0认证模式,第1张

背景

首先token认证方式的出现是伴随着系统架构的发展而来的。

最初的单机应用使用sessionId+cookie完全可以满足问题。

随着系统业务访问量加大,为满足服务高可用需求,系统引入了分布式架构,但是session只能存在单机节点上,为了解决这一问题,首先使用过 session粘贴 技术(通过hash等手段让请求始终访问之前生成sessionId的节点),这个方案的问题是万一存sessionId的节点挂了,整个系统都不能登陆了,也违背了高可用的设计思路;后来使用 session复制 的方案,即每台节点都copy一份sessionId,这引出2个问题,第一个就是分布式一致性的问题,节点同一时间重复创建了怎么办?另一个就是更头疼的系统扩展问题,随着系统越来越庞大,节点增多,session复制消耗的性能也越来越大,整个系统因为session出现了可遇见的性能瓶颈;再后来出现了session集中存储到一个地方,这样解放了服务器,但是session存储的高可用问题又来了,简直就是套娃式困局。

既然 有状态服务 出现瓶颈,大佬们开始提出 无状态服务 ,就是使用token验证方式替代较重的session,token就是一个字符串,只要服务根据约定好的算法解析出的结果跟预期一致就可以通过验证,也不需要保存在内存中,无状态服务的好处是系统扩展轻松,新追加的服务轻装上阵,完全没有顾虑。Oauth就是一种规范,用来保证token机制的可行,目前在web端和移动端都是主流登陆验证方案,Oauth20于2010年正式提出,一直沿用到现在,可见其稳定。

随着互联网行业迅猛发展,各种app,各种网站层出不穷,传统登陆方式就要求用户在每个想登陆的站点都要注册自己的账号信息和密码,这也留下了一定隐患,有的小站点可能技术不那么强,这就有可能导致用户信息泄漏,Oauth正好可以解决这种对一些站点即想用又不信任问题。从使用体验的角度,单点登陆的方式也可以简化用户对账号密码的管理。

Oauth2认证模式的4种类型

这种模式最常用,也是最安全的模式,相关的角色有3个:
客户端 (web站点/app,从认证服务的视角,不管你是web页面还是后台服务,都属于客户端范畴)、 资源服务 (存放个人信息的服务,比如微信存放你个人信息的服务)、 认证服务 (某可信的账户平台如微信平台)

使用之前需要在认证服务注册下自己的app信息(主要包括app名称、app站点、认证callback地址等),认证平台会给客户端创建一个clientId(客户端id)和client_secret(客户端秘钥)。这是为了解决认证平台不信任客户端的问题。

使用的时候用户首先会点击客户端登陆页上的一个链接,打开认证服务上的一个登陆确认页面,用户认证的时候会上传以下请求参数:

认证成功后,认证服务会重定向到redirect_uri,
同时认证服务还会按照之前注册好的callback地址,向客户端发一个get请求,参数:

这样客户端拿到了code,会再向认证平台的授权服务器发起请求,参数:

授权服务验证code没问题后,会给客户端返回token。

客户端拿着token就可以去资源服务器请求用户信息了(头像、姓名等)。

这种模式大致流程是:

这个模式不经过code验证的过程,这种token可以直接在callback的uri上看到,并且客户端后台也不会验证token,这种模式安全性显然没有授权码模式好,应用也不多。

流程:
用户直接在客户端上输入账号密码,然后由客户端向认证服务请求要token。

要使用这种模式就表明用户完全信任客户端了,如果信任客户端,直接在客户端上注册账号不就好了,何必把其他平台的账号告诉客户端,万一客户端保存了这个账号密码呢?

流程:
用客户端自己的名义向认证服务获取token,跟用户没什么关系。这也不会是登陆会考虑的场景吧

>

在上一篇文章中,我们已经实现了授权服务器,拿到访问token已经不是问题了。本文主要讲述如何搭建一个资源服务器,根据第三方客户端访问请求和token来实现资源的权限控制。

资源服务器的核心配置类就是需要继承 ResourceServerConfigurerAdapter ,并重写其中的资源配置方法和安全鉴权方法。

token校验有两种方式:

我们该案例中资源服务器和授权服务器是不同的机器,大多数实际业务场景中也是不同的,所以我们以 RemoteTokenServices 为例进行演示,这里放到和资源服务器配置类一块:

资源服务器也是一个独立的应用,所以也需要增加安全访问的配置,这个在先前讲Spring Security的时候已经详细介绍过了。

最后,增加我们的访问资源即可,这里就是Controller。

此时,我们就完成了资源服务器的所有内容。

我们以 客户端模式 启动上一节的授权服务器后,通过>

话不多说,先上图:

分析一波:

其实不管微信或者QQ大体上都是使用这种OAuth2的基本流程:

OAuth2 在服务提供者上可分为两类:

注:这两者有时候可能存在同一个应用程序中(即SOA架构)。在Spring OAuth中可以简便的将其分配到两个应用中(即微服务),而且可多个资源获取服务共享一个授权认证服务。

主要的 *** 作:

分析一波:
1) 第一步 *** 作

注:其中 client_id和 client_secret都是授权服务器发送给第三方应用的,如:微信等一系列授权,在其平台上注册,获取其appid和secret同样道理(个人理解为账号密码)。

既然是账号秘密,总不能以get请求,也太不安全了。因此,OAuth2要求该请求必须是POST请求,同时,还必须时>OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth20是OAuth协议的延续版本,但不向后兼容OAuth 10即完全废止了OAuth10。

第三方应用授权登录:在APP或者网页接入一些第三方应用时,时长会需要用户登录另一个合作平台,比如QQ,微博,微信的授权登录。

原生app授权:app登录请求后台接口,为了安全认证,所有请求都带token信息,如果登录验证、请求后台数据。

前后端分离单页面应用(spa):前后端分离框架,前端请求后台数据,需要进行oauth2安全认证,比如使用vue、react后者h5开发的app。

OAuth 20的运行流程如下图,摘自RFC 6749。

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。

(1)用户访问客户端,后者将前者导向认证服务器,假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

(2)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌:GET /oauth/tokenresponse_type=code&client_id=test&redirect_uri=重定向页面链接。请求成功返回code授权码,一般有效时间是10分钟。

(3)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。POST /oauth/tokenresponse_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=重定向页面链接。

简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

流程步骤:

请求URL:

密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下。一般不支持refresh token。

步骤说明:
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。

<article class="hentry">

指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

它的步骤如下:
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
(B)认证服务器确认无误后,向客户端提供访问令牌。

A步骤中,客户端发出的>

当下的解决方案是引入一个新的非常简单的应用来作为微信授权的代理服务,可以这么做:  

1 把公众号的网页授权接口域名设置成另外一个子域名,如proxyyourcom;    
2 然后把php_weixin_proxy里面的indexphp部署到proxyyourcom

php_weixin_proxy下的indexphp是一个很简单的php文件,你可以直接查看源码了解它的实现方式。因为当前项目的环境,我采用php来完成这个代理服务实现,实际上,你完全可以用任意平台语言来完成类似的功能。

当其它业务需要发起微信授权时,将授权请求先发到proxyyourcom,然后proxyyourcom会把这个请求转发到微信;  
当用户同意授权后,proxyyourcom会收到微信的授权回调,并把回调结果(code、state参数)原封不动地再返回给最开始发起授权的业务。

唯一的区别在于,在不使用proxyyourcom的时候,你从应用发起微信授权的链接应该是这样的:  
>

后面这个链接跟上面的比:  
1 后面的链接中的host变成了proxyyourcom,也就是代理的授权回调域名;    
2 后面的多了一个device参数,这个是必要的。因为微信pc端跟移动端的授权地址是不一样的,而后面的链接是发送个proxyyourcom的,所以需要多加个参数告诉它在转发给授权申请给微信的时候,是用PC端还是移动端的授权地址。

1 用户从我们的应用触发需要授权的 *** 作,比如点击微信登录;    
2 应用收到这种用户请求后,将用户重定向到微信提供的一个授权页面:    
或    
3 用户通过微信扫码(PC端授权,上边左图)或者点击确认按钮(移动端授权,上边右图)告知微信,授权应用访问自己的微信账号信息;    
4 微信收到用户的授权许可后,生成授权码,并把它作为参数回调至应用的某个页面;    
5 应用的回调页面在接收到微信的回调请求后,拿到其中的授权码,并通过微信官方提供的access token api接口获取access token;    
6 最后通过access token以及微信官方提供的另一个userinfo api接口就能获取到用户的微信账号信息。

为了实现这个过程,首先要为应用申请一个微信公众号,并将应用最终部署的域名设置到微信公众号设置里面的授权回调页面域名这个选项里面。微信官方对这个选项的说明如下:

关于网页授权回调域名的说明

1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 >

2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:,配置以后此域名下面的页面>

3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可

由此可见,这个规则极其严格。如果说我们的应用最终部署的时候只有一个域名,那么这种规则不会有什么问题;但是考虑到将来应用的复杂性,我们可能在应用设计之初就会对应用做拆分,然后不同的业务采用不同的二级域名来部署。比如一个带有交易的应用,你可能会把登录注册,交易管理和常规业务都独立出来,然后采用以下的方式来部署它们:  
>

那么这种情况该如何处理?

当下的解决方案是引入一个新的非常简单的应用来作为微信授权的代理服务,可以这么做:  
1 把公众号的网页授权接口域名设置成另外一个子域名,如proxyyourcom;    
2 然后把php_weixin_proxy里面的indexphp部署到proxyyourcom

php_weixin_proxy下的indexphp是一个很简单的php文件,你可以直接查看源码了解它的实现方式。因为当前项目的环境,我采用php来完成这个代理服务实现,实际上,你完全可以用任意平台语言来完成类似的功能。

当其它业务需要发起微信授权时,将授权请求先发到proxyyourcom,然后proxyyourcom会把这个请求转发到微信;  
当用户同意授权后,proxyyourcom会收到微信的授权回调,并把回调结果(code、state参数)原封不动地再返回给最开始发起授权的业务。

唯一的区别在于,在不使用proxyyourcom的时候,你从应用发起微信授权的链接应该是这样的:  
>

后面这个链接跟上面的比:  
1 后面的链接中的host变成了proxyyourcom,也就是代理的授权回调域名;    
2 后面的多了一个device参数,这个是必要的。因为微信pc端跟移动端的授权地址是不一样的,而后面的链接是发送个proxyyourcom的,所以需要多加个参数告诉它在转发给授权申请给微信的时候,是用PC端还是移动端的授权地址。

整体方案思路:

小结:

这个方案我测试过,是行的通的。虽然说引入了代理服务,增加了一次重定向 *** 作,不过由于这个授权请求并不是所有请求都需要,所以实际上也不会对用户体验产生多大的影响,但是从架构上来说,它的好处很明显,能够配合着应用的拆分逻辑,集成同一个公众号的登录及支付功能,不必为每个子应用都单独申请一个公众号来开发了(这种方式从业务上来说也不合理,一个公司哪需要运营那么多公众号)。

很多网文对于springboot Security OAuth2对于授权码模式都是千篇一律介绍那些最基本的 *** 作,很多新手都会那些 *** 作,但慢慢就会迷惑懵懂了。这如何用于实际中?难道让用户去复制网址上的code,然后去post拿到token?这肯定是不行。基于这样问题,我做了个简单项目例子来说明
关于springboot Security OAuth2方面的概念基础知识,网上的教材多得是,我不重复了。
我用springboot的版本是212RELEASE,springcloud的版本是GreenwichRC2。本项目中有两个工程服务,a服务和b服务,两个pomxml中都加入

b服务中pomxml多添加如下

先说测试结果
访问b服务不需认证的连接,b服务的端口为8082

点击获取数据后,会自动跳转到a服务的登陆界面,a服务的端口是8083

输入用户名和密码点击登陆后,转入授权界面

端口是8082,地址多了参数。页面多了个5数字,是从b服务器中带上token获取的,控制台打印出token。在这个项目里没去掉地址后面的参数。

从现在的代码上只能有限地分析问题所在:
你在10869887:9998上的服务返回了“请求无效 (Bad request)”的应答,说明请求肯定是提上去了,但至于是OAuth拒绝了你(如密码错误),还是那边产生了其他异常,从图中无法判断。如果有可能,你查看一下返回应答的内容,可能有细节信息的帮助,要是能直接调试那个站点的代码当然就更好了。或者你可以使用Fiddler2之类的软件,手动创建一个请求,看看能够login,来判断问题是出在请求端,还是服务端。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/12689983.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-27
下一篇 2023-05-27

发表评论

登录后才能评论

评论列表(0条)

保存