抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

OAuth2介绍

前言

​ OAuth 2 是一个授权框架,或称授权标准,它可以使第三方应用程序或客户端获得对HTTP服务上(例如 Google,GitHub )用户帐户信息的有限访问权限。OAuth 2 通过将用户身份验证委派给托管用户帐户的服务以及授权客户端访问用户帐户进行工作。综上,OAuth 2 可以为 Web 应用 和桌面应用以及移动应用提供授权流程。

OAuth 协议为用户资源的授权提供了一个安全又简易的标准。与以往的授权方式不同之处是 OAuth的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 OAuth是安全的。OAuthOpen Authorization 的简写

OAuth 本身不存在一个标准的实现,后端开发者自己根据实际的需求和标准的规定实现。其步骤一般如下:

  1. 第三方要求用户给予授权
  2. 用户同意授权
  3. 根据上一步获得的授权,第三方向认证服务器请求令牌(token
  4. 认证服务器对授权进行认证,确认无误后发放令牌
  5. 第三方使用令牌向资源服务器请求资源
  6. 资源服务器使用令牌向认证服务器确认令牌的正确性,确认无误后提供资源

OAuth2.0是为了解决什么问题

任何身份认证,本质上都是基于对请求方的不信任所产生的。同时,请求方是信任被请求方的,例如用户请求服务时,会信任服务方。所以,**身份认证就是为了解决身份的可信任**问题。

​ 在OAuth2.0中,简单来说有三方:用户(这里是指属于服务方的用户)、服务方(如微信、微博等)、第三方应用

  1. 服务方不信任用户,所以需要用户提供密码或其他可信凭据
  2. 服务方不信任第三方应用,所以需要第三方提供自已交给它的凭据(如微信授权的code,AppID等)
  3. 用户部分信任第三方应用,所以用户愿意把自已在服务方里的某些服务交给第三方使用,但不愿意把自已在服务方的密码等交给第三方应用

引入场景

在正式讲解之前,这里先引入一段应用场景,用以与后文的角色讲解对应。

​ 开发者A注册某IT论坛后,发现可以在信息栏中填写自己的 Github 个人信息和仓库项目,但是他又觉得手工填写十分麻烦,直接提供 Github 账户和密码给论坛管理员帮忙处理更是十分智障。
​ 不过该论坛似乎和 Github 有不可告人的秘密,开发者A可以点击“导入”按钮,授权该论坛访问自己的 Github 账户并限制其只具备读权限。这样一来, Github 中的所有仓库和相关信息就可以很方便地被导入到信息栏中,账户隐私信息也不会泄露。
​ 这背后,便是 OAuth 2 在大显神威。

OAuth2 角色

OAuth 2 标准中定义了以下几种角色:

  • 资源所有者(Resource Owner)
  • 资源服务器(Resource Server)
  • 授权服务器(Authorization Server)
  • 客户端(Client)

资源所有者(Resource Owner)

​ 资源所有者是 OAuth 2 四大基本角色之一,在 OAuth 2 标准中,资源所有者即代表授权客户端访问本身资源信息的用户(User),也就是应用场景中的“开发者A”。客户端访问用户帐户的权限仅限于用户授权的“范围”(aka. scope,例如读取或写入权限)。

如果没有特别说明,下文中出现的”用户”将统一代表资源所有者。

资源/授权服务器(Resource/Authorization Server)

资源服务器托管了受保护的用户账号信息,而授权服务器验证用户身份然后为客户端派发资源访问令牌。

​ 在上述应用场景中,Github 既是授权服务器也是资源服务器,个人信息和仓库信息即为资源(Resource)。而在实际工程中,不同的服务器应用往往独立部署,协同保护用户账户信息资源。

客户端(Client)

在 OAuth 2 中,客户端即代表意图访问受限资源的第三方应用。在访问实现之前,它必须先经过用户者授权,并且获得的授权凭证将进一步由授权服务器进行验证。

如果没有特别说明,下文中将不对”应用”,“第三方应用”,“客户端”做出区分。

OAuth 2 的授权流程

目前为止你应该对 OAuth 2 的角色有了些概念,接下来让我们来看看这几个角色之间的抽象授权流程图和相关解释。

请注意,实际的授权流程图会因为用户返回授权许可类型的不同而不同。但是下图大体上能反映一次完整抽象的授权流程。

Authrization Request

​ 客户端向用户请求对资源服务器的authorization grant

Authorization Grant(Get)

​ 如果用户授权该次请求,客户端将收到一个authorization grant

Authorization Grant(Post)

​ 客户端向授权服务器发送它自己的客户端身份标识和上一步中的authorization grant,请求访问令牌。

Access Token(Get)

​ 如果客户端身份被认证,并且authorization grant也被验证通过,授权服务器将为客户端派发access token。授权阶段至此全部结束。

Access Token(Post && Validate)

​ 客户端向资源服务器发送access token用于验证并请求资源信息。

Protected Resource(Get)

​ 如果access token验证通过,资源服务器将向客户端返回资源信息。

客户端应用注册

在应用 OAuth 2 之前,你必须在授权方服务中注册你的应用。如 Google Identity Platform 或者 Github OAuth Setting,诸如此类 OAuth 实现平台中一般都要求开发者提供如下所示的授权设置项。

  • 应用名称
  • 应用网站
  • 重定向URI或回调URL

重定向URI是授权方服务在用户授权(或拒绝)应用程序之后重定向供用户访问的地址,因此也是用于处理授权码或访问令牌的应用程序的一部分。

Client ID 和 Client Secret

​ 一旦你的应用注册成功,授权方服务将以client idclient secret的形式为应用发布client credentials(客户端凭证)。client id是公开透明的字符串,授权方服务使用该字符串来标识应用程序,并且还用于构建呈现给用户的授权 url 。当应用请求访问用户的帐户时,client secret用于验证应用身份,并且必须在客户端和服务之间保持私有性。

授权许可(Authorization Grant)

如上文的抽象授权流程图所示,前四个阶段包含了获取authorization grantaccess token的动作。授权许可类型取决于应用请求授权的方式和授权方服务支持的 Grant Type。OAuth 2 定义了四种 Grant Type,每一种都有适用的应用场景。

  • 授权码模式(Authorization Code)
    结合普通服务器端应用使用。
  • 简化模式(Implicit)
    结合移动应用或 Web App 使用。
  • 密码模式(Resource Owner Password Credentials)
    适用于受信任客户端应用,例如同个组织的内部或外部应用。
  • 客户端模式(Client Credentials)
    适用于客户端调用主服务API型应用(比如百度API Store)

以下将分别介绍这四种许可类型的相关授权流程。

Authorization Code Flow(授权码模式)

Authorization Code 是最常使用的一种授权许可类型,它适用于第三方应用类型为server-side型应用的场景。Authorization Code授权流程基于重定向跳转,客户端必须能够与User-agent(即用户的 Web 浏览器)交互并接收通过User-agent路由发送的实际authorization code值。

请求步骤
  1. 用户访问客户端,后者将前者导向认证服务器
  2. 用户选择是否给予客户端授权
  3. 假设用户给予授权,认证服务器将用户导向客户端事先指定的重定向URI,同时附上一个授权码
  4. 客户端收到授权码,附上早先的重定向URI,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见
  5. 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)等
