现在网络的资料到处都是,很容易搜索到自己想要的答案。但答案通常只能解决自己一部分的问题。如果自己想要有一套自己的解决方案,还得重新撸一遍靠谱。

我需要学下OAuth2.0吗?

没看之前以为OAuth2.0是登录认证授权的东西,自己的项目里应该是需要的。实际上OAuth是为了第三方应用访问我们资源用的,大多数开发者基本不会用到这个东西。对于自己应用的认证授权,还是基于拦截器的token,SpringSecurity即可。只有做平台级别,像微信,微博,github这种级别才会用到。而真到那个时候,再看也行,也通常不会是你来做。简而言之,非必须。

接下来,我将从几个方面了解和学习使用OAuth2.0。对不对就不管了,反正我也几乎不会用到。ps.有个项目用到了,所以才会有本文。

  1. OAuth2.0介绍和功能
  2. 微信开放平台和github的OAuth2.0接入应用
  3. 自己写一个OAuth2.0服务
  4. Springboot OAuth2.0集成

快速了解OAuth2.0

资源很多,看起来比较麻烦,可以直接看Authorization Code授权码流程,以微信登录为例子的介绍。

OAuth2.0是什么

官方介绍是: OAuth 2.0授权框架允许第三方应用程序通过协调资源所有者和HTTP服务之间的审批交互,或允许第三方应用程序自己获得访问权限,从而获得对HTTP服务的有限访问。也就是授权别人(client)访问我们的资源

The OAuth 2.0 authorization framework enables a third-party
application to obtain limited access to an HTTP service, either on
behalf of a resource owner by orchestrating an approval interaction
between the resource owner and the HTTP service, or by allowing the
third-party application to obtain access on its own behalf.  This
specification replaces and obsoletes the OAuth 1.0 protocol described
in RFC 5849.

别人(client)是什么?

登录石墨文档可以输入账号密码登录,也可以选择微信登录,微信扫码确认后,就登录了石墨文档。石墨文档通过微信认证的方式实现了自身的认证登录。这个石墨文档就是client的角色(Role)。

什么时候需要让别人(client)访问我们的资源?

最常见的是微信授权登录,对client来说,是用户授权client拿到用户在微信上的信息,比如性别,唯一id。

我们怎么才能做到授权给别人访问我们的资源

我们自己怎么获取自己的资源?我们登录后就可以获取到自己的账号信息等资源了。那么怎么给到别人?直接把我们的账号密码给第三者太不安全了。一个是自己的账号密码存在泄露的风险,一个是自己的账号权限太大,万一被别人删除了或者看到了不该看到的东西怎么办。在使用阿里云的时候,我们可以给自己的账号开子账号,给子账号一些权限访问哪些服务(授权太复杂了),这样我们就可以达到最初的目的了: 让别人安全的访问我们的资源。

OAuth通过引入授权层并将client的角色与资源所有者的角色分离, 并且给client单独的凭证(access_token),client通过access_token获取有限的资源。

OAuth2.0定义的角色

resource owner
资源的拥有者,通常就是用户,比如登录的用户。

resource server
资源服务,提供资源的服务。需要access_token才允许被调用。比如微信api,通过access_token调用它可以获取到用户的性别等信息。

client
客户端,第三方客户端,被授权访问的应用。比如石墨文档通过微信登录的时候,石墨文档就是client角色,它要用户授权获取用户微信的信息。

authorization server
资源认证授权服务。用户登录到本服务后,可以选择授权给第三方。比如微信登录,微信认证服务就是authorization server的角色。可以颁发给client code,可以通过code换取access token. 可以通过access_token认证用户。

乍一看可能有点迷乱。站在登录用户的角度,简单的分为3个阵营: 用户本身(resource owner), 用户要登录的应用(client), 用户的资源(resource server and authrization server). authorization server和resource server是一体的,一家的,比如都是微信的。这样清晰一些。只是对于微信内部,个人信息,朋友圈,公众号,小程序啥的,资源挺多,相当于多个resource server, 内部就分成了authorization server和多个resource server.

