OAuth 简介

OAuth 简介

1、前言

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

2、角色

​ 首先介绍 OAuth2 协议中的角色,整个授权协议的流程都是围绕着这些角色:

  • resource owner:资源所有者,能够允许访问瘦保护资源的实体,如果是个人则被称为 end-user

  • resource server:资源服务器,托管受保护资源的服务器

  • client:客户端,使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。例如 web 应用及移动程序等

  • authorization server:授权服务器,能够向客户端颁发令牌

  • user-agent:用户代理,帮助资源所有者与客户端沟通的工具,一般为 web 浏览器、移动 APP 等

举例说明:

我想在 CSDN网站看到一篇文章想进行评论和留言,但是评论的时候发现必须要登录以后才能留言。此时有两个选项,第一是在 CSDN注册一个新的账号然后登录去留言,第二是使用 GitHub 的账号登录然后评论留言。如果我们现在选择使用 GitHub 登录那么 OAuth2 的认证流程就开始了。此时 CSDN 相对于 GitHub 就是一个客户端,而我们使用的浏览器就是一个用户代理。当从 GitHub 授权服务器获得 token 后,CSDN 需要请求 GitHub 的张哈票信息,请求的地址就是 GitHub 的资源服务器

3、授权流程

img

  1. Authrization Request
    客户端向用户请求对资源服务器的authorization grant
  2. Authorization Grant(Get)
    如果用户授权该次请求,客户端将收到一个authorization grant
  3. Authorization Grant(Post)
    客户端向授权服务器发送它自己的客户端身份标识和上一步中的authorization grant,请求访问令牌。
  4. Access Token(Get)
    如果客户端身份被认证,并且authorization grant也被验证通过,授权服务器将为客户端派发access token。授权阶段至此全部结束。
  5. Access Token(Post && Validate)
    客户端向资源服务器发送access token用于验证并请求资源信息。
  6. Protected Resource(Get)
    如果access token验证通过,资源服务器将向客户端返回资源信息。

4、客户端应用注册

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

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

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

4.1 Client ID 和 Client Secret

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

5、授权许可(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)

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

5.1 授权码模式(Authorization Code Flow)

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

img

1. User Authorization Request

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

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
  • response_type=code
    此参数和参数值用于提示授权服务器当前客户端正在进行Authorization Code授权流程。
  • client_id
    客户端身份标识。
  • redirect_uri
    标识授权服务器接收客户端请求后返回给User-agent的跳转访问地址。
  • scope
    指定客户端请求的访问级别。
  • state
    由客户端生成的随机字符串,步骤2中用户进行授权客户端的请求时也会携带此字符串用于比较,这是为了防止CSRF攻击。

2. User Authorizes Applcation

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

img

3. Authorization Code Grant

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

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

state值将与客户端在请求中最初设置的值相同。客户端将检查重定向中的状态值是否与最初设置的状态值相匹配。这可以防止CSRF和其他相关攻击。

code是授权服务器生成的authorization code值。code相对较短,通常持续1到10分钟,具体取决于授权服务器设置。

4. Access Token Request

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

  • grant_type=authorization_code - 这告诉服务器当前客户端正在使用Authorization Code授权流程。
  • code - 应用程序包含它在重定向中给出的授权码。
  • redirect_uri - 与请求authorization code时使用的redirect_uri相同。某些资源(API)不需要此参数。
  • client_id - 客户端标识。
  • client_secret - 应用程序的客户端密钥。这确保了获取access token的请求只能从客户端发出,而不能从可能截获authorization code的攻击者发出。

5. Access Token Grant

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

1
2
3
4
5
6
7
{
"access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"token_type":"bearer",
"expires_in":3600,
"refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
"scope":"create delete"
}

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

5.2 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

img

1. 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授权流程相比没有并什么变化。

2. User Authorizes Application(略)

3. 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 参数。

4. User-agent Follows the Redirect URI

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

5. Application Sends Access Token Extraction Script

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

6. Access Token Passed to Application

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

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

5.3 Resource Owner Password Credentials Flow

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

img

1. Resource Owner Password Credentials From User Input

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

2. 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

3. 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"
}

5.4 Client Credentials Flow

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

img

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

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"
}

