oauth2
引言
读了《设计模式之美》和《凤凰架构》架构安全篇之后,决定写一个OAuth2.0的认证流程的Demo,也算是一个阶段性的总结,具体原理实现见《凤凰架构》(架构安全设计篇)。
涉及到的源码可以从https://github.com/WeiXiao-Hyy/oauth2获取,欢迎Star!
OAuth2.0原理
主要解决的问题
面向解决第三方应用(Third-Party Application)的认证授权协议,使用Token代替用户密码作为授权的凭证。
- 有了令牌之后,哪怕令牌被泄漏,也不会导致密码的泄漏
- 令牌上可以设定访问资源的范围以及时效性
- 每个应用都持有独立的令牌,哪个失效都不会波及其他
OAuth2.0一共提出了四种不同的授权方式:
- 授权码模式(Authorization Code)
- 隐式授权模式(Implicit)
- 密码模式(Resource Owner Password Credentials)
- 客户端模式(Client Credentials)
本文介绍和实现的是授权码方式。
OAuth2.0流程图
- client请求授权服务端,获取Authorization Code;
- client通过Authorization Code再次请求授权服务端,获取Access Token;
- client通过服务端返回的Access Token获取用户的基本信息。
流程图如下所示:
第一次使用github账号应用来登陆掘金,需要进行授权
掘金即是第三方应用, 请求GitHub。
注册一个新的OAuth Application
如果我们需要使用GitHub账号来关联我们自己的第三方应用则需要完整走一遍OAuth2.0流程。
GitHub生成的client_id, client_secret(密钥), redirect_uri, homepage_url, application_name等等。
clientId如何生成唯一的
在注册clientId以及申请clientSecret时,如何保证生成的clientId是不重复的呢?
和JWT以及Cookie-Session对比
参考《凤凰架构》架构安全篇 https://icyfenix.cn/architect-perspective/general-architecture/system-security/authorization.html
谈谈State参数为什么可以防止CSRF攻击
观察GitHub OAuth2.0实现文档,可以观察到在authroize
接口需要传递一个state参数,并且在redirect_uri重定向时原封不动传递回来,所以为什么可以防止CSRF攻击呢?
核心: 在于授权服务端进行token请求绑定时,会从session将本次会话的账号与生成access_token进行绑定,而对于用户是谁并不关心。
案例
- 用户B登录
掘金
网站,并且选择绑定自己的GitHub
账号; 掘金
网站将用户B重定向到GitHub
,由于他之前已经登录过GitHub
,所以GitHub
直接向他显示是否授权掘金访问的页面;- 用户B在点击"同意授权"之后,截获
GitHub
服务器返回的含有authorization code
参数的HTTP响应; - 用户B精心构造一个Web页面,它会触发
掘金
网站向GitHub
发起令牌申请的请求,而这个请求中的authorization code
参数正是上一步截获到的code
; - 用户B将这个Web页面放到互联网上,等待或者诱骗受害者用户A来访问;
- 用户A之前登录了
掘金
网站,只是没有把自己的账号和其他社交账号绑定起来。在用户A访问了用户B准备的这个Web页面,令牌申请流程在用户A的浏览器里被顺利触发,掘金
网站从GitHub
那里获取到access_token
,但是这个token
以及通过它进一步获取到的用户信息却都是攻击者用户B; 掘金
网站将用户B的GitHub
账号同用户A的掘金
账号关联绑定起来,从此以后,用户B就可以用自己的GitHub
账号通过OAuth
登录到用户A在掘金
网站中的账号,堂而皇之的冒充用户A的身份执行各种操作;
代码实现(知识碎片总结)
本部分将开发过程中遇到的难点记录下来,具体源码参考此repo 👍
postman 请求共享session问题
本文为单体应用,使用了HttpServletRequest
和HttpSession
方便开发,将相关的数据保存在服务器的Session中,但是Postman在发送请求的时候,
不会将其视为同一个连接,导致获取不到Session中的数据,需要每次将用户登陆的Cookie赋值到相应的接口上才能获取到Session中的数据。
实现HandlerInterceptor接口完成请求过滤
涉及到接口请求过滤条件时,可以通过实现WebMvcConfigurer
接口来添加过滤规则,例如:
- 授权前用户没有登陆则需要重定向到登陆页面
- 如果access_token过期,则需要重新获取
- …
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//注意注入bean的写法
@Bean
public OauthInterceptor oauthInterceptor() {
return new OauthInterceptor();
}
@Bean
public AuthAccessTokenInterceptor accessTokenInterceptor() {
return new AuthAccessTokenInterceptor();
}
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor()).addPathPatterns("/user/**", "/oauth2.0/authorizePage", "/oauth2.0/authorize", "/sso/token");
registry.addInterceptor(oauthInterceptor()).addPathPatterns("/oauth2.0/authorize");
registry.addInterceptor(accessTokenInterceptor()).addPathPatterns("/api/**");
}
}
参考资料
- https://icyfenix.cn/architect-perspective/general-architecture/system-security/
- https://time.geekbang.org/column/intro/100039001
- https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
- https://juejin.cn/post/6844903668861534215#heading-4
- https://www.jianshu.com/p/c7c8f51713b6