OAuth,JWT ,OIDC你们搞得我好乱啊
hi,这里是桑小榆,这次分享的不是生活文,而是技术文。
基于OAuth2.0协议的授权认证,初次接触授权认证知识的时候,出现了不少热门名词,“OAuth”,“JWT”,“OIDC”,这简直让人头大,晕头转向甚至学了又不知道自己学了啥,网络上看见相关的文章也是一头雾水,要么是部分知识,要么是讲着讲着莫名其妙蹦出一个陌生词。
▲图/ 来源《功夫》
所以这次,本着大道至简的思想,和大家一起探讨,如果有看不懂的地方,那就是我没理解或者讲明白。本篇不包含源码,先以理论基础理解作为铺垫,后篇将会以源码实操的方式进行帮助理解。
首先我们回顾传统的授权方式,基本是通过账号,密码的方式进行授权认证,授予第三方也是通过共享密码的方式进行授权。随着信息化的普及,系统越来越庞大,人人对于网络触手可及,这就容易带来安全隐患。
第一,用户授权第三方通过共享密码,此处密码也是明文的,显然很不安全。
第二,我们给第三方的权限其实也就一小部分,如果通过密码共享,则第三方通过你的密码可以获取所有的权限,这是不安全的。
第三,我们授权了第三方应用之后,我们是无法撤回权限的。只能通过更改密码,那么我们授权的第三方都会受影响,非常麻烦且需要重新授权。
基于以上传统授权方式的缺陷,于是引入了OAuth协议。协议,顾名思义就是大家(国际互联组织)共同达成的一种标准的规则。这种协议,例如大家耳熟能详的IP协议,TCP协议等都是共同制定的一种标准协议,防止各个国家或企业拥有自己的协议就容易造成协议泛滥,使用受限且对接繁杂。
OAuth协议,也属于网络层的协议,为了保护资源安全而存在。既然是协议,那必然需要一种传输介质,使得挨家挨户都认识,且需要携带能够识别身份的信息,那就是JWT(Json Web Token),也就是我们常说的Token令牌,关于JWT内容将在下篇探讨。
那么,OAuth协议包含了哪些内容呢?
首先是四个主要的参与角色。
Resource Owner:资源所有者,对资源具有授权能力的人,也就是用户。
Resource Server: 资源服务器,保存用户信息的服务器并且能够验证令牌是否合法(比如说微信服务器,保存你的微信头像,昵称等)。
Client:客户端,第三方应用,它获得用户的授权后便可以去访问用户的信息。比如哔哩哔哩。
Authorization Server:授权服务器,它将认证用户的身份,为用户提供授权审批流程,并最终颁发授权令牌(Access Token)。发送令牌给第三方的服务器(可以和资源服务器是同一个)
通过OAuth协议的几个参与者,我们可以总结OAuth的作用,是让用户的权限,安全可控的授予第三方应用,第三方应用获取到用户授予的权限之后,与资源服务器进行交互。
其次,OAuth 支持四种授权模式。
由于客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。因此OAuth支持获得令牌的方式有四种。
授权码模式(authorization code)
授权码模式是比较标准的模式,微信、GitHub、哔哩哔哩等知名应用就是使用的这种模式,因为通过授权码的方式可以有效的隐藏令牌不被泄露,安全性更高。
具体授权码如何操作,我们可以举个实际的例子:
▲图/ 来源B站登录界面
比如,我们想登录哔哩哔哩网站,哔哩哔哩提供了第三方登录方式,微信,微博或者QQ登录。
1.当我们选择微信登录的时候,哔哩哔哩会提供一个跳转链接,我们点击的时会跳转到微信授权服务器。
//接口服务
https://open.weixin.qq.com/connect/qrconnect
?appid=wxafc256bf83583323
&redirect_uri=https%3A%2F%2Fpassport.bilibili.com%2Flogin%2Fsnsback%3Fsns%3Dwechat%26state%3D8b90df300a6711edbeb2d280ef8fddbc%26source%3Dnew_main_mini
&response_type=code&scope=snsapi_login
&state=authorize#wechat_redirect
参数解释:
appid:是微信端开放的授权标识,相当于client_id。
refirect_uri:回跳地址,微信生成授权码code之后就会在这个链接上回传给哔哩哔哩。
response_type:授权类型,此处为授权码类型。
scope:授权范围,此处为是授权给哔哩哔哩的授权api。
state:校验参数,校验用户信息是否被篡改。
2.我们扫码之后,微信服务器会询问我们是否授权哔哩哔哩(将获取你的昵称、头像)。
3.当我们点击允许的时候,微信授权服务器就会针对当前操作的用户返回一个授权码给代理用户,代理用户就是指的浏览器,因为是浏览器帮我们去询问授权的。
4.确认授权之后,代理用户(浏览器)就会把这个授权码通过重定向地址传回给哔哩哔哩。
5.哔哩哔哩拿着这个授权码和其他重要认证信息向微信授权服务器请求令牌。
6.微信授权服务器接收并识别这个授权码之后发送一个令牌给哔哩哔哩。
7.哔哩哔哩拿着这个令牌就去获取微信资源服务器读取用户资料。
▲图/ 授权码模式图解
隐式模式(不推荐使用),适合没有后台的第三方。
他的使用方式,我们还通过哔哩哔哩登录的例子说明。
//接口服务
https://open.weixin.qq.com/connect/qrconnect
?appid=wxafc256bf83583323
&redirect_uri=https://www.blibli.com/callback
&response_type=token&scope=snsapi_login
&state=authorize#wechat_redirect
1.登录哔哩哔哩时,会提供一个微信登录的链接,我们点击之后会跳转到微信授权服务器。
2.进入之后,微信服务器会询问我们是否授权哔哩哔哩,并确认授权给哔哩哔哩。
3.此时,微信授权服务器直接把令牌发送给了哔哩哔哩,哔哩哔哩根据令牌获取用户的信息。
我们发现,这个模式是没有哔哩哔哩后台认证与微信授权服务交互的过程,仅仅是拿到令牌之后返回到了哔哩哔哩的前台。这种方式我们很容易通过链接进行存储和伪造授权,是不安全的。
密码模式,适合传统的账户密码系统改造为OAuth授权,以及用户在很信任第三方的情况下使用。
//接口服务
https://open.weixin.qq.com/connect/qrconnect
?appid=wxafc256bf83583323
&grant_type=password
&username=sunny_100kmiles&rpassword=imluckyboy
&scope=snsapi_login
&state=authorize //wechat_redirect
1.该模式登录哔哩哔哩时,哔哩哔哩会使用我们的账号密码直接向微信授权服务器索要令牌。
2.微信授权服务器接收到账号密码之后匹配成功则会发送令牌给哔哩哔哩。
3.哔哩哔哩根据令牌获取我们的信息,但如果更改了密码之后也需要重新刷新令牌。
客户端模式,适合没有前端的第三方,也没有用户的参与。仅仅是授权服务器与资源服务器之间的交互。
//接口服务
https://open.weixin.qq.com/connect/qrconnect
?appid=wxafc256bf83583323
&grant_type=client_credentials
&client_secret=xxxxxxxx
综合以上四个授权模式,有些敏锐的小伙伴会发现,第一种整体来看较为复杂,为何要使用授权码code去微信授权服务器获取token(令牌)呢?直接在第四步,将token直接返回给客户端哔哩哔哩,不是更加方便吗?还减少了一次客户端与授权服务器的交互,性能上也更优?
这种授权码的设计方式也不难理解,如果直接将token(令牌)通过redirect_uri回调的方式返回给客户端哔哩哔哩的话,也就是经过了代理用户(浏览器)这一层,此时浏览器传送的过程中很容易存到浏览器的cacher和log记录中,也容易传到其他恶意站点或者被截获,这给攻击者盗取令牌带来了更多机会。
并且浏览器的redirect_uri本身就是一个不安全的信息通道,通常我们不会把重要,敏感的数据以这种方式传递。
因此,引入授权码进行授权,OAuth协议经用户授权之后生成一个code给哔哩哔哩客户端,哔哩哔哩后台将会根据这个code和其他重要信息(例如微信的appid,appsecrect等)向微信授权服务获取令牌。这个时候我们会发现就算授权码code被攻击者截获了也起不到任何的作用,大大提高了安全性。
那么,这种模式设计就无懈可击了?显然拿到令牌之后,向资源服务器请求交互的过程中,还是会被截获token,甚至被篡改。
在OAuth1.0协议中,是通过反复的对授权码code和token进行签名,来保证token不会被篡改,但是OAuth2.0却没有这项,因为OAuth2.0是基于https的,我们知道https协议传送的报文是加密的,不容易被篡改。显然OAuth2.0在这基础之上,性能上更优于OAuth1.0的。
还有一种存在的问题就是。用户授权之后,授权码code会通过redirect_uri进行传回给哔哩哔哩客户端,如果没有对这串redirect_uri进行校验,或者校验规则级别不高。例如,哔哩哔哩提供的回调地址是www.blibli.com,但是被人截获篡改成了www.clicli.com\www.blibli.com,那么这样的授权就被www.clicli给劫走了。这就是跨站请求伪造。
由于这个授权服务器,哔哩哔哩客户端和用户之间有几次交互,在得到授权码的时候需要一次回跳,但是这次回跳是可以被阻塞的。
那么就会出现这样的案例,比如我是黑客,我使用自己的账号登录哔哩哔哩进行第三方微信登录,当我授权之后,微信授权服务会返回带有授权码code的回跳链接给哔哩哔哩客户端,返回的过程中被我阻塞了,哔哩哔哩客户端就收不到授权码。
此时,我将这个跳转链接发给正处于登录状态的用户(小张)。
诱导小张进行正常点击,那么我的账号返回的授权码链接就被小张点击之后向微信服务器获取令牌,那么此时我的第三方账号和小张的账号就已经绑定了,此时我相当于以小张的账号进行登录哔哩哔哩,进行删除资源,删除好友,取关等一系列恶意操作。
艺术来源于生活,这一点儿也不假。在网络上看到过这样的一起案例。
A女士前往ATM取钱,在插卡输入密码之后,后面排队的B先生扔了几张真钞到A女士的脚旁诱导她去捡真钞,B先生遂即切换A女士的卡将自己的卡插入ATM机,之后A女士发现需要重新登录,输入几次密码之后依然输入错误,慌忙之中B先生建议A女士前往旁边的前台咨询,于是便去了。在A女士连续输入几次密码之后,早已被B先生记住了,支开A女士之后使用A女士的银行卡进行操作。这一通引开注意,诱导操作是不是和刚刚的案例具有异曲同工之妙呢。
那么在程序里,基于OAuth2.0中是如何解决的呢?
回到上面授权码模式给出的链接,你会发现参数中多了一个state参数,通过携带的state参数,哔哩哔哩就可以通过state进行校验账户的信息是否被篡改。此时的state就相当于当前账号的sessionid,或cookie的签名串。
好了,以上我们探讨了OAuth的设计原理以及作用。并且抛出了JWT(令牌)的作用和概念,下篇中会详细讲解JWT是如何带着将令穿梭在端与端之间。
参考资料:
https://www.rfc-editor.org/rfc/rfc6749.html
https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html