OAuth 2.0
OAuth 2.0是一个使用第三方授权服务器来获取对一个受访问限制服务的有限许可的授权方式,可以实现受访问限制服务在不用知道用户在第三方授权服务器用户名密码的情况下,获取受访问限制服务器的部分权限。
例如使用京东绑定微信账号时,京东需要获取微信账号当中的一些需要授权才能访问的信息比如手机号,但是我们不能让京东直接知道微信账号和密码,而是通过直接登录微信并授权京东,让京东拿着短期失效的令牌而非长时间不改的账号密码去问微信要需要授权的信息,这就是OAuth 2.0的作用。
基本定义
OAuth 2.0是由RFC6749标准定义的,标准规定OAuth2.0授权模型中的四个角色是:
- 资源所有者(resource owner):对受保护限制资源访问行为进行授权的实体,例如用户
- 资源服务器(resource server):存储着受保护资源,可以使用令牌对访问受保护资源行为进行认证
- 授权服务器(authorization):用于颁发令牌的授权认证服务器
- 客户端(client):发出受保护资源访问请求的应用,它需要令牌来访问资源服务器
标准也规定了OAuth2.0获取令牌的四种方式分别是:
- 授权码(authorization-code)
- 隐含式(implicit)
- 密码式(password)
- 客户端凭证(client credentials)
授权方式
定义中的四种获取令牌的方式都需要在授权服务器处提前进行登记,并获取到名为客户端ID(client ID)和客户端密钥(client secret)的密钥对,这个密钥对需要保密且长期不变化,用于验证请求令牌的服务器,防止其他人随意向授权服务器请求令牌。
下面介绍四种授权方式
授权码方式
授权码(authorization code)方式是四种方式里最安全也是最常见的方式,它采用后端请求令牌、存储令牌方式,先拿着登记的客户端ID与客户端密钥向授权服务器申请一个授权码,然后再使用这个授权码获取令牌。这里“客户端ID”与“客户端密钥”中的客户端,由于是后端请求令牌,所以指的其实是请求令牌的资源服务器。
下面分步来说明颁发令牌过程
- 客户端要访问资源服务器需要授权资源,客户端就返回给用户一个带有之前登记好的客户端ID、授权类型code、接收授权码与授权范围的回调链接,用户点击这个链接就会访问授权服务器
- 用户确定授权后会被授权服务器带着授权码重定向到回调链接处
- 接着客户端带着授权码、授权方式、客户端ID、客户端密钥、接收令牌的回调链接请求授权服务器获取令牌
- 授权服务器收到请求并验证后向接收令牌的回调链接发送JSON格式的令牌,客户端收到令牌拿着令牌去访问资源服务器
这里举一个例子,假设我需要使用谷歌账号来登录Github,此时Github需要获取我谷歌账号内的邮箱地址、性别等需要授权访问的信息才能完成登录,这里授权服务器是谷歌、资源所有者是我、资源服务器是谷歌、客户端是Github。这里进一步做一下解释,这四步在例子中的体现就是
- 用户使用谷歌账号登录Github,希望Github访问自己谷歌账号中的一些私密信息来简化注册登录过程,这里客户端就是Github,资源与授权服务器均是谷歌,Github登录界面显示一个谷歌登录的链接,这个链接就是带有Github预先在谷歌处获取的客户端ID与接收谷歌要发来授权码的回调链接(同时也作为用户授权完毕跳转回Github的链接),用户点击这个链接跳转到谷歌授权的网页
- 用户登录谷歌账号(或者已经登录)后授权Github可以访问谷歌账号信息,谷歌会将用户重定向到回调链接处并带着授权码,这时候Github已经获取到了授权码
- Github带着授权码、授权方式、客户端ID、客户端密钥、接收令牌的回调链接请求谷歌获取令牌
- 谷歌验证没问题后向回调链接处发送JSON格式令牌,Github拿着令牌去访问谷歌的账号信息API
下面是请求与响应的URL格式
# 步骤一
# 客户端让用户对授权服务器发起下方对请求来获取授权码
# response_type 授权类型固定为code,必须带有
# client_id 客户端ID,必须带有
# redirect_uri 重定向URI,必须带有
# scope 申请权限范围,可选
# state 客户端状态,会被授权服务器完整返回,可选
https://auth.com/oauth2/authorize
?response_type=code
&client_id=CLIENT_ID
&redirect_uri=CALLBACK_URL
&state=xyz
&scope=read
# 步骤二
# 授权服务器向回调URL返回授权码
# code 授权码
# state 返回的那个客户端参数
https://service.com/callback
?code=AUTHORIZATION_CODE
# 步骤三
# 客户端拿到授权码后在后端向授权服务器请求令牌
# grant_type 使用授权码方式为authorization_code,必须带有
# client_id 与client_secret配合让授权服务器确认客户端身份,必须带有
# client_secret 与client_id配合让授权服务器确认客户端身份,必须带有
# code 上一步获取到的授权码,必须带有
# redirect_uri 重定向的URI,需要与步骤一的该项一致,必须带有
https://auth.com/oauth2/token
?grant_type=authorization_code
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&code=AUTHORIZATION_CODE
&redirect_uri=CALLBACK_URL
# 步骤四
# 授权服务器向redirect_uri指定网址发送JSON格式数据,其中access_token字段就是令牌
{
"access_token":ACCESS_TOKEN,
"token_type":"example",
"expires_in":3600,
"refresh_token":REFRESH_TOKEN
}
隐含式
隐含(implicit)方式是跳过授权码直接授予客户端令牌的方式,用于客户端为前端JavaScript代码的方式,但是这种方式由于将令牌存在前端,会暴露给用户和其他应用而不够安全
隐含式请求授权有两步,分别为
- 客户端返回给用户一个带有之前登记好的客户端ID、接收授权码与授权范围的回调链接,用户点击这个链接访问授权服务器
- 授权服务器收到请求并验证后向接收令牌的回调链接发起请求,请求URL带有位于URL锚点(fragment)的令牌,客户端收到令牌拿着令牌去访问资源服务器
下面是请求与响应的URL格式
# 步骤一
# 客户端让用户对授权服务器发起下方对请求来直接获取令牌
# response_type 授权类型固定为token,必须带有
# client_id 客户端ID,必须带有
# redirect_uri 重定向URI,必须带有
# scope 申请权限范围,可选
# state 客户端状态,会被授权服务器完整返回,可选
https://auth.com/oauth2/authorize
?response_type=token
&client_id=CLIENT_ID
&redirect_uri=CALLBACK_URL
&state=xyz
&scope=read
# 步骤二
# 授权服务器向客户端传来的URI发送带有access_token令牌的回应
# access_token 令牌,必须带有,注意这里是使用fragment(#xxx)而非query(?xxx)
# token_type 令牌类型,大小写不敏感,必须带有
# expires_in 以秒为单位的令牌过期时间,可以省略,但必须以其他方式设置过期时间否则不安全
# scope 权限范围,如果与步骤一scope一致则可省略
# state 返回步骤一客户端传来的state
https://service.com/oauth2/callback
#access_token=ACCESS_TOKEN
&token_type=example
&expires_in=3600
&state=xyz
注意:implicit的第二个步骤,授权服务器为返回给用户的回调链接中的参数都是以
fragment #
而非query ?
的形式传递的。这是因为如果使用query
方式,授权服务器传递给用户链接内的access_token
等信息会再次传给OAuth 2.0
中的客户端,这样无法在使用http
协议传输数据时避免中间人攻击,窃走access_token
。而使用fragment
的形式时,传递给用户的链接跳转回原网站时不会被传递,access_token
等参数会只被保留在用户浏览器这一方,避免了中间人攻击
密码式
密码式(password)即直接将授权服务器用户名和密码传递给需要访问资源服务器的客户端,安全风险更大,且服务必须可信否则密码有泄露风险
密码式请求授权有两步,分别为
- 用户为客户端提供自己在授权服务器的可授权的用户名和密码,客户端直接带着客户端ID、用户名、密码、授权方式password请求授权服务器
- 授权服务器直接返回JSON格式令牌
下面是请求与响应的URL和JSON格式
# 步骤一
# 客户端带着用户已经提交上的用户名和密码对授权服务器发起请求
# grant_type 授权类型固定为password,必须带有
# client_id 客户端ID,必须带有
# username 用户名,必须带有
# password 密码,必须带有
# scope 申请权限范围,可选
https://auth.com/oauth2/authorize
?grant_type=password
&client_id=CLIENT_ID
&username=USER_NAME
&password=PASSWORD
# 步骤二
# 授权服务器直接向客户端颁发令牌
{
"access_token":ACCESS_TOKEN,
"token_type":"example",
"expires_in":3600,
"refresh_token":REFRESH_TOKEN
}
凭证式
凭证式(client credentials)是在命令行请求的一种方式,这个方式用于网站后台授权一般不用于个人用户级别
凭证式请求授权有两步,分别为
- 客户端带着授权类型client_credentials、客户端id、客户端密钥请求授权服务器
- 授权服务器直接返回令牌
下面是请求与响应的URL格式
# 步骤一
# 客户端向授权服务器发送请求
# grant_type 授权类型固定为client_credentials,必须带有
# client_id 客户端ID,必须带有
# client_secret 客户端密钥,必须带有
https://auth.com/oauth2/token
?grant_type=client_credentials
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
# 步骤二
# 授权服务器直接向客户端颁发令牌
{
"access_token":ACCESS_TOKEN,
"token_type":"example",
"expires_in":3600,
"refresh_token":REFRESH_TOKEN
}
使用令牌
客户端网站拿到令牌之后,就可以向资源服务器请求被保护的数据了,具体做法是每次http
请求时,在http
头文件中增加值为令牌的Authorization
字段
下面是请求的URL格式
GET /admin/password HTTP/1.1
Host: secret.com
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/x-www-form-urlencoded
更新令牌
OAuth 2.0
还支持令牌更新。授权服务器在颁发access_token
授权令牌时同时也颁发一个用于更新授权令牌的refresh_token
刷新令牌,授权令牌到期前,客户端带着refresh_token
再去请求授权服务器获取新令牌
下面是请求的URL格式
# grant_type 授权类型,固定为refresh_token表刷新令牌,必须带有
# client_id 客户端ID,必须带有
# client_secret 客户端密钥,必须带有
# refresh_token 在上次授权时JSON里带着的refresh_token
# scope 表示授权范围,不能超出上次颁发令牌时申请的权限范围
https://auth.com/oauth2/token
?grant_type=refresh_token
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&refresh_token=REFRESH_TOKEN