上回我们聊到,既然Spring官网也有提到,要学习Spring Security OAuth相关的知识,最好先学习OAuth2.0相关的知识,而官网中OAuth 2.0 Framework的链接地址对应的就是rfc6749的文档,结构是这样的:
额全是字,是不是看起来有点头疼。
强哥会将文档分为两部分讲解:Obtaining Authorization(获取Access Token的方式)和Security Considerations(OAuth2.0的安全思考)。
为什么只分这两部分,因为文档中主要就是围绕这两个点来说的 。熟悉了这两点,基本上也就熟悉了OAuth2.0。当然,可能有的小伙伴还不知道OAuth2.0到底是个啥,那就先转到今天的第二篇推文去了解下再回来吧。
Obtaining Authorization章节目录如下:
没错,OAuth2.0定义了4种获取授权(即access token-访问令牌)的方式,4.5节Extension Grants因为是扩展,就先不算在里面。抽象的获取流程图如下:
为什么说是抽象的,因为根据四种获取授权的方式,具体的流程图会有变化。
注意,不管哪一种授权方式,第三方应用申请令牌之前,都必须先到授权服务器注册,登记应用的
- redirection URI(用于接口的回调和安全验证,在之后的Security Considerations相关推文会说明)
- client type(分为:confidential和pulic,用于说明第三方应用是否有能力足够安全的保护凭证)
- 其他额外信息。
登记后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的:
接下来我们开始正式讲解获取授权令牌的方式。
Authorization Code Grant(授予授权码方式)
简单的说就是第三方网站或应用需要先获取到一个Authorization Code,然后再拿着这个Authorization Code去获取Access Token的过程。流程图如下:
要弄懂上面的流程图,首先必须先了解四个角色分别代表的意思:
- resource owner:资源主人,即登录用户;
- user-agent:用户代理,一般指登录用户使用的浏览器;
- authorization server:认证服务器,即服务提供商专门用来处理认证的,比如微信的授权服务器;
- 服务器client:客户端,即第三方网站或应用程序,比如打开知乎,使用第三方登录,选择微信登录,这时候知乎应用就是客户端。
为了更好理解,强哥把图翻译了一下:
也就是说:
(A)强哥用浏览器,进去了知乎的网站,但是知乎需要我们登录才能访问其网站的信息。然后强哥在网站上选择了微信授权登录,于是知乎的服务就会让强哥的浏览器跳转到微信的授权服务器(带着知乎在微信授权服务器注册登记过的Client Identifier和Redirection URL)。
知乎发起请求类似如下:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Ezhihu%2Ecom%2Fcb HTTP/1.1Host: server.wechat.com
其中response_type=code就是说明本次是通过code获取token的,client_id即上面指的Client Identifier,redirect_uri是经过urlencoded过的重定向回调地址。
(B)微信授权服务器会在强哥的浏览器显示一个微信扫码的界面,用微信扫码后,就直接会在手机微信弹出授权页面(提示知乎需要获取我的昵称、头像、地区及性别等信息)。
(C)强哥选择“同意”后,微信的授权服务器就会使强哥的浏览器跳转到(A)步骤知乎服务器传递给微信授权服务器的Redirection URL,当然在URL上会附带Authorization Code(授权码)。注意:Redirection URL只有在这个步骤中被用来异步回调跳转的。之后再被传递是为了安全考虑。也就是说,获取Authorization Code的步骤其实是异步的,毕竟需要等待用户的授权才行,这个时间可能要等很长,所以同步不太现实。
微信授权服务器返回的结果类似如下:
HTTP/1.1 302 FoundLocation: https://client.zhihu.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
其中code解释比较关键,强哥直接附上原文截图:
也就是说,最好设置过期时间最大10分钟,且只能使用一次。当然,这里的授权服务器是微信,我们也管不了这么多。如果有自己让我们自己提供自己应用的授权服务器,那么,这个就要注意咯。
(D)知乎网站的Redirection URL地址在接收了回调解析出Authorization Code后,将Authorization Code和Redirection URL再次去调用微信的授权服务器获取Access Token。
知乎发起请求类似如下:
POST /token HTTP/1.1Host: server.wechat.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
其中grant_type必须是“authorization_code”,而code就是上面步骤返回的授权码。
(E)微信授权服务器获取到知乎发过来的获取Access Token的请求,在对应的接口中,经过鉴权通过后,将Access Token以及Refresh Token返回给知乎服务器。
注意:这里获取Access Token的过程是同步的,直接就在知乎发起获取Access Token的请求中就把Access Token返回给了知乎,所以说这次的Redirection URL请求参数是为了安全考虑的。
微信授权服务器返回的结果类似如下:
HTTP/1.1 200 OKContent-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: no-cache {"access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA","example_parameter":"example_value" }
可以看到除了access_token外,还有一些其他的返回数据,expires_in为超时时间,而refresh_token这个也比较重要,后文会进行解释。
(F)知乎在获取到微信授权服务器返回的Access Token后,页面就可以直接让用户登录啦(当然,正常是这样的,不过现在知乎网站还是需要你将微信账号绑定到具体的个人的手机号,即经过一次手机号的绑定,可能知乎还是以手机号作为识别用户的唯一方式,这样确实能避免因为微信以后不给授权导致用户无法登录的问题)。
好了,以上就是整个授权码获取授权令牌的过程。经过强哥这么解释,应该比较清晰了吧。
熟悉了最复杂也最安全的Authorization Code Grant授权码授权方式之后,之后的三种方式相对来说会简单许多。
Implicit Grant(隐式获取)
隐式获取Access Token的方式,适用于纯前端的服务。不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
流程图如下:
流程图中,获取Access Token的请求类似如下:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1Host: server.example.com
上面 URL 中,response_type参数为token,表示要求直接返回令牌。
授权服务器返回结果类似如下:
HTTP/1.1 302 FoundLocation: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
我们看到,返回的结果是通过重定向返回的,结果数据存在Location响应头中。且令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring)。
使用锚点也是为了安全考虑。不过开发人员需要注意到有些user-agents并不支持解析锚点数据,所以需要进行二次处理:
这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。
注意:这种方式不支持返回Refresh Token,所以我们在G步骤中并没有看到Refresh Token的返回。
Resource Owner Password Credentials Grant(密码方式获取)
如果你高度信任某个应用,则可以使用这种方式。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。支持返回refresh token。
流程图如下:
获取Token的请求如下:
POST /token HTTP/1.1Host: server.example.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencoded grant_type=password&username=johndoe&password=A3ddj3w
上面 URL 中,grant_type参数是授权方式,这里的password表示"密码式",username和password是资源拥有者的用户名和密码。
注意:为了避免密码被暴力破解,授权服务器需要对请求做一些必要的限制:
返回授权码结果类似如下:
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: no-cache {"access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA","example_parameter":"example_value" }
也是直接在同步返回。这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。
Client Credentials Grant(客户端凭证方式获取)
适用于没有前端的命令行应用,即在命令行下请求令牌。是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。且不支持返回Refresh Token。
流程图如下:
获取Token的请求如下:
POST /token HTTP/1.1Host: server.example.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencoded grant_type=client_credentials
上面 URL 中,grant_type参数等于client_credentials表示采用凭证式,client_id和client_secret用来让授权服务器确认Client的身份。
返回授权码结果类似如下:
HTTP/1.1 200 OKContent-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: no-cache {"access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"example_parameter":"example_value"}
注意到,不会返回Refresh Token。
好了,以上就是四种获取授权令牌的方式的解析了。好累……
Refresh Token(刷新令牌)
最后就是聊聊我们一直提到的Refresh Token了。为什么要单独把它提出来聊一下,因为强哥觉得这个Refresh Token也很重要。
我们从上面的4中获取Access Token的方式中发现,只有Authorization Code Grant和Resource Owner Password Credentials Grant两种方式在返回结果中可以附带返回Refresh Token。
Refresh Token的作用是:当Access Token无效或者过期的时候,Client可以拿着Refresh Token去获取新的Access Token,然后继续获取用户资源。因为Refresh Token是长期有效的。所以也存在一定的安全隐患。
获取流程图如下:
获取请求如下:
POST /token HTTP/1.1Host: server.example.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
返回结果如下:
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: no-cache {"access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA","example_parameter":"example_value" }
为了解决Refresh Token生命周期过程的安全隐患。授权服务器会返回一个新的Access Token和新的Refresh Token。同时旧的Refresh Token会失效。这样就可以避免Refresh Token被别人获取后可以随意的获取Access Token。
OK,以上就是所有的关于Obtaining Authorization的想过内容了。内容很多,不过还是值得细品。不夸张的说:读完本文相当于读完了rfc6749文档的一半内容。
之后的推文就是关于Security Considerations方面的内容啦。敬请期待~