客户端申请认证的URI(User Authorization Request)

首先,客户端构造了一个用于请求authorization code的URL并引导User-agent跳转访问。

包含参数:

  • response_type:表示授权类型,必选项,此处的值固定为”code”

  • client_id:表示客户端的ID,必选项。(如微信授权登录,此ID是APPID

  • redirect_uri:表示重定向URI,可选项

  • scope:表示申请的权限范围,可选项 state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值

    示例:

1
2
3
4
5
6
https://authorization-server.com/auth
?response_type=code
&client_id=29352915982374239857
&redirect_uri=https%3A%2F%2Fexample-client.com%2Fcallback
&scope=create+delete
&state=xcoiv98y2kd22vusuye3kch
授权服务器认证(User Authorizes Application)

当用户点击上文中的示例链接时,用户必须已经在授权服务中进行登录(否则将会跳转到登录界面,不过 OAuth 2 并不关心认证过程),然后授权服务会提示用户授权或拒绝应用程序访问其帐户。以下是授权应用程序的示例:

认证服务器回应客户端的URI(Authorization Code Grant)

如果用户确认授权,授权服务器将重定向User-agent至之前客户端提供的指向客户端的redirect_uri地址,并附带codestate参数(由之前客户端提供),于是客户端便能直接读取到authorization code值。

1
2
3
https://example-client.com/redirect
?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
&state=xcoiv98y2kd22vusuye3kch

包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
客户端向认证服务器申请令牌的HTTP请求(Access Token Request)

现在客户端已经拥有了服务器派发的authorization code,接下来便可以使用authorization code和其他参数向服务器请求access token(POST方式)。其他相关参数如下:

包含以下参数:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。

  • code:表示上一步获得的授权码,必选项。

  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。

  • client_id:表示客户端ID,必选项。

  • client_secret :应用程序的客户端密钥。这确保了获取access token的请求只能从客户端发出,而不能从可能截获authorization code的攻击者发出。

    示例:

1
2
3
4
5
6
7
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
认证服务器发送的HTTP回复(Access Token)

服务器将会验证第4步中的请求参数,当验证通过后(校验authorization code是否过期,client idclient secret是否匹配等),服务器将向客户端返回access token

包含以下参数:

  • access_token:表示访问令牌,必选项。

  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。

  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。

  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。

  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

    示例:

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}

