Spring Security Oauth2 Token 提取流程源码分析
问题
Spring Security Oauth2 请求 提取 Token 方式
- 请求头携带token
- url参数携带token
- 请求form表单
源码
spring-security-Oauth2
版本: 2.3.4.RELEASE
配置
- 访问受保护的资源需要携带token访问,所以资源服务器都会写一个相应的安全配置类
- 配置适配器类都是继承
ResourceServerConfigurerAdapter
,配合使用@EnableResourceServer
启用相应资源服务器配置,内部关联了ResourceServerSecurityConfigurer
和HttpSecurity
资源安全配置
@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.Filter
和org.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
提取请求头参数,获取不到再去获取请求参数。