搞清楚这里面的角色后,再来看协议的流程。

OAuth2.0的协议流程

整体抽象协议流程如下。

  • A: client找用户(resource owner)请求授权,说你得让我获取你的微信昵称. 然后用户就看到微信授权页面(authorization server)显示是否允许client获取昵称。
  • B: 用户点确认,微信就会给client一个临时授权code。
  • C: client拿着上一步得到的授权code去找authorization server换一个access_token.
  • D: authorization server认证了client的参数,确认后,给client返回access_token.
  • E: client拿access_token去获取信息,比如获取用户昵称,头像。
  • F: client如愿以偿。

直接看上面的图,看到B和C都特么叫Authorization Grant, 授权发放。一会儿用户给client发放,一会儿client又找authorization发放,这是要哪样。我当年就是看了无数次这个图没看懂。接着,一般的文章又会介绍OAuth2.0的4种授权方式,结合上图的授权,就会非常容易混淆,看不懂了。

官网描述授权发放:

An authorization grant is a credential representing the resource
owner’s authorization (to access its protected resources) used by the
client to obtain an access token.  This specification defines four
grant types – authorization code, implicit, resource owner password
credentials, and client credentials – as well as an extensibility
mechanism for defining additional types.

授权发放模式是指client获取access_token的方式。官方给了4种,

  • authorization code: 授权码
  • implicit: 隐藏式
  • resource owner password: 用户的账号密码方式
  • client: client的id认证方式
  • 当然,也可以自由扩展,能认证返回access_token就行。

grant是指授权给client,4中授权模式对应的是上图的BCD,即获取access_token的过程有4种方式。

access token存储方式?

access token就是访问资源的凭证,令牌。它的安全很重要。为了防止泄露被窃取,通常是client后端服务用token获取资源,是存储在client后台服务的,比如redis,mysql中,和用户关联存储。在浏览器上是体现不出的。那中间人就不能网络拦截到token。即access_token不应该在浏览器访问网络中出现

以下用石墨文档(client)采用微信(authorization server & resource server)登录的方案来描述理解4种授权方式。

Authorization Code授权码

authorization code就不翻译了,代码里也是这么写的,翻译了就可能对不上了。这种方式获取token,首先用户让微信给一个code给client。client拿着code找微信换一个access_token,以后就用access_token获取用户的唯一id。

这个图对象比较多。先认识对象,微信用户(resource owner),浏览器,石墨文档(client),微信开放平台(authorization server),微信api(resource server)。

首先,石墨文档(client)需要在微信开放平台(authorization)注册,获取appId和appSecret. 这组凭证是石墨自己访问微信开放平台的。access_token则是代表用户本人,注意区别。

1.微信用户通过浏览器访问了石墨文档,然后登陆页,然后点了微信登录按钮
这时候,浏览器请求石墨微信登录回调地址,石墨后台返回302,response附属location. 浏览器重定向跳转到微信二维码认证页面。

1
https://open.weixin.qq.com/connect/qrconnect?appid=wx5a67899f4af8b0b1&redirect_uri=https%3A%2F%2Fshimo.im%2Flizard-api%2Fauth%2Fwechat%2Flogin_callback&response_type=code&scope=snsapi_login&state=740a608f-23b4-4abd-b941-e13d2b5c0dbe#wechat_redirect

注意几个参数:

  1. appid: 石墨文档作为client在微信开发平台的凭证id
  2. redirect_uri: 认证通过后,微信开放平台添加一个code参数到这个url后面,然后浏览器重定向到这个url。这个url是石墨文档后台接收code的接口。
  3. response_type: code。固定
  4. scope: 授权范围,想获取用户哪些资料的api。网页登录为snsapi_login
  5. state: 这个是防刷的,防止csrf跨站请求伪造攻击。微信认证成功后,回调的时候会原样返回给石墨(client)。如果没这个,client接收参数就只有code,别人就是随意伪造碰撞code。而石墨生成了一次性token作为state,回调接口只有一次有效期。这里石墨用的uuid.

