OAuth 2.0 是什么?
OAuth 2.0 是一个授权的开放网络标准
角色
Resource Owner:资源所有者,既用户
User Agent:用户代理
Authorization Server:既提供第三方登录服务的服务器
Client:第三方应用,我们的应用就是一个client
客户端的授权模式类型
- 授权码模式
- 隐式授权模式
- 密码模式
- 客户端模式
授权码模式
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
1.授权请求
比如你想登录gitlab,并且想利用三方授权登录,比如谷歌账号,当你点击谷歌图标的时候,会首先发起一个请求,会带上以下参数,向授权服务器发送授权请求:
response_type:表示授权类型,必选项
client_id:表示客户端的ID,必选项,一般是在授权服务器上申请应用的时候,颁发的
redirect_uri:重定向的 uri
scope:表示申请的权限范围
state:表示客户端的当前状态,可以是任意值,认证服务器会原封不动的返回这个值
下面是一个例子:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
授权服务器验证该请求,确保所有的参数提交且有效,授权服务器会引导用户进入授权页面
2.当用户点击确定授权时,授权服务器会返回授权码(code)和状态参数(state),返回请求到相应的回调地址(redirect_uri)。至此,用户的主动行为已经结束。
3.第三方应用,也就是我们的客户端,在拿到授权码之后会直接向授权服务器请求访问令牌,参数如下:
grant_type:授权模式,必选项,值 "authorization_code"
code:从授权服务器收到的授权码
redirect_uri:回调地址
client_id:标识应用ID
client_secret:授权服务器颁发的密钥
授权服务器验证通过后会返回如下参数:
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"
}
至此整个授权码流程结束。
需要注意的几个问题
重定向 redirect_uri uri 整个授权过程都是一样的
授权过程只是授权:
比如,你想用 "支付宝" 授权登录 "优酷" ,整个授权的过程只是支付宝验证用户授权,返回一个 openId 和 支付宝允许给优酷的一些用户信息,优化拿到 openId可以直接上传服务端注册,至此整个授权流程结束。后面优酷相关联的资源获取和授权没有任何关联了。优酷会对这个 openId和账号做唯一映射,这样下次还用支付宝授权登录,优酷的服务端不会在创建新的账号给该用户,即不需要用户密码的一键登录。为什么整个授权流程不直接返回 access_token 而是要经过中间步骤先要获取授权码 code ,在根据授权码再次获取 access_token ?
最直接的答案是安全,code有效期比较短,而且一个code只能换取一次access_token即失效。获取code的请求是用户主动授权之后获取的从这里截止,用户的主动行为结束。
获取参数不包含 appsecret,请求回来的code回立马再次发起请求,这次请求会带上appsecret,授权服务器验证并返回 access_token
隐式授权模式
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+
和 授权码请求一样,首先你会发起一个授权请求,验证参数
- 授权请求:
response_type:表示授权类型,此处固定值为 token
client_id:客户端标识
redirect_uri:重定向 uri
scope:访问的权限范围
state:表示客户端的当前状态,可以是任意值,认证服务器会原封不动的返回
客户端发起 HTTP 请求,如下:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
2.授权服务器会验证该请求,确保所有的参数已提交且有效。授权服务器验证请求参数中的uri和客户端提交的重定向的uri保持一致。
如果,该请求是有效的,授权服务器验证对资源所有者进行身份验证并展示授权页面给用户。
用户同意授权请求时,授权服务器回返回如下参数:
access_token:授权服务器颁发的访问令牌
token_type:令牌类型
expires_in:过期时间
scope:表示授权权限范围
state:表示客户端的当前状态,可以是任意值,认证服务器会原封不动的返回这个值
这些参数,拼接在重定向 uri 地址的后面
3.第三方应用向资源服务器发起请求,不包含上一步获取的access_token
资源服务器返回一个网页(通常是带有嵌入式脚本的 HTML 文档),其中的脚本可以提取出令牌,浏览器把access_token发送给客户端
至此整个简单授权模式结束。
隐式授权和授权码授权的区别就是,是直接获取access_token,不需要经过code步骤。但是获取的access_token需要资源服务器的脚本从第三方代理(既浏览器)中提取出来)发送给客户端。
密码模式
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
这种模式用户必须把"用户名"和"密码"给到客户端,客户端不得存储密码。这通常用在用户读客户端高度信任的情况下。
- 资源所有者像客户端提供他的用户名和密码
- 当发起请求客户端向授权服务器进行身份认证
- 授权服务器对客户端进行身份验证,验证资源所有者的凭证,如果有效则颁发访问令牌。
访问令牌请求参数:
grant_type:值必须为 "password"
username:资源所有者用户名
password:资源所有者密码
scope:客户端授权请求范围
例如:
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
授权服务器验证授权请求通过,参数如下:
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"
}
客户端模式
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
其实,这种模式不属于授权模式,是客户端的行为,而非用户行为
1.客户端向授权服务器请求验证,并要求一个访问令牌
2.授权服务器向客户端进行身份验证,如果有效,则颁发一个访问令牌
访问令牌请求:
grant_type:表示授权类型,值必须为 "client_credentials"
scope:客户端的授权请求反问
如下,请求:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
授权服务器验证该请求,如果请求有效则返回,令牌:
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, "example_parameter":"example_value"
}
刷新访问令牌
如果,授权服务器颁发了刷新 refresh_token,可以在客户端发起刷新 access_token,一般 refresh_token有效期是大于access_token有效期
请求参数如下:
grant_type:授权类型,此处必须为 "refresh_token"
refresh_token:颁发给客户端的刷新令牌
scope:表示可访问的权限范围
如下 HTTP 请求
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
参考链接
http://www.rfcreader.com/#rfc6749