首先我们需要明确的是,即使用户刚登录过 GitHub,CSDN 也不可能向 GitHub 随便发一个请求就能拿到访客信息,这显然是不安全的。就算用户允许你获取他在 GitHub 上的信息,但是 GitHub 为了保护用户的信息安全,也不会让你随意获取。所以在操作之前,CSDN 与 GitHub 之间需要有一个协商。

####3.1 网站和 GitHub 之间的协商

GitHub 会对用户的权限进行分类,比如读取仓库信息的权限,写入仓库的权限,读取用户信息的权限,修改用户信息的权限等。如果我想获取用户的信息,GitHub 会要求我现在他的平台上注册一个应用,在申请的时候标明需要获取用户的哪些权限,用多少就申请多少,并且在申请的时候填写你的网站域名,GitHub 只会允许在这个域名中获取用户信息。

此时 CSDN 已经和 GitHub 之间达成了共识,GitHub 也会发给 CSDN 两张门票,一张是clientID,另一张是clientSecrect

3.2 用户与 GitHub 之间的协商

用户进入网站后,点击 GitHub 登录按钮的时候,CSDN 会将第一步拿到的 clientID 交给用户,让他进入到 GitHub 的授权页面,GitHub 看到用户手中的 clientID 会知道CSDN 是被允许获取信息的,于是 GitHub 将 CSDN 想要获取的权限罗列出来,并询问用户是否允许 CSDN 获取这些权限

1
2
3
4
5
6
7
8
// 用户登录 github,协商
GET https://github.com/login/oauth/authorize

// 协商凭证
params = {
client_id: "xxxx",
redirect_uri: "http://my-website.com"
}

如果用户觉得 CSDN 网站想要的权限太多了,或者用户压根就不想让 CSDN 获取信息,选择了拒绝的话,整个 OAuth2的认证就结束了,认证也以失败告终。如果用户觉得 OK 的话,在授权页面点击确认授权后,页面就会跳转到我预先设定的’redirect_url’并附带一个盖了章的门票 code

1
2
// 协商成功后带着盖了章的 code
Location: http://my-website.com?code=xxx

这个时候用户和 GitHub 之间的协商也就完成了,GitHub 会在自己的系统中记录此次协商,标识该用户已经允许在GitHub 进行访问、操作和使用关于用户的部分资源

3.3 告诉 GitHub,CSDN 要来拜访了

在第二步中,CSDN 已经拿到了盖过章的门票code,但是这个 code 只能表明,用户允许 CSDN 从 GitHub 上获取用户的数据,如果我直接拿这个 code 去 GitHub 访问数据一定会被拒绝,因为任何人都可以持有 code,GitHub 不知道 code 持有方就是 CSDN

还记得之前申请应用的时候 GitHub给的两张门票,clientID 已经使用过了,接下来就轮到 clientSecrect

1
2
3
4
5
6
7
8
9
10
// 网站和 github 之间的协商
POST https://github.com/login/oauth/access_token

// 协商凭证包括 github 给用户盖的章和 github 发给我的门票
params = {
code: "xxx",
client_id: "xxx",
client_secret: "xxx",
redirect_uri: "http://my-website.com"
}

拿着用户盖过章的 code 和能够标识个人身份的 clientID、clientSecrect 去拜访 GitHub,拿到最后的绿卡 access_token

1
2
3
4
5
6
7
// 拿到最后的绿卡
response = {
access_token: "e72e16c7e42f292c6912e7710c838347ae178b4a"
scope: "user,gist"
token_type: "bearer",
refresh_token: "xxxx"
}

3.4 用户使用 GitHub 在 CSDN 留言

1
2
3
// 访问用户数据
GET https://api.github.com/user
?access_token=e72e16c7e42f292c6912e7710c838347ae178b4a

上一步 github 已经把最后的绿卡 access_token 给我了,通过 github 提供的 API 加绿卡就能够访问用户的信息了,能获取用户的哪些权限在 response 中也给了明确的说明,scope 为 user 和 gist,也就是只能获取 user 组和 gist 组两个小组的权限,user 组中就包含了用户的名字和邮箱等信息了。

1
2
3
4
5
// 告诉我用户的名字和邮箱
response = {
username: "barretlee",
email: "barret.china@gmail.com"
}

值得一看的文章:

https://learnku.com/articles/20082

https://learnku.com/articles/20031


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!