背景
我们先从一个非常典型例子出发,假如你在某网盘上传了你的很多图片,网盘提供了图片存储,另一个某打印店提供了在线打印图片。
由于存储和答应是由两家不同的服务商提供的,两家各自都提供了用户的注册功能,所以如果你要想在打印店上去打印你放在网盘的照片时,此时你会想到2种方案:
- 假设你的账户密码都不一样,你可以先将待打印的图片从网盘上下载下来,然后在上传到打印店的网站上,之后进行打印。这种模式是最原始,也是效率最低下。
- 为了省事,你可以将你网盘的账户密码交给打印店老板,告诉他你要打印的图片,让他给你操作,省事,但是风险太大,帐号密码都泄漏了,就不怕别人去篡改个人信息或者查看你的隐私?
之前很多公司包括Google
、Yahoo
、Microsoft
都尝试解决这个问题,这也促使OAuth
的诞生。
什么是 OAuth ?
OAuth(Open Authorization
)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容,目前在全世界范围内得到广泛应用。
OAuth 解决思路
OAuth
在 客户端(Client
) 即 打印店 与 资源服务器(Resource Server
) 即 网盘 之间,设置了一个授权层(Authorization Layer
),通过 授权服务器(Authorization Server
) 提供用户授权。
客户端 要想访问 资源服务器 必须要经过 授权层,用户 即 资源拥有者(Resource Owner
) 可以在登录的时候,指定授权层令牌的权限范围和有效期,当用户同意授权后才向客户端开放用户储存的资料。
通俗来说:当打印店要访问用户的你网盘的图片时,通过OAuth
机制,打印店要向网盘的授权服务器请求授权,网盘服务商将引导你在网盘的网站上登录,并询问你是否将访问图片的服务授权给打印店。当你点击同意后,打印店就可以访问你网盘上的图片服务。整个过程打印店没有触及到你网盘的帐号信息,安全便捷。
OAuth 授权流程
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
大体流程如下:
-
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
验证通过,资源服务器将向客户端返回资源信息。
授权模式
根据应用请求授权的方式和授权方服务支持的 Grant Type
的不同,OAuth 2
定义了四种 Grant Type
(授权模式),每一种都有适用的应用场景:
- 授权码模式(
Authorization Code
):最常使用的一种授权许可类型,结合普通服务器端应用使用。 - 隐式授权模式(
Implicit
):跳过授权码这个步骤,适用于移动应用或Web App
- 密码模式(
Resource Owner Password Credentials
):客户端提供帐号密码,向服务商索要授权,适用于受信任客户端应用 - 客户端模式(
Client Credentials
):客户端直接向服务商索取授权,适用于客户端调用主服务API
型应用
授权码模式(Authorization Code
)
是目前互联网上最常使用的一种授权模式,比如QQ
,微博,Facebook
和豆瓣等等,它要求第三方应用先申请一个授权码(Authorization Code
),然后再用该码获取令牌,请求数据,流程如下:
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ +---------------+
| -+----(A)-- User Authorization Request ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates ------------>| Server |
| | | |
| -+----(C)-- Authorization Code Grant ------<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Access Token Request ---------------' |
| Client | |
| | |
| |< ---(E)-- Access Token Grant -----------------------'
+---------+ (w/ Optional Refresh Token)
User Authorization Request
:用户访问客户端,客户端构造了一个用于请求authorization code
的URL
并引导用户跳转访问,链接格式大概如下:
https://open.server-name.com/oauth2/authorize
?response_type=code
&client_id=AppID
&redirect_uri=REDIRECT_URI
&scope=SCOPE
&state=STATE
-
response_type
:授权模式,必选,此时值固定为code
-
client_id
:客户端身份标识,一般是注册时候的分配的AppID
-
redirect_uri
:授权成功后重定向地址,必选,注意需要将uri
进行URLEncode
-
scope
:授权范围,可选 -
state
:客户端的状态值,必选,一般是随机字符串,成功授权后回调时会原样带回,为了防止CSRF
攻击
User authenticates
:用户决定是否给客户端授权,授权服务会提示用户授权或拒绝应用程序访问其帐户信息
Authorization Code Grant
:用户确认授权,授权服务器将重定向之前客户端提供的redirect_uri
地址,并附带code
和state
参数,客户端便能取到authorization code
的值,链接格式大概如下:
https://client-name.com/redirect_url
?code=520DD95263C1CFEA087
&state=STATE
-
code
:授权服务器生成的authorization code
,即授权码,code
有效期较短,一般维持在5 - 10
分钟,授权服务器可自行配置 -
state
:即客户端之前携带的state
值,可进行检查对比与最初设置的状态值相匹配,防止CSRF
攻击
Access Token Request
:客户端获取到授权码 code
后,可向服务器请求(post
)获取 access_token
,一般来说请求参数如下:
参数 | 是否必须 | 含义 |
---|---|---|
grant_type | 必须 | 授权类型,此值固定为authorization_code
|
code | 必须 | 授权码 |
client_id | 必须 | 客户端标识,一般是第三方分配的AppID
|
client_secret | 必须 | 客户端密钥,一般是第三方分配的AppSecret
|
redirect_uri | 必须 | 回调地址,与之前的 redirect_uri 保持一致 |
Access Token Grant
:服务器会验证客户端传过来的参数,验证通过后,给客户端返回 access token
,一般返回数据格式如下:
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"scope":"user_info"
}
-
access_token
:访问令牌 -
token_type
:令牌类型 -
expires_in
:令牌过期时间 -
refresh_token
:刷新令牌 -
scope
:权限范围
其中 refresh_token
用于在授权自动续期步骤中,获取新的Access_Token
时需要提供的参数。
至此,授权流程全部结束。
隐式授权模式(Implicit
)
跟前面的Authorization Code
模式非常相似,只是省略掉了颁发授权码(Authorization Code)
给客户端的过程,而是直接返回访问令牌和可选的刷新令牌。适用于没有Server
服务器来接受处理Authorization Code
的第三方应用,其整个流程如下:
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ +---------------+
| -+----(A)-- User Authorization Request --->| |
| User- | | Authorization |
| Agent -|----(B)-- User Authenticates ----------->| Server |
| | | |
| |<---(C)-- Redirect URI With ------------<| |
| | Access Token +---------------+
| |
| | +---------------+
| |----(D)---Follow Redirect URI --------->| Web-Hosted |
| | | Client |
| | | Resource |
| |<---(E)-- Send Token Extract Script ----<| |
| | +---------------+
+-|--------+
| |
(A) (F) Pass Token to Application
| |
^ v
+---------+
| |
| Client |
| |
User Authorization Request
:客户端提构造了一个用于请求授权的链接,链接格式大概如下:
https://open.server-name.com/oauth2/authorize
?response_type=token
&client_id=AppID
&redirect_uri=REDIRECT_URI
&scope=SCOPE
&state=STATE
与之前的授权码模式相比,只是将 response_type
换成了 token
User Authenticates
:与之前的相同,用户决定是否给客户端授权
Redirect URI With Access Token
:用户同意授权,认证服务器将用户重定向到客户端指定的重定向redirect_uri
,并在uri
的中添加访问令牌,链接格式大概如下:
http://client.name.com/redirect_url/#access_token=2YotnFZFEjr1zCsicMWpAA&token_type=Bearer&expires_in=3600&scope=SCOPE&state=STATE
此时的 token_type
恒为 Bearer
。
要注意的是,返回值放到了REDIRECT_URI
的 hash
部分,而不是作为 ?query
参数,这样浏览器在访问重定向指定的url
时,就不会把这些数据发送到服务器。
Follow Redirect URI
:浏览器请求redirect_uri
标识的客户端地址,此时不包含hash
值,并保留access_token
相关信息。
Send Token Extract Script
:客户端返回一个包含 token
的页面,页面执行脚本获取 redirect_uri
中的 access_token
。
Pass Token to Application
:浏览器获取的令牌发给客户端。
至此,授权流程全部结束。
密码模式(Resource Owner Password Credentials
)
这种模式更加简化,客户端直接使用用户提供的username
和password
来直接请求获取access_token
信息,这种模式一般适用于用户高度信任第三方客户端的情况,其整个流程如下:
+----------+
| Resource |
| Owner |
| |
+----------+
v
|
(A) Resource Owner Password Credentials From User Input
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ----------------->| |
| | Password Credentials To Server | Authorization |
| Client | | Server |
| |<--(C)- Access Token Passed To Application-<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
Resource Owner Password Credentials From User Input
:用户向客户端提供用户名与密码作为授权凭据。
Resource Owner Password Credentials From Client To Server
:客户端向授权服务器发送用户输入的授权凭据以请求 access token
(要求客户端必须已经在服务器端进行注册),其请求参数主要如下:
参数 | 是否必须 | 含义 |
---|---|---|
grant_type | 必须 | 授权类型,此值固定为password
|
username | 必须 | 用户登陆名 |
passward | 必须 | 用户登陆密码 |
scope | 非必须 | 授权范围 |
Access Token Passed To Application
:授权服务器对客户端进行认证并检验用户凭据的合法性,如果检验通过,将向客户端返回 access token
。
至此,授权流程结束。
客户端模式(Client Credentials
)
这是最简单的一种授权模式,客户端直接已自己的名义,而不是用户的名义直接去授权服务器发起授权,获取 access_token
,流程也是非常简单:
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
Client Authentication
:客户端直接发起授权请求,此时的 grant_type
的值为 client_credentials
Access Token
:认证服务器确认身份后,向客户端发放 access_token
小试牛刀
鉴于授权码模式(Authorization Code
) 是目前来说使用最为广泛,流程也时最为最完整、流程最严密的一种授权模式,我以它为例采用的 koa2
,自定义实现了一个简单的 Authorization Code
授权方案。
主要思路还是通过 client
引导用户点击进入授权页,在授权页面通过确认授权后向后端请求 code
, 重定向到 redirect_uri
, 并获取服务端的 code
,之后通过 code
再去请求服务端获取令牌 access_token
,之后通过 access_token
获取用户信息,整个效果如下图所示:
这里我就不放代码了,容易占地方!!详情请戳这里
pass
:代码里面的数据都是mock
的,还是有较多异常case
未做处理,旨在了解整个授权的过程和实现思路,仅供参考
结语
OAuth
的授权方案目前已经非常成熟,在整个互联网行业中也是非常常见的,早些年做微信生态的公众号的时候初次接触,到现在的深入了解,也算是一些进步吧。
希望阅读完本文也能让你对 OAuth
授权有一个深刻全面的认识,或者是加深巩固 OAuth
授权方面的知识。
理论结合实践,才是我们做为coder
应有的追求,加油!