我们登陆豆瓣网的时候,如果不想单独注册豆瓣网账号的话,就可以选择用QQ、微博或者微信账号进行授权登录。这种SSO单点登录背后的原理就是OAuth。
四种角色
在OAuth2.0协议中定义了以下四个角色:
resource owner(资源拥有者):能够有权授予对保护资源访问权限的实体。例如我们使用通过微信账号登录豆瓣网,而微信账号信息(头像、名称、电话、email、地址等)的实际拥有者就是资源拥有者,也被称为最终用户。
resource server(资源服务器):承载受保护资源的服务器,能够接收使用访问令牌对受保护资源的请求并响应,它与授权服务器可以是同一服务器,也可以是不同服务器。微信服务器就是资源服务器。
client(客户端):代表资源所有者及其授权发出对受保护资源请求的应用程序,如豆瓣网。
authorization server(授权服务器):认证服务器,即服务提供商专门用来处理认证授权的服务器。如微信开放平台服务器。
access token与refresh token
与访问令牌(Access Token)不同的是,刷新令牌(Refresh Token)仅用于授权服务器,从不发送到资源服务器。其目的是在访问令牌( Access Token)过期前,重新获取新的访问令牌(Access Token),不需要资源所有者(Resource Owner)重新确认授权。
授权模式
OAuth2.0定义了四种授权模式,它们分别是:
授权码模式(authorization code):授权码模式是功能最完整、流程最严密的授权模式。
简化模式(implicit)
密码模式(resource owner password credentials)
客户端模式(client credentials)
实际上大部分应用场景中使用的都是授权码模式。
authorization code
首先微信用户点击豆瓣网微信授权登录按钮后,豆瓣网会将请求通过URL重定向的方式跳转至微信用户授权界面;
此时微信用户在微信上进行身份认证,与豆瓣网并无交互了,这一点非常类似于我们使用网银支付的场景;
用户使用微信客户端扫描二维码认证或者输入用户名密码后,微信会验证用户身份信息的正确性,如正确,则认为用户确认授权微信登录豆瓣网,此时会先生成一个临时凭证authorization code,并携带此凭证通过用户浏览器将请求重定向回豆瓣网在第一次重定向时携带的callBackUrl地址;
之后用户浏览器会携带authorization code访问豆瓣网服务,豆瓣网则通过此临时凭证再次调用微信授权接口,获取正式的访问凭据access_token;
在豆瓣网获取到微信授权访问凭据access_token后,豆瓣网通过此token再访问微信提供的相关接口,获取微信授权允许的用户信息,如头像,昵称等,并据此完成自身的用户逻辑。
过程:
客户端携带 client_id, scope, redirect_uri, state 等信息引导用户请求授权服务器的授权端点下发 code。
授权服务器验证客户端身份,验证通过则询问用户是否同意授权(此时会跳转到用户能够直观看到的授权页面,等待用户点击确认授权)。
假设用户同意授权,此时授权服务器会将 code 和 state(如果客户端传递了该参数)拼接在 redirect_uri 后面,以302形式下发 code。
客户端携带 code, redirect_uri, 以及 client_secret 请求授权服务器的令牌端点下发 access_token (这一步实际上中间经过了客户端的服务器,除了 code,其它参数都是在应用服务器端添加)。
授权服务器验证客户端身份,同时验证 code,以及 redirect_uri 是否与请求 code 时相同,验证通过后下发 access_token,并选择性下发 refresh_token。
获取授权码
授权码是授权流程的一个中间临时凭证,是对用户确认授权这一操作的一个暂时性的证书,其生命周期一般较短,协议建议最大不要超过10分钟,在这一有效时间周期内,客户端可以凭借该暂时性证书去授权服务器换取访问令牌。
请求参数示例:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https://client.example.com/cb HTTP/1.1 Host: server.example.com
客户端携带上述参数请求授权服务器的令牌端点,授权服务器会验证客户端的身份以及相关参数,并在确认用户登录的前提下弹出确认授权页询问用户是否授权,如果用户同意授权,则会将授权码(code)和state信息(如果客户端传递了该参数)添加到回调地址后面,以 302 的形式下发。
成功响应参数说明:
成功响应示例:HTTP/1.1302 FoundLocation: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
如果请求参数错误,或者服务器端响应错误,那么需要将错误信息添加在回调地址后面,以 302 形式下发(回调地址错误,或客户端标识无效除外)。
错误响应参数说明:
错误响应示例:
HTTP/1.1302 FoundLocation: https://client.example.com/cb?error=access_denied&state=xyz
下发访问令牌
授权服务器的授权端点在以 302 形式下发 code 之后,用户 User-Agent,比如浏览器,将携带对应的 code 回调请求用户指定的 redirect_url,这个地址应该能够保证请求打到应用服务器的对应接口,该接口可以由此拿到 code,并附加相应参数请求授权服务器的令牌端点,授权端点验证 code 和相关参数,验证通过则下发 access_token。
请求参数说明:
如果在注册应用时有下发客户端凭证信息(client_secret),那么客户端必须携带该参数以让授权服务器验证客户端的有效性。
针对客户端凭证需要多说的一点就是,不能将其传递到客户端,客户端无法保证凭证的安全,凭证应该始终留在应用的服务器端,当下发code回调请求到应用服务器时,在服务器端携带上凭证再次请求下发令牌。
请求参数示例:
POST /token HTTP/1.1Host: server.example.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https://client.example.com/cb
授权服务器需要验证客户端的有效性,以及是否与之前请求授权码的客户端是同一个(请求授权时的信息可以记录在 code,或以 code 为 key 建立缓存),授权服务器还要保证code 处于生命周期内(推荐10分钟内有效),且只能被使用一次。授权服务器验证通过之后,生成 access_token,并选择性下发 refresh_token,OAuth2.0 协议明确了 token 的下发策略,对于生成策略没有做太多说明。
成功响应参数说明:
最后访问令牌以 JSON 格式响应,并要求指定响应首部 Cache-Control: no-store 和 Pragma: no-cache。
成功响应示例:
HTTP/1.1200 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”}
错误响应参数说明:
错误响应示例:
HTTP/1.1400 Bad RequestContent-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: no-cache { “error”: “invalid_request”}
授权码授权流程分为两步走,将用户授权与下发 token 分开,这给授权带来了更多的灵活性,正常授权过程中必须经过用户登录这一步骤,在用户已登录的前提下,可以直接询问用户是否同意授权,但是在一些场景下,比如内部走 SSO 登录的应用集成了基于 OAuth 登录的第三方应用,这个时候在 OAuth 授权登录第三方应用时用户体验较好的流程是不需要用户再一次输入用户名和密码登录的,这就需要将外围APP 的登录态传递给该应用,但是这样是存在安全问题的,用户的登录态必须把握在走SSO 登录流程的应用中,这样的场景下授权码授权模式的两步走流程就可以满足在不交出用户登录态的情况下,无需再次登录即可授权。
内部应用可以拿着第三方应用的client_id 等信息代替第三方应用去请求获取 code,因为自己持有用户的登录态,所以过程中无需用户再次输入用户名和密码,拿到 code 之后将其交给第三方应用,第三方应用利用 code 和自己的 client_secret 信息去请求授权服务器下发 token,整个流程内部应用不需要交出自己持有的用户登录态,第三方应用也无需交出自己的 client_secret 信息,最终却能够实现在保护用户登录凭证的前提下无需再次登录即可完成整个授权流程。
令牌的刷新
为了防止客户端使用一个令牌无限次数使用,令牌一般会有过期时间限制,当快要到期时,需要重新获取令牌,如果再重新走授权码的授权流程,对用户体验非常不好,于是OAuth2.0 允许用户自动更新令牌。
具体方法是,B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。
https://b.com/oauth/token?grant_type=refresh_token&client_id=xxx&client_secret=xxx&refresh_token=xxx
b.com 是授权服务器(Authorization Server),请求包含如下内容:
grant_type:授权类型,值为 ‘refresh_token’。
client_id:客户端 id,即客户端(Client)在授权服务器上注册被分配的 id.
client_secret:客户端(Client)和授权服务器(Authorization Server)通行的密钥,由授权服务器颁发,在特殊需要确认的情况下需要作为验证条件。
refresh_token:用户获取新的 access_token 的 refresh_token。
implicit
简化模式是对授权码模式的简化,用于在浏览器中使用脚本语言如JS实现的客户端中,它的特点是不通过客户端应用程序的服务器,而是直接在浏览器中向认证服务器申请令牌,跳过了“授权码临时凭证”这个步骤。
由于这种方式访问令牌access_token会在URL片段中进行传输,因此可能会导致访问令牌被其他未经授权的第三方截取,所以安全性上并不是那么的强壮。
resource owner password credentials
用户需要向客户端提供自己的用户名和密码。安全性非常低,因此一般不会考虑使用这种模式。
client credentials
客户端模式是指客户端以自己的名义,而不是以用户的名义,向“服务提供方”进行认证。
文章评论