Remember two ways to make Spring Security "nosy".
Encounter problems
An application provides a Rest interface to the outside, and the access authentication of the interface is controlled by Spring Security OAuth2, and the token is in the form of JWT. For some reasons, the interface of a specific path prefix (assumed to be /custom/
) needs to use another custom authentication method. The token is a random string of random strings. The tokens of the two authentication methods are passed in the Authorization: bearer xxx
, and the form is 061c88383e9767.
So when an external request for the interface of this application, the situation is as follows:
At this time, the problem appeared.
I configured Spring Security to pass /custom/
prefix requests directly WebSecurityConfigurerAdapter
httpSecurity.authorizeRequests().regexMatchers("^(?!/custom/).*$").permitAll();
But the interface requesting the /custom/
was still blocked, and the following error was reported:
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}
analyse problem
From the error message, you can first eliminate CustomWebFilter
by checking. The token of the custom authentication method is not in JSON format, so naturally it does not try to convert it into JSON.
It is speculated that the problem lies in Spring Security's "nosy", intercepting requests that should not be intercepted.
After some search-oriented programming and source code debugging, the location where the above error message is thrown is found in the JwtAccessTokenConverter.decode
method:
protected Map<String, Object> decode(String token) {
try {
// 下面这行会抛出异常
Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
// ... some code here
}
catch (Exception e) {
throw new InvalidTokenException("Cannot convert access token to JSON", e);
}
}
The call stack is as follows:
It can be seen from the context of the call (the highlighted line) that the execution logic is in a OAuth2AuthenticationProcessingFilter
, which will try to extract the Bearer Token from the request, and then do some processing (here is JWT conversion and verification, etc.). This Filter is ResourceServerSecurityConfigurer.configure
. Our application is also used as a Spring Security OAuth2 Resource Server. From the class name, it can be seen that this is the configuration.
Solve the problem
After finding the problem, after my own thinking and discussion among colleagues, two feasible solutions were reached.
Option 1: Let specific requests skip OAuth2AuthenticationProcessingFilter
The idea of this program is to make a judgment before the execution of the OAuth2AuthenticationProcessingFilter.doFilter
- If the request path starts with
/custom/
, skip the Filter and continue execution; - If the request path does not
/custom/
, execute it normally.
Key code indication:
@Aspect
@Component
public class AuthorizationHeaderAspect {
@Pointcut("execution(* org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(..))")
public void securityOauth2DoFilter() {}
@Around("securityOauth2DoFilter()")
public void skipNotCustom(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args == null || args.length != 3 || !(args[0] instanceof HttpServletRequest && args[1] instanceof javax.servlet.ServletResponse && args[2] instanceof FilterChain)) {
joinPoint.proceed();
return;
}
HttpServletRequest request = (HttpServletRequest) args[0];
if (request.getRequestURI().startsWith("/custom/")) {
joinPoint.proceed();
} else {
((FilterChain) args[2]).doFilter((ServletRequest) args[0], (ServletResponse) args[1]);
}
}
}
Option 2: Adjust the Filter order
If the request can reach our custom Filter first, the request path starts with /custom/
, after processing the custom token verification and other logic, then remove the Authorization
Header (in OAuth2AuthenticationProcessingFilter.doFilter
, if the Bearer Token is not available, no exception will be thrown ), the direct release of other requests is also an idea that can achieve the goal.
But the current situation is that the custom OAuth2AuthenticationProcessingFilter
executed after 061c88383e9985 by default. How to adjust their execution order?
OAuth2AuthenticationProcessingFilter
we found earlier, that is, in the ResourceServerSecurityConfigurer.configure
method, we can see that the Filter is added by the following writing method:
@Override
public void configure(HttpSecurity http) throws Exception {
// ... some code here
http
.authorizeRequests().expressionHandler(expressionHandler)
.and()
.addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
}
The core method is HttpSecurity.addFilterBefore
. Speaking of HttpSecurity
, we have an impression... It is WebSecurityConfigurerAdapter
to configure the parameters when requesting release through 061c88383e99cf. Can the custom filter be registered before OAuth2AuthenticationProcessingFilter
We modify the code at the place where the release rule was configured as follows:
// ...
httpSecurity.authorizeRequests().registry.regexMatchers("^(?!/custom/).*$").permitAll()
.and()
.addFilterAfter(new CustomWebFilter(), X509AuthenticationFilter.class);
// ...
Note: CustomWebFilter is changed to new directly, manually added to the Security Filter Chain, and no longer automatically injected into other Filter Chains.
Why is the custom filter added after X509AuthenticationFilter.class
Reference may be spring-security-config package FilterComparator
in order to make a decision preset Filter, seen from the preceding code OAuth2AuthenticationProcessingFilter
is added to AbstractPreAuthenticatedProcessingFilter.class
before, in FilterComparator
sequentially in a preset, X509AuthenticationFilter.class
is AbstractPreAuthenticatedProcessingFilter.class
before, we This addition is sufficient to ensure that the custom Filter is before OAuth2AuthenticationProcessingFilter
.
After making the above modification, the custom Filter is already in the position we expected, then in this Filter, we need to do the necessary processing /custom/
Authorization
Header. The key code is as follows:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (request.getServletPath().startsWith("/custom/")) {
// do something here
// ...
final String authorizationHeader = "Authorization";
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper((HttpServletRequest) servletRequest) {
@Override
public String getHeader(String name) {
if (authorizationHeader.equalsIgnoreCase(name)) {
return null;
}
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaders(String name) {
if (authorizationHeader.equalsIgnoreCase(name)) {
return new Vector<String>().elements();
}
return super.getHeaders(name);
}
};
filterChain.doFilter(requestWrapper, servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
summary
After trying, the two schemes can meet the needs, and the first scheme is finally used in the project. I believe there are other ideas that can solve the problem.
After this process, the problem of insufficient understanding of Spring Security has also been exposed, and the follow-up needs to take time to do some more in-depth learning.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。