从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。

对比网站应用微信登录:返回样例

1
2
3
4
5
6
7
8
{ 
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

至此,授权流程全部结束。直到access token 过期或失效之前,客户端可以通过资源服务器API访问用户的帐户,并具备scope中给定的操作权限。

更新令牌

​ 如果用户访问的时候,客户端的访问令牌access_token已经过期,则需要使用更新令牌refresh_token申请一个新的访问令牌。
​ 客户端发出更新令牌的HTTP请求,包含以下参数:

  • granttype:表示使用的授权模式,此处的值固定为”refreshtoken”,必选项。
  • refresh_token:表示早前收到的更新令牌,必选项。
  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。

示例:

1
2
3
4
5
6
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
特点
  • 通过前端渠道客户获取授权码

  • 通过后端渠道,客户使用authorization code 交换access token 或refresh token

  • 假定资源拥有者和客户在不同的设备上

  • 最安全的流程,因为令牌不会传递经过User-Agent

小结

​ 授权码模式本质上是客户端通过用户名密码发起获取授权码请求,服务端根据回调地址返回授权码,客户端根据授权码访问资源服务器,资源服务器根据授权码拿到授权服务器给的access token返回给客户端,客户端就可以带着这个access token访问资源服务器上的有效资源.

Implicit Flow(简化模式)

Implicit授权流程和Authorization Code基于重定向跳转的授权流程十分相似,但它适用于移动应用和 Web App,这些应用与普通服务器端应用相比有个特点,即client secret不能有效保存和信任。

相比Authorization Code授权流程,Implicit去除了请求和获得authorization code的过程,而用户点击授权后,授权服务器也会直接把access token放在redirect_uri中发送给User-agent(浏览器)。 同时第1步构造请求用户授权 url 中的response_type参数值也由 code 更改为 tokenid_token

请求步骤
  1. 客户端重定向用户到认证服务器
  2. 用户选择是否授权给客户端
  3. 用户授权,授权服务器根据客户端请求参数uri重定向到客户端,并将token放入url的hash部分
  4. 资源服务返回一个页面,可以获取hash值
  5. 浏览器执行脚本获取令牌
  6. 浏览器叫令牌发送给客户端
客户端申请认证的URI(User Authorization Request)

客户端构造的URL如下所示:

1
2
3
https://{yourOktaDomain}.com/oauth2/default/v1/authorize?client_id=0oabv6kx4qq6
h1U5l0h7&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%3
A8080&state=state-296bc9a0-a2a2-4a57-be1a-d0e2fd9bb601&nonce=foo

response_typeresponse_type参数值为 tokenid_token 。其他请求参数与Authorization Code授权流程相比没有并什么变化。

授权服务器认证(User Authorizes Application)(略)
访问令牌重定向URI(Redirect URI With Access Token In Fragment)

假设用户授予访问权限,授权服务器将User-agent(浏览器) 重定向回客户端使用之前提供的redirect_uri。并在 uri 的 #fragment 部分添加access_token键值对。如下所示:

1
http://localhost:8080/#access_token=eyJhb[...]erw&token_type=Bearer&expires_in=3600&scope=openid&state=state-296bc9a0-a2a2-4a57-be1a-d0e2fd9bb601

token_type - 当且仅当response_type设置为 token 时返回,值恒为 Bearer

注意在Implicit流程中,access_token值放在了 URI 的 #fragment 部分,而不是作为 ?query 参数。

重定向URI(User-agent Follows the Redirect URI)

User-agent(浏览器)遵循重定向指令,请求redirect_uri标识的客户端地址,并在本地保留 uri 的 #fragment 部分的access_token信息

执行脚本获取令牌(Application Sends Access Token Extraction Script)

​ 客户端生成一个包含 token 解构脚本的 Html 页面,这个页面被发送给User-agent(浏览器),执行脚本解构完整的redirect_uri并提取其中的access_tokenaccess token信息在第4步中已经被User-agent保存)。