2.用户微信扫描二维码,通过认证
用户微信点击了确认,就代表了授权通过了,允许石墨获取access_token. 浏览器微信二维码页面收到code,拼接redirect的url,浏览器重定向到石墨后台

1
https://shimo.im/lizard-api/auth/wechat/login_callback?code=081r8QFa1aYaRA03J7Ha1gUXl42r8QFk&state=740a608f-23b4-4abd-b941-e13d2b5c0dbe

这个回调地址code就是微信开放平台(authorization server)颁发给石墨文档(client)的code。

石墨文档后台接收到code和state后,校验state是否有效,然后拿code和appId,appSecret去访问微信接口获取用户信息。这一步是在石墨文档后台进行的。浏览器看不到access_token. 而对于上一步生成的code,即便有人拿到code,应该已经失效了。code和state都是一次性的有效期。这样保证了access_token的安全性。

1
2
3
4
5
6
7
8
9
10
11
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

返回:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

3.石墨文档获取到用户唯一id后,根据后台用户账号的绑定关系,确认当前登录用户登录


Implicit

Implicit翻译是隐藏式,这种针对纯web前端应用,没有后台,就没办法像上面一样了,令牌只能放在前端。这种也叫client side,又称为User Agent Flow,先说具体用法。

client直接请求authorization server获取access_token. 请求参数是client id和redirect url, 最后认证返回后拼接#access_code. 类似下图。

由于access_token网络传输,并不安全,很少使用这种方案。qq互联提供了一种,见 https://wiki.connect.qq.com/%e4%bd%bf%e7%94%a8implicit_grant%e6%96%b9%e5%bc%8f%e8%8e%b7%e5%8f%96access_token

1.请求认证的url参数: clientId, redirectUrI, Scope,state

  • client_id: 注册应用的 id,比如石墨文档在qq注册应用的appid
  • redirect_uri: 认证成功后要回调的地址,这个要和注册的时候一致
  • response_type: token
  • state: 同样的一次性随机数,会原样返回给client,防止csrf攻击。

2.认证成功后,回调地址中的access_token
qq认证成功后的示例:

1
http://graph.qq.com/demo/index.jsp?#access_token=FE04************************CCE2&expires_in=7776000&state=test

可以看到参数access_token是通过#传递的。这里涉及一个叫中间人攻击的安全问题,见http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

注意,令牌access_token的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在”中间人攻击”的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险

qq提示
可通过js方法:window.location.hash来获取URL中#后的参数值。

password

password需要用户把微信账号和密码给石墨文档,石墨拿着去微信换token,这种放弃看了,不可能给的,微信也不支持。

client

这种就是纯后台方案。client拿着client_id和secret去换access_token。通常就是我们后台服务调用的时候用到。比如使用aws的s3或者阿里云的oss上传文件。我们需要通过ak,sk认证,获取一个token,拿token去上传文件。这个流程写起来还挺麻烦的,一般都会封装好客户端给我们用。这里就不涉及用户了,只是客户端自己的认证了。

customize
自定义,我们也可以username + password登录走OAuth2.0校验。只是没client,或者说每个登录用户类似client。登录后返回access_token, 用户可以访问其他资源。

更新令牌

前面讲的4个授权方式,都是为了获取access_token。返回格式类似微信的:

1
2
3
4
5
6
7
8
{ 
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

openid,unionid并不是必须的,这是微信认证用户的唯一的id。在微信的设计中,

  • access_token: 接口调用凭证,用来获取资源信息,比如用户的头像,昵称等。超时时间很短。微信设定为2h。
  • expire_in: access_token的超时时间,单位秒
  • refresh_token: 用户刷新access_token用的token。超时时间比较久。微信设定超时为30天,失效后需要用户重新授权。
1
2
3
4
5
6
7
8
9
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
返回
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}

请求刷新token:

  • appid: client id,石墨注册在微信平台的id
  • grant_type: refresh_token固定
  • refresh_token: 上一步获取到的token

参考