項目安全框架: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());
。。。
至此,以上問題應該可以解決。如果有出現其他問題的同學,可以留言溝通!