头图

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

  1. If the request path starts with /custom/ , skip the Filter and continue execution;
  2. 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.

refer to


mzlogin
65 声望3 粉丝