关于 Spring Security 对 OAuth2 的支持

  • 前言
  • OAuth2 生态
  • 关于 OAuth2
  • Spring Security
  • FilterChainProxy
  • SecurityFilterChain 定义示例
  • 总结


前言

之前或多或少的接触过 Spring Security,最近有契机基于 Spring Security 搭建了一套较完整的 OAuth2 认证服务,对 Spring SecurityOAuth2 支持的生态做了简单的了解,以此文分享

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 整合 scoket springsecurity整合oauth2_java

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);

		// 获取请求对应的 Filter 组
		List<Filter> filters = getFilters(firewallRequest);

		// 如果没有就继续执行
		if (filters == null || filters.size() == 0) {
			firewallRequest.reset();
			chain.doFilter(firewallRequest, firewallResponse);
			return;
		}

		// 如果存在就执行这些 Filter
		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
	}

	------------------ getFilters ------------

	private List<Filter> getFilters(HttpServletRequest request) {
		int count = 0;
		
		// filterChains:即用户注册的所有 SecurityFilterChain
		for (SecurityFilterChain chain : this.filterChains) {
			// 根据路由匹配,返回匹到的第一个
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}
		return null;
	}

FilterChainProxy 的部分源码:

  • 拦截请求并获取对应的 (Spring SecurityFilter 组,如果有就执行过滤逻辑
  • 这些 Filter 就是普通的 Servlet Filter,通常有 Spring Security 内置或用户自定义,但它们不是直接注册到 Servlet 容器里,而是注册到 SecurityFilterChain
  • getFilters 的逻辑就是遍历所有 SecurityFilterChain,返回第一个匹配 SecurityFilterChainFilter 组,因此 SecurityFilterChain 的匹配器(RequestMather)和顺序定义很重要

SecurityFilterChain 定义示例

@Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSFC(HttpSecurity httpSecurity) throws Exception {
        // 匹配一组 AuthorizationServer 相关的路由
        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(
                        "/open"
                ))
                .authorizeHttpRequests()
                // 放开 login
                .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 等路由忽略跨域等
    - 自定义了一个指定了 RequestMatcherSecurityFilterChain,处理 /open 端口
    - 兜底 SecurityFilterChain 未指定 RequestMatcher,则匹配所有剩余的路由并处理它们:示例中的配置标识上述端口需要认证才能访问
    - 断点下的优先级如下,则拦截到的请求返回第一个匹配到 SecurityFilterChainFilter

spring 整合 scoket springsecurity整合oauth2_spring_02

总结

  • 本文从 Spring SecurityOAuth2 的支持入手,简单的了解了下当前最 的生态环境
  • OAuth2 规范的理解,对 Spring Security 的使用很有帮助
  • 浅入浅出的聊了下 Spring Security 的架构,因此对 OAuth2 各组件的支持就是提供对应的 Filter
  • SecurityFilterChain 式的安全策略配置应该是 Spring SecuritySpring Security OAuth2 最大的不同(我猜的,后者我并没有完整的使用过)