项目安全框架:SpringSecurity+Oauth2+JWZ,采用集中认证授权方式。配置不在这里具体写了,可参考百度或google的通用配置。
然而,在项目中当使用无效的access_token或者不带token进行资源访问时,我发现前端出现了【500 内部错误】的信息,并没有如预期那样出现【
】的状态码。调查日志发现如下报错:

21:12:55.345 [http-nio-9201-exec-1] ERROR o.a.c.c.C.[.[localhost] - [log,175] - Exception Processing ErrorPage[errorCode=0, location=/error] org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 : [no body] at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:105) at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:170) at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:112) at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63) at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:782) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:740) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674) at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:583) at org.springframework.security.oauth2.provider.token.RemoteTokenServices.postForMap(RemoteTokenServices.java:149) at org.springframework.security.oauth2.provider.token.RemoteTokenServices.loadAuthentication(RemoteTokenServices.java:106) at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager.authenticate(OAuth2AuthenticationManager.java:83) at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:150) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712) at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:461) at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384) at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:394) at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:253) at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:348) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:173) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
这部分内容是最重要的内容,也是解决问题的思路来源,我截取出来:
21:12:55.345 [http-nio-9201-exec-1] ERROR o.a.c.c.C.[.[localhost] - [log,175] - Exception Processing ErrorPage[errorCode=0, location=/error] org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 : [no body] at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:105) at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:170) at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:112) at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63) at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:782) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:740) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674) at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:583) at
从日志可以看出,当进行token有效检查的时候,会通过httpclient调用/oauth/check_token这个api进行认证,token无效的时候,其实已经返回了401的状态码,
但是我们根据错误日志(上图的红色文字部分),通过查看RestTemplate.class的源码可发现
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { ResponseErrorHandler errorHandler = this.getErrorHandler(); boolean hasError = errorHandler.hasError(response); if(this.logger.isDebugEnabled()) { try { int code = response.getRawStatusCode(); HttpStatus status = HttpStatus.resolve(code); this.logger.debug("Response " + (status != null?status:Integer.valueOf(code))); } catch (IOException var8) { ; } } if(hasError) { errorHandler.handleError(url, method, response); } }
当check_token发生错误时,这里直接进行了异常处理,未对返回的信息做解析,因此我们得到了这样的错误信息“org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 : [no body]”。
解决方法也很简单,根据错误日志的提示,我们重写自己的ResponseErrorHandler就行。比如:
import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.ResponseErrorHandler; import java.io.IOException; /** * @Description: 定制无效token时的错误信息处理 * @Author : yyq */ public class CustomerResponseErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { // 这里返回false return false; } @Override public void handleError(ClientHttpResponse response) throws IOException { } }
然后在ResourceServerConfig文件中加入我们自定义的ErrorHandler配置
@Bean @LoadBalanced public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(new TkResponseErrorHandler()); return restTemplate; }
PS:如果有同学出现的错误信息是返回的400 BadRequest,可能是AuthorizationServerConfigurerAdapter配置中少配置了自定义异常的选项。可参考
/** * 定义授权和令牌端点以及令牌服务 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints // 请求方式 .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) // 指定token存储位置 .tokenStore(tokenStore()) // 自定义生成令牌 // .tokenEnhancer(tokenEnhancer) .tokenEnhancer(tokenEnhancer()) // 用户账号密码认证 .userDetailsService(userDetailsService) // 指定认证管理器 .authenticationManager(authenticationManager) // 自定义异常处理 .exceptionTranslator(new CustomWebResponseExceptionTranslator());
。。。
至此,以上问题应该可以解决。如果有出现其他问题的同学,可以留言沟通!