OAuth2.0的授权码方式

TL;DR:OAuth在很多地方都能遇到,例如单点登录、微信小程序申请使用用户头像、google Map申请访问google Drive等。其核心是要给第三方应用发放一个代表用户权限的token,以便于第三方应用可以直接使用该token访问用户资源。OAuth的主要目的就是如何安全地发放这个token。

背景

下面将以一个应用开发者的角度说明OAuth2.0的过程,OAuth2.0实际上有多种授权方式,这里介绍最常用也是最复杂同时也是最安全的授权码方式。

假如我要开发一款浏览相册的web应用,这个应用需要用到用户网盘中的相册数据。这里就出现了四个角色:

用户 —> 浏览器 —> 相册应用后台 —> 网盘提供商

浏览器负责展示,相册应用后台负责计算,网盘提供商负责提供数据

过程

申请Client_ID

首先我需要向网盘提供商申请一个 client_id 和 client_secret。这就好比,如果我作为第三方应用需要用到网盘提供商中用户的数据,那么我就应该提前在网盘提供商那里备案,否则,随便一个第三方应用都可以冒充我的身份去网盘提供商那里获取数据然后捣乱。

获取授权码

用户通过浏览器进入到我的系统后,此时因为我还没能拿到用户的网盘数据,我需要先让用户给我网盘数据的使用权限。具体方式为:跳转到网盘提供商专门提供的授权页面:

https://wangpan-authorization-server.com/authorize?
  response_type=code
  &client_id=8Ybgz6u_gCuEyuW2OokUuDh5
  &redirect_uri=https://www.xiangce.com/authorization-code.html
  &scope=photo+offline_access
  &state=F0Pte-EQQ97NTw2o

其中 wangpan-authorization-server.com 为网盘提供商的授权服务器地址,进入该页面会先需要登录网盘账户,然后会弹出提示是否给相册应用权限

如果用户点击允许,则授权服务器会将页面重定向到 redirect_url 页面,同时附上授权码参数 code

https://www.xiangce.com/authorization-code.html?
state=F0Pte-EQQ97NTw2o
&code=EOC2fKbZ6d_fIkdNx9pXWs3-HB7ohhg_ppjNAE1oAZl4f4GK

此时,浏览器就会将该code参数发送给相册应用后台。

注:state 是浏览器随机生成的一个字符串,用于防止CSRF攻击。不是本文重点,需要时再百度。

获取token

相册应用后台得到授权码 code 后就会发送一条post请求到网盘提供商的授权服务器,用于申请token:

POST https://wangpan-authorization-server.com/authorize

grant_type=authorization_code
&client_id=8Ybgz6u_gCuEyuW2OokUuDh5
&client_secret=HIRDkHcvdK8FkucGDviXjFiIxdRiZiJ_proRt44IbtzfAx_c
&redirect_uri=https://www.xiangce.com/authorization-code.html
&code=EOC2fKbZ6d_fIkdNx9pXWs3-HB7ohhg_ppjNAE1oAZl4f4GK

网盘提供商的授权服务器收到这个请求后,校验完参数就会将 access token 返回给网盘应用后台

{
  "token_type": "Bearer",
  "expires_in": 86400,
  "access_token": "Ovkoc4dSjhc0IcXTG60ApSSQv8F6PRa5fBp-Cu4GjmCNDEuXU8XmXEk7sYHptNnbBAkjjpUw",
  "scope": "photo offline_access",
  "refresh_token": "vEmuz3SrhaYw8pyfGXU7gvEY"
}

应用token

有了 access token 后,相册应用就可以将其加到请求头中,访问用户在网盘提供商中的数据了,例如,我想要获得某个网盘中的照片就可以用这个请求:

curl -H "Authorization: Bearer Ovkoc4dSjhc0IcXTG60ApSSQv8F6PRa5fBp-Cu4GjmCNDEuXU8XmXEk7sYHptNnbBAkjjpUw" \
"https://wangpan.com/the_user/photos"

刷新token

一般来说,access token 都会有时间限制,超时后就需要重新申请。当然可以重复上述过程申请新的 access token,但OAuth2提供了更简单的方法。

在上文中“获取token”部分,网盘授权服务器返回 access token 的同时,也返回了一个 refresh_token,它就是用来刷新 access token 的。例如,我可以直接在相册应用后台发送这个请求来更新token:

GET https://wangpan-authorization-server.com/authorize
grant_type=refresh_token&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
refresh_token=REFRESH_TOKEN

JWT

通过OAuth可以给应用发放一个token,这个token当然可以是授权服务器随机生成的字符串,但是,如果它能携带一些信息是不是更好呢?例如,可以在token中编码当前用户的信息,这样,相册应用如果想获取用户信息时,只需要对该token解码就行,而不用单独去发送一个询问请求(请求中需要携带该token)

JWT就是一种token的编码格式,它的一般形式为:

# Header.Payload.Signature
xxxxx.yyyyy.zzzzz
  1. header:令牌头部,记录了整个令牌的类型和签名算法
  2. payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里
  3. signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名,该签名的作用是:保证令牌不被伪造和篡改

OAuth vs JWT

两者并没有直接关系,

OAuth只是一套发放token的流程,发放的token可以是任意形式。例如,除JWT格式外,使用随机字符串、时间戳等能保证唯一的串都行

JWT只是一种token的形式,可以使用任意流程发放。例如,除OAuth外,还有OpenID等也能发放token

疑惑

为什么不直接返回token而要先返回一个授权码呢?

因为这个授权码是要直接放到url中作为参数的,如果直接将token放到url中是非常不安全的,例如,从“应用token”部分可以看到,一旦我得到token,我就可以直接使用该token访问用户数据了,并不需要额外的信息或者额外的验证步骤

那为什么要将授权码放到redict_url中,再由浏览器将其传给相册应用后台呢?让授权服务器将token直接发给相册应用后台不可以吗?

这种做法好像是可行,但细想一下就能发现问题:

用户点击授权按钮后,授权服务器在后台直接将token发给相册应用后台。那用户浏览器页面怎么办?虽然此时相册应用后台已经拿到token了,但浏览器此时还是授权服务器的页面。

那我能不能让授权服务器后台将token发送给相册应用后台,同时设置一个redict_url,但此时这个redict_url不携带任何参数,仅仅作为授权操作后的跳转页面?

仍然是不行的,一个很大的问题就是,如果相册应用需要获取token,则它需要将client_id和client_secret发给网盘授权服务器,特别是client_secret是必须存在后台的,而上文中,获取授权码的方式是url携带参数的方式(因为是用GET方式请求的,为什么是GET?因为是从页面上点击超链接跳转的,例如单点登录时下面提供的几个其他网站的登录选项按钮,点击就会跳到其他网站的授权页面),这就限制了这个获取token的请求必须在后台发送,而不能在浏览器端发送。也就是说,用户授权完成后一定不能也不会返回最终的token,只能是一个中间值。

redirect_url可以随意指定吗?

不行,它需要和client_id相互绑定,但可以有多个。在申请client_id时就会让你填redirect_url域名相关的东西

redirect_url的限制

参考

https://www.ruanyifeng.com/blog/2019/04/oauth_design.html

https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

https://www.oauth.com/playground/index.html

Leave a Comment