Spring Security Oauth2 Token 提取流程源码分析

问题

Spring Security Oauth2 请求 提取 Token 方式

  • 请求头携带token
  • url参数携带token
  • 请求form表单

源码

spring-security-Oauth2 版本: 2.3.4.RELEASE

配置

  • 访问受保护的资源需要携带token访问,所以资源服务器都会写一个相应的安全配置类
  • 配置适配器类都是继承 ResourceServerConfigurerAdapter,配合使用@EnableResourceServer启用相应资源服务器配置,内部关联了ResourceServerSecurityConfigurerHttpSecurity

资源安全配置

@Override
    public void configure(ResourceServerSecurityConfigurer resources) {
       // 配置代码 省略
    }

Http安全相关

配置权限地址拦截这些都是使用这个方法参数进行相应的配置),相应代码如下:

@Override
    public void configure(HttpSecurity http) throws Exception {
        // 配置代码 省略
    }

关键代码分析

ResourceServerSecurityConfigurer 源码分析

  • ResourceServerSecurityConfigurer是一个配置器,
    关键地方是看他挂载了那些过滤器
  • 方法签名为org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer.configure注册了OAuth2AuthenticationProcessingFilter正是核心的关键,注册在AbstractPreAuthenticatedProcessingFilter之前,详细代码和部分注释如下:
@Override
	public void configure(HttpSecurity http) throws Exception {
		AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);
		// 初始化过滤器
		resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
		resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
		resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
		if (eventPublisher != null) {
			resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
		}
		// 配置 token 提取实现类
		if (tokenExtractor != null) {
			resourcesServerFilter.setTokenExtractor(tokenExtractor);
		}
		if (authenticationDetailsSource != null) {
			resourcesServerFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
		}
		resourcesServerFilter = postProcess(resourcesServerFilter);
		// 设置无状态
		resourcesServerFilter.setStateless(stateless);

		http
			.authorizeRequests().expressionHandler(expressionHandler)
		.and()
                // 在AbstractPreAuthenticatedProcessingFilter之前插入resourcesServerFilter过滤器
			.addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
			.exceptionHandling()
                    // 配置访问拒绝处理器
				.accessDeniedHandler(accessDeniedHandler)
				.authenticationEntryPoint(authenticationEntryPoint);
	}

OAuth2AuthenticationProcessingFilter 源码分析

  • OAuth2AuthenticationProcessingFilter是一个普通实现javax.servlet.Filterorg.springframework.beans.factory.InitializingBean接口的Bean;
  • 既然是过滤器,核心方法当然是org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter 详细代码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
			ServletException {
                // 获取日志是否开启 debug 模式
		final boolean debug = logger.isDebugEnabled();
		final HttpServletRequest request = (HttpServletRequest) req;
		final HttpServletResponse response = (HttpServletResponse) res;
		try {
		        // 关键代码,提取token返回认证信息
			Authentication authentication = tokenExtractor.extract(request);
                        // 省略代码...
		}
		catch (OAuth2Exception failed) {

		         // 省略代码...
			return;
		}
		chain.doFilter(request, response);
	}
  • 追溯源码org/springframework/security/oauth/spring-security-oauth2/2.3.4.RELEASE/spring-security-oauth2-2.3.4.RELEASE-sources.jar!/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilter.java:64
  • 发现TokenExtractor接口实现类是org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor
  • BearerTokenExtractor详细源码里面先后调用了几个方法,从extract->extractToken方法,extractToken方法是关键代码,详细代码如下:
protected String extractToken(HttpServletRequest request) {
        // 提取请求头参数
        String token = this.extractHeaderToken(request);
        if (token == null) {
            logger.debug("Token not found in headers. Trying request parameters.");
            // 直接获取请求参数
            token = request.getParameter("access_token");
            if (token == null) {
                logger.debug("Token not found in request parameters.  Not an OAuth2 request.");
            } else {
                // 如果两种方式都获取不到,直接设置一个认证请求头,不带 token
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, "Bearer");
            }
        }

        return token;
    }

    protected String extractHeaderToken(HttpServletRequest request) {
        // 获取请求头为 Authorization 的信息 
        Enumeration headers = request.getHeaders("Authorization");

        String value;
        do {
            if (!headers.hasMoreElements()) {
                return null;
            }

            value = (String)headers.nextElement();
        } while(!value.toLowerCase().startsWith("Bearer".toLowerCase()));
        // 如果存在,提取Bear后部分实际token值
        String authHeaderValue = value.substring("Bearer".length()).trim();
        // 重新设置 request 属性
        request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, value.substring(0, "Bearer".length()).trim());
        int commaIndex = authHeaderValue.indexOf(44);
        if (commaIndex > 0) {
            authHeaderValue = authHeaderValue.substring(0, commaIndex);
        }

        return authHeaderValue;
    }

从上面代码分析得到:
extractToken方法是提取token,返回相应的认证信息
extractHeaderToken是从请求头获取相应 token,extractHeaderToken无法从请求头相应参数获取 token 时候,request.getParameter("access_token");这段代码会获取请求参数。

结论

整个流程下来,是通过OAuth2AuthenticationProcessingFilter提取请求头参数,获取不到再去获取请求参数。