关于 Spring Security 对 OAuth2 的支持
前言
之前或多或少的接触过 Spring Security ,最近有契机基于 Spring Security 搭建了一套较完整的 OAuth2 认证服务,对 Spring Security 对 OAuth2 支持的生态做了简单的了解,以此文分享
OAuth2 生态
- 现在的
Spring Security 是独立完整的项目,囊括了对 oauth2 等的支持而不是基于之前的 Spring Security OAuth2 项目独立实现(原项目已 @Deprecated ) Spring Security oauth2 模块支持 oauth2-client oauth2-resource-server oauth2-jose ,不再提供 认证中心 的支持- 认证中心由单独的新项目
spring-security-oauth2-authorization-server 支持 ,该项目是由 Spring Security 团队牵头的社区开源项目,目前正式版本仅为 0.2.3 spring-security-oauth2-authorization-server 目前并没有或者说并没有特别成熟的 Spring Boot 自动装配支持,但实际上作为较新的项目,其配置方式已经很大程度上向 Spring Boot 靠拢(而不是传统的 @EnabledXXX 模式)- 以上是当前
Spring Security (准确的说是 Spring )对 OAuth2 支持的最 新 生态,本着 技术就要用新的 的原则,个人项目完全基于该生态搭建
关于 OAuth2
- 这里不会去聊
OAuth2 的细节,只想聊下 OAuth2 对我思考方式和对 【规范】二字理解的影响 - 说来说去,无论新的生态还是老的技术,无非是实现和使用上的区别,其底层的规范、理解还是基于
OAuth2 本身定义的,因此对这些生态组件的学习、理解很大程度就基于对规范的理解 - 规范往往看似通俗易懂却又字字细节,就像我自认为很了解
OAuth2 规范,但又说不出个一二三来,这让我意识到规范是很值得 咬文嚼字 的东西 - 比如
OAuth2 最常规的模式:授权码模式 中,用户是基于代理(浏览器)被 Client 重定向 到 认证中心 与 认证中心 单独交互 从而保证授权码过程对 Client 无感,而不应该笼统的描述为 Client 从 认证中心 获取 授权码 (两者相差甚远) - 这对学习、使用实现组件比如
Spring Security 时的影响是很大的,或者对是否、为什么选择 OAuth2 的何种模式都是很关键的 - 综上所述,如果想提高
OAuth2 生态组件的使用体验,首先深度的回顾一下 OAuth2 规范是很有必要的 - 贴一张
授权码模式 的序列图,思考一下
Spring Security
Spring 体系下 OAuth2 的实现,前面提到,它提供 Client ResourceServer 的支持,AuthorizationServer 的部分由另一个项目支持- 笼统地说,它基于一组一组的
Filter 实现,对应路由的一组 Filter 封装成一个 SecurityFilterChain ,由我们以 Bean 组件的形式提供,最终以一个 FilterChainProxy 实例的形式注册到 Web 容器(Servlet )中
FilterChainProxy
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(firewallRequest);
if (filters == null || filters.size() == 0) {
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
return;
}
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
------------------ getFilters ------------
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
for (SecurityFilterChain chain : this.filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
FilterChainProxy 的部分源码:
- 拦截请求并获取对应的 (
Spring Security )Filter 组,如果有就执行过滤逻辑 - 这些
Filter 就是普通的 Servlet Filter ,通常有 Spring Security 内置或用户自定义,但它们不是直接注册到 Servlet 容器里,而是注册到 SecurityFilterChain 里 getFilters 的逻辑就是遍历所有 SecurityFilterChain ,返回第一个匹配 SecurityFilterChain 的 Filter 组,因此 SecurityFilterChain 的匹配器(RequestMather )和顺序定义很重要
SecurityFilterChain 定义示例
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSFC(HttpSecurity httpSecurity) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(httpSecurity);
return httpSecurity
.formLogin()
.and()
.build();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
public SecurityFilterChain loginSFC(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.requestMatcher(new AntPathRequestMatcher(
"/login"
))
.authorizeHttpRequests()
.anyRequest().permitAll()
.and()
.csrf().disable()
.build();
}
@Bean
public SecurityFilterChain defaultWebSFC(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf().disable()
.build();
}
- 如上是
AuthorizationServer 的一段 SecurityFilterChain 配置示例 @Order 注解指定对应的优先级,依次定义了三级过滤: - AuthorizationServer 通过的对内置路由的处理,比如 /oauth2/authorize /oauth2/token 等路由忽略跨域等 - 自定义了一个指定了 RequestMatcher 的 SecurityFilterChain ,处理 /login 端口 - 兜底 SecurityFilterChain 未指定 RequestMatcher ,则匹配所有剩余的路由并处理它们:示例中的配置标识上述端口需要认证才能访问 - 断点下的优先级如下,则拦截到的请求返回第一个匹配到 SecurityFilterChain 的 Filter 组
总结
- 本文从
Spring Security 对 OAuth2 的支持入手,简单的了解了下当前最 新 的生态环境 OAuth2 规范的理解,对 Spring Security 的使用很有帮助- 浅入浅出的聊了下
Spring Security 的架构,因此对 OAuth2 各组件的支持就是提供对应的 Filter SecurityFilterChain 式的安全策略配置应该是 Spring Security 与 Spring Security OAuth2 最大的不同
我猜的,后者我并没有完整的使用过 :)
|