1 缘起与目的
最近在做网关,一直在接触认证与授权相关问题。突然想到之前OAuth2.0相关知识,正好看到掘金在使用GitHub登录时使用了非常经典的OAuth2.0实现,所以不如就使用这个例子来详解一下这个协议。
本文并不详细介绍OAuth2.0的基础知识,阅读之前需要您对此有一定的了解。如果您了解不多也没关系,建议先看一下这两篇写OAuth2.0的文章,写的非常好,几乎是博客中介绍的最详细且易懂的了,笔者力荐。
OAuth 2.0 的一个简单解释 - 阮一峰的网络日志 (ruanyifeng.com)
OAuth 2.0 的四种方式 - 阮一峰的网络日志 (ruanyifeng.com)
2 掘金登录Github步骤图
3 分步骤详细解读
3.1 用户选择GitHub账号登录
3.2 携带参数跳转GitHub
这里掘金会开启浏览器小窗,跳转GitHub页面,让你去认证+授权。如下图所示。
让我们把url拿出来看下。
再处理一下,如下所示。
https://github.com/login/oauth/authorize?
?client_id=手动马赛克
&redirect_uri=https://juejin.cn/passport/auth/login_success
&state=手动马赛克
&allow_signup=true
&scope=user:email
如上url可以看到client_id、redirect_uri、state、scope等参数清晰可见。
client_id参数让GitHub知道是掘金在请求,
redirect_uri参数是GitHub授权结束后跳转网址,
scope参数表示要求的授权范围,
state是为了防止csrf攻击的随机参数,详见文章最后。
3.3-3.4 GitHub认证与授权
这部分就不详细展开了,只放一下GitHub在点击授权那一刻的授权url。
https://github.com/login/oauth/authorize
可以看到除了本身OAuth2.0的部分参数外还有一些GitHub自己的授权参数,如authenticity_token等。
3.5 GitHub完成授权,跳转到开始掘金传入的入参redirect_uri
https://juejin.cn/passport/auth/login_success?code=手动马赛克&state=手动马赛克
这里可以看到GitHub已经返回了用于掘金获取GitHub信息的授权码code,另外还有用于防范csrf攻击的state。
注意 :以下过程均是发生在掘金redirect_uri向服务器请求后发生的,由于此过程大部分是掘金后端服务请求GitHub服务,无法观测到,所以只是根据OAuth2.0猜测的。
3.6 掘金前端拿到授权码传给后端。
3.7 掘金后端带着授权码等信息去GitHub请求token。
3.8 掘金后端拿到token。
3.9 掘金后端拿着token去GitHub请求用户数据
3.10 GitHub校验后返回用户数据。
3.6 - 3.7 掘金前端拿到授权码传给后端,后端带着授权码等信息去GitHub请求token
https://github.com.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
上面 URL 中,client_id参数和client_secret参数用来让 GitHub 确认 掘金 的身份(client_secret参数是保密的,因此只能在后端发请求),grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码,code参数是上一步拿到的授权码,redirect_uri参数是为了校验确保它与第一步提供的redirect_uri 一致,这是为了确保授权码确实是由预期的客户端接收到的。
3.8 GitHub返回token
返回值大概如下
{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"user:email",
"uid":100101,
"info":{...}
}
access_token为请求令牌,用于后续掘金向GitHub请求用户数据。
expires_in为请求令牌过期时间。
refresh_token为刷新令牌。令牌的有效期到了,如果让用户重新走一遍上面的流程,再申请一个新的令牌,很可能体验不好,而且也没有必要。OAuth 2.0 允许第三方自动更新令牌。具体方法是,GitHub网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh_token 字段)。令牌到期前,掘金使用 refresh_token 发一个请求,去更新令牌。
3.9-3.10 掘金后端请求GitHub用户信息
因为看不到具体的请求,这里就不做过多赘述了。
3.11 掘金后端收到前端发送的redirect_uri请求,302到新url
3.12 授权成功,关闭小窗
302后的新Url图示如下:
新url会返回授权成功的html页面。并关闭小窗。
4 state的作用
前文提到了一下state,这里详细说明一下它的作用。
state 参数用于防止跨站请求伪造(CSRF)攻击。在授权请求过程中,客户端生成一个唯一的 state 值并将其包含在授权请求中。当授权服务器将用户重定向回客户端时,会附带相同的 state 值。客户端在接收到响应后,验证返回的 state 值是否与原始请求中生成的值一致,从而确保请求的合法性。
假设state不存在,存在如下场景:
有一个攻击A,登录了掘金,并且去关联GitHub账号,在授权时截获了自己的授权码回调Url,也就是如下URL:
https://juejin.cn/passport/auth/login_success?code=攻击者A的授权码
攻击者A将上述url精心封装成了一个A标签放到了掘金的网站上并发布了出来。
大冤种B在自己浏览器已经登录了掘金,这时候恰好看到了A精心封装的A标签,就点了一下。这时候掘金以为自己收到了GitHub的授权码,将该授权码(攻击者A的授权码)与正在登录中账号B做了关联。从此A就可以随时通过自己的GitHub账号登录B的掘金。
从上述过程可以看出,本质原因是授权与绑定信息分离,掘金无法验证绑定信息时前一次获取授权码时是同一个人。引入state就是为了保证授权的用户和关联账户的账号是同一个人。A请求时会带着与A关联的随机state,B再点A的url掘金会发现这个state不是跟B关联的,这就是state的作用。
当然,通常意义上授权码都有过期时限与只能使用一次的限制。
如果读者觉得笔者还是在不知所云,不知道在说啥,强烈建议您读一下以下文章,写的非常不错。
移花接木:针对OAuth2的CSRF攻击 - 简书 (jianshu.com)
5 结语
在这里班门弄斧了一下,其中很多流程都是笔者半猜半看写出来的,不一定准确,如果读者有异议,欢迎斧正。