oauth2是什么?
oauth2.0是一个标准的授权协议,它可以为第三方应用,如:web程序、桌面/移动客户端等提供授权,获取用户的数据,它的标准是RFC 6749 文件,该文件解释了oauth的定义:
OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据
其核心可以理解为:服务端向客户端颁发一个令牌,客户端通过令牌来访问服务端的数据。
为什么要使用oauth2.0
举个通俗的例子:古代的皇宫是皇上居住的地方,普通人是不允许进出的,某一天来了一个人对着守卫说:我是皇上七舅老爷的外孙女,快让我进去。
正常情况下守卫肯定不让他进去,谁知道这个人是不是骗子,守卫一般会找人禀明皇上,皇上跑过来一看,果真是他七舅老爷的外孙女,此时守卫才会放他进去
但隔了几天守卫换了,这个人出去了一趟又要进宫,或者他要进皇宫其它的地方,是不是又得走一遍流程,这样搞了几次后,皇上也不耐烦了,每次我总这样跑来跑去,效率也太低了,要是明天八舅老爷的外孙女来了不得累死,好了,我给你们发一张令牌:“皇室亲属进去牌”,并备注:这个人的信息,及可以进出的门。
这样门卫看到这样令牌后,直接根据上面的备注信息确认是否让他进去就可以了
oauth2.0的四种授权方式
- 授权码(Authorization Code)
- 隐藏式(implicit)
- 密码式(password)
- 客户端凭证(client credentials)
授权码方式
先思考一个问题:比如我们要访问B网站,B网站依赖于A网站给的用户数据,该如何做?
授权码的方式,是先在第三方应用申请一个授权码,然后用该码获取令牌。
比如下面链接:
https://b.com/oauth/authorize? response_type=code& client_id=app37482284& redirect_uri=https%3A%2F%2Fa.com%2Fcallback& scope=read&state=xcoiv98y2kd22vusuye3kch
首先用户访问B网站,获取一个授权码,client_id:是让B知道是谁在访问,redirect_uri:B网站接受或拒绝后跳转的网址
scope:标识要求授权的范围,这里是只读
state:随机生成的一个字符串,表示客户端的当前状态,服务端授权后会原封不动的返回这个值,简单的来说:这个参数主要保证请求的授权的客户端和后面使用授权服务的客户端是一个,防止CSRF攻击,可参考:《OAuth2.0忽略state参数引发的CSRF漏洞》(https://blog.csdn.net/gjb724332682/article/details/54428808)
用户点击这个链接后,会跳转到一个授权页面,类型微信的授权页面,如下:
然后用户点击授权,B网站会生成一个授权码,然后将需要的用户信息通过:
授权码 -> 临时令牌 的关系保存下来,然后将授权码返给A网站,跳转之后的地址为:
https://a.com/callback?code=8x4d3N674g2&state=xcoiv98y2kd22vusuye3kch
然后A网站通过授权码在后端调用B的接口就可以获取到令牌了,
注意:这里获取令牌是在后端进行,A调用B服务器接口还需要传入一个密钥。
这样就避免了攻击者拿到授权码后在其它服务器上窃取令牌。
https://b.com/oauth/token? client_id=app37482284& client_secret=X9gK6uB20sD1myc8cRe& grant_type=authorization_code& code=AUTHORIZATION_CODE& redirect_uri=CALLBACK_URL
然后B网站根据授权码拿到令牌(token),返回给A的回调地址,A收到token后,就可以通过token获取到用户信息。
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store Pragma: no-cache { "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3", "token_type":"bearer", "expires_in":3600, "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk", "scope":"create delete" }
回顾上面的调用流程,主要分为两个步骤:
- 用户在客户端确认授权,获取授权码
- 根据授权码获取令牌
为什么要先获取一个授权码,然后再根据授权码获取令牌呢?
因为获取授权码往往是在客户端上发起,如果第一步就获取到了令牌,那么攻击者很容易就窃取到令牌,所以获取令牌往往是放到服务端来做,这样的安全性更高。
隐藏式
隐藏式实质是授权码方式的一种缩略版,即直接是第一步就通过客户端访问获取到令牌,以上也说过这种方式的安全性很低,极可能存在中间人攻击的风险。
第一步,同样是客户端授权
https://b.com/oauth/authorize? response_type=token& client_id=app37482284& redirect_uri=https%3A%2F%2Fa.com%2Fcallback& scope=read&state=xcoiv98y2kd22vusuye3kch
第二步,直接回调到A网站地址,并携带令牌
https://a.com/callback#token=ACCESS_TOKEN
因为这种方式安全很低,已经不推荐使用,之所以出现这种方式,主要是因为最原始的应用不支持浏览器访问外部主机链接,所以采取了这种妥协的方式,后面由于Cross-Origin Resource Sharing (CORS)技术的出现,解决了这个问题,目前对于web服务推荐使用授权码方式,具体可参考:
《Why you should stop using the OAuth implicit grant!》(https://medium.com/oauth-2/why-you-should-stop-using-the-oauth-implicit-grant-2436ced1c926)
《Is the OAuth 2.0 Implicit Flow Dead?》(https://developer.okta.com/blog/2019/05/01/is-the-oauth-implicit-flow-dead)
密码式
密码式是单点登录用到比较多的一种场景。即客户端直接通过用户名和密码请求获取令牌。
先说说单点登录,比如我们登录了taobao.com网站,准备买一些东西,可看了半天没看到合适的,准备到tmall.com看看,淘宝和天猫都是阿里系的,既然都是一家的,完全可以复用taobao网站的登录状态。授权给tmall使用。
所以这就可以用到oauth2的密码方式,用户登录了一次,服务端把生成一个令牌,保存用户的信息,taobao和tmall都可以共享。
POST /oauth/token HTTP/1.1 Host: authorization-server.com Content-type: application/x-www-form-urlencoded https://taobao/token?grant_type=password &username=guestuser1 &password=123654ww &client_id=xxxxxxxxxx
返回令牌信息
{ "access_token": "ece8ec32-5855-48c5-838b-0d7f20c64d1c", "token_type": "bearer", "expires_in": 3600, "scope": "create" }
grant_type:授权方式,password表示:密码式
username:用户名
password:密码
在获取令牌的时候,服务端会将令牌->用户信息保存起来,后面客户端直接通过令牌就可以获取到用户信息。
客户端凭证
凭证式实质也是授权码的一种缩略版,可以理解为授权码方式的第二步,获取令牌都在服务端进行
https://b.com/token? grant_type=client_credentials& client_id=app37482284& client_secret=X9gK6uB20sD1myc8cR
这种方式适用于没有前端的应用,主要是用于第三方应用在后端访问传递数据。