令牌发送给客户端(Access Token Passed to Applicatio)

User-agent(浏览器)向客户端发送解构提取的access token

至此,授权流程全部结束。直到access token 过期或失效之前,客户端可以通过资源服务器API访问用户的帐户,并具备scope中给定的操作权限。

特点
  • 适用于公开的浏览器单页面应用
  • Access Token 直接从授权服务器返回
  • 不支持Refresh Token
  • 假定资源拥有者和客户在同一设备上
  • 最容易受安全攻击
小结

简化模式,就是没有授权码的授权模式,去掉了授权码这个步骤。

密码模式(Resource Owner Password Credentials Flow)

Resource Owner Password Credentials授权流程适用于用户与客户端具有信任关系的情况,例如设备操作系统或同一组织的内部及外部应用。用户与应用交互表现形式往往体现为客户端能够直接获取用户凭据(用户名和密码,通常使用交互表单)。

请求步骤
  • 用户向客户端提供用户名和密码
  • 客户端将用户名和密码发送给认证服务器去认证授权
  • 认证服务器验证无误,返回给客户端令牌
提供用户名密码(Resource Owner Password Credentials From User Input)

​ 用户向客户端提供用户名与密码作为授权凭据。

服务器认证授权(Resource Owner Password Credentials From Client To Server)

客户端向授权服务器发送用户输入的授权凭据以请求 access token。客户端必须已经在服务器端进行注册。

1
2
3
4
5
6
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

grant_type - 必选项,值恒为 password

返回客户端令牌(Access Token Passed to Application)

授权服务器对客户端进行认证并检验用户凭据的合法性,如果检验通过,将向客户端派发 access token>

1
2
3
4
5
6
7
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
特点
  • 使用用户名密码登录的应用,比如桌面应用
  • 使用用户名/密码作为授权方式从授权服务器上获取access token
  • 一般不支持refresh token
  • 假定资源拥有者和公开客户在相同设备上
小结

密码授权模式,是对客户端极度信任的情况下,将用户名和密码交给客户端,客户端去授权服务器获取令牌。

客户端模式(Client Credentials Flow)

Client Credential是最简单的一种授权流程。客户端可以直接使用它的client credentials或其他有效认证信息向授权服务器发起获取access token的请求。

请求步骤
  • 客户端直接向授权服务器发起授权认证,获取令牌
  • 授权服务器校验通过,颁发令牌

两步中的请求体和返回体分别如下:

1
2
3
4
5
6
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

grant_type - 必选项,值恒为 client_credentials

1
2
3
4
5
6
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"example_parameter":"example_value"
}
特点
  • 适用于服务间通信,机器代表用户或者它自己
  • 只有后端渠道,使用客户凭证获取一个access token
  • 因为客户凭证可以使用对称或者非对称加密,该方式支持共享密码或者证书

名词中英文对照

英文 中文
Authorization Grant 授权许可
Authorization Code 授权码
Access Token 访问令牌
Authorization 授权
Authentication 认证

评论