记住两种让SpringSecurity“管好自己的事”的方法。遇到一个问题一个应用程序对外提供了一个Rest接口。接口的访问认证由SpringSecurityOAuth2控制,token采用JWT形式。由于某些原因,具有特定路径前缀(假设为/custom/)的接口需要使用另一种自定义认证方式,token是一串没有规则的随机字符串。两种认证方式的token以Authorization:bearerxxx的形式在Headers中传递。那么当外部请求这个应用的接口时,情况是这样的:这个时候,问题就出现了。我通过WebSecurityConfigurerAdapter配置SpringSecurity直接释放/custom/前缀的请求:httpSecurity.authorizeRequests().regexMatchers("^(?!/custom/).*$").permitAll();但是前缀为/custom/的请求还是被拦截了,报如下错误:{"error":"invalid_token","error_description":"CannotconvertaccesstokentoJSON"}从错误提示分析问题,首先可以排除CustomWebFilter的嫌疑,通过检查自定义authentication方法的token不是json格式,自然会尝试转成json。推测问题出在SpringSecurity的“多管闲事”上,拦截了不该拦截的请求。经过一番面向搜索的编程和源码调试,发现抛出上述错误信息的位置是在JwtAccessTokenConverter.decode方法中:protectedMapdecode(Stringtoken){try{//如下这行会抛出异常Jwtjwt=JwtHelper.decodeAndVerify(token,verifier);//...这里有一些代码}catch(Exceptione){thrownewInvalidTokenException("CannotconvertaccesstokentoJSON",e);}}调用栈如下:从调用的上下文可以看出(高亮那一行),执行逻辑在一个名为OAuth2AuthenticationProcessingFilter的Filter中,它会尝试从请求中提取BearerToken,然后做一些处理(这里是JWT转换和校验等)。这个Filter是在ResourceServerSecurityConfigurer.configure中初始化的,我们的应用也是一个SpringSecurityOAuth2ResourceServer,通过类名来配置。解决问题找到问题后,通过自己的思考和同事间的讨论,得出了两个可行的解决方案。方案一:让特定的请求跳过OAuth2AuthenticationProcessingFilter这个方案的思路是在OAuth2AuthenticationProcessingFilter.doFilter方法执行前使用AOP做判断。如果请求路径以/custom/开头,则跳过Filter继续执行;如果请求的路径不是/custom/开头的话,会正常执行。关键代码提示:@Aspect@ComponentpublicclassAuthorizationHeaderAspect{@Pointcut("execution(*org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(..))")publicvoidsecurityOauth2DoFilter(){}@Around(“securityOauth2DoFilter()”)publicvoidskipNotCustom(ProceedingJoinPointjoinPoint)throwsThrowable{Object[]args=joinPoint.getArgs();if(args==null||args.length!=3||!(args[0]instanceofHttpServletRequest&&args[1]instanceofjavax.servlet.ServletResponse&&args[2]instanceofFilterChain)){joinPoint.proceed();返回;}HttpServletRequest请求=(HttpServletRequest)args[0];if(request.getRequestURI().startsWith("/custom/")){joinPoint.proceed();}else{((FilterChain)args[2]).doFilter((ServletRequest)args[0],(ServletResponse)args[1]);}}}方案二:调整Filt如果er顺序让请求先到达我们自定义的Filter,请求路径以/custom/开头,处理完自定义的token验证逻辑,然后去掉AuthorizationHeader(在OAuth2AuthenticationProcessingFilter.doFilter中,如果Bearer无法获取到Token,不会抛出异常),其他的请求直接放掉,也是一种可以达到目的的思路但现状是自定义Filter默认在OAuth2AuthenticationProcessingFilter之后执行。如何调整它们的执行顺序?在我们之前找到的OAuth2AuthenticationProcessingFilter注册的地方,也就是ResourceServerSecurityConfigurer.configure方法中,我们可以看到Filter是这样添加的:@Overridepublicvoidconfigure(HttpSecurityhttp)throwsException{//...这里有一些代码说起HttpSecurity,我们的印象是...WebSecurityConfigurerAdapter用于配置请求释放时的入参。那个时候可以在OAuth2AuthenticationProcessingFilter之前注册自定义Filter吗?我们将之前配置发布规则处的代码修改为://...httpSecurity.authorizeRequests().registry.regexMatchers("^(?!/custom/).*$").permitAll().and()。addFilterAfter(新的CustomWebFilter(),X509AuthenticationFilter.class);//...注意:CustomWebFilter是直接新建的,手动添加到SecurityFilterChain中,不再自动注入到其他FilterChain中。为什么将自定义Filter添加到X509AuthenticationFilter.class之后呢?可以参考spring-security-config包的FilterComparator中预设的Filter顺序来决定。从前面的代码我们可以看到在AbstractPreAuthenticatedProcessingFilter.class之前添加了OAuth2AuthenticationProcessingFilter,而在FilterComparator预设的顺序中,X509AuthenticationFilter.class在AbstractPreAuthenticatedProcessingFilter.class之前,我们的添加足以保证自定义Filter在OAuth2AuthenticationProcessingFilter之前。经过上面的修改,自定义过滤器已经在我们预期的位置了,那么我们就在这个过滤器中对以/custom/开头的请求路径做一些必要的处理,然后清空AuthorizationHeader。关键代码如下:@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequestrequest=(HttpServletRequest)servletRequest;if(request.getServletPath().startsWith("/custom/")){//在这里做点什么//...finalStringauthorizationHeader="Authorization";HttpServletRequestWrapperrequestWrapper=newHttpServletRequestWrapper((HttpServletRequest)servletRequest){@OverridepublicStringgetHeader(Stringname){if(authorizationHeader.equalsIgnoreCase(name)){返回空;}返回超级。获取标题(名称);}@OverridepublicEnumerationgetHeaders(Stringname){如果(authorizationHeader.equalsIgnoreCase(name)){returnnewVector().elements();}返回super.getHeaders(名称);}};filterChain.doFilter(requestWrapper,servletResponse);}else{filterChain.doFilter(servletRequest,servletResponse);}}总结经过尝试,两种方案都能满足需求。项目最终采用了第一种方案。我相信还有其他的想法可以解决这个问题。经过这个过程,也暴露了对SpringSecurity的理解不够,后续还需要找时间做一些更深入的学习。参考https://www.cnblogs.com/alala...