我在項目中的某個Controller上添加了@RequirePermissions
註解,但願在執行該請求前,能夠先進行權限驗證。可是當我請求該Controller時,返回的確是404錯誤。
首先我懷疑的是由於權限不足而拋出了404錯誤。可是我發現我在AController
的請求方法1上加了@RequiresPermession
註釋,可是請求方法2一樣也報了404錯誤。因此應該不是shiro對權限進行了攔截,更像是整個controller的請求映射都沒被Spring正常解析。ios
咱們知道SpringMVC處理請求轉發的地方是在DispatchServlet
的doDispatch
方法中。跨域
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { //若是是Multipart請求,則先處理 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. //根據請求找到對應HandlerMapping,在經過HandlerMapping返回對應的處理器執行鏈HandlerExecuteChain mappedHandler = getHandler(processedRequest); //找不到對應的映射,則拋出404異常 if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); //GET 和 HEAD請求 若是資源沒更新,則直接返回 if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //請求的預處理,其實就是應用攔截器的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //正式由Controller處理請求, // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } //根據Controller返回的視圖名,解析視圖 applyDefaultViewName(processedRequest, mv); //後置處理,應用攔截器的後置處理方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } //處理異常或是渲染視圖 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
一種懷疑是在getHandler
時,找不到對應的executeHandlerChain,因此產生了404錯誤。可是在斷點中咱們發現依舊能夠獲取到相應的executeHandlerChain
。
session
貌似沒有問題(其實若是夠細心且瞭解MappingHandler的話,此時應該已經能看出問題了)。
繼續往下,直到過了前置處理依舊沒有問題(說明基本上不是攔截器形成的404錯誤)。
而再往下發現通過ha.handle()
方法後,返回的mv對象爲null,而此時看response對象已經出現了404
的錯誤。
app
所以咱們將關注點放在handle
的執行順序上。
咱們獲得的ha
是HttpRequestHandlerAdapter
對象。它的handle
方法以下:async
@Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; }
HandlerAdapter
是一個處理器適配器。主要是適配不一樣類型的處理器。而此時的Handler
類型是ResourceHttpRequestHandler
。
其中handleRequest
方法以下:ide
@Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // For very general mappings (e.g. "/") we need to check 404 first //根據請求路徑,解析對應的靜態資源 Resource resource = getResource(request); //若是找不到對應資源,則拋出404錯誤 if (resource == null) { logger.trace("No matching resource found - returning 404"); response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } if (HttpMethod.OPTIONS.matches(request.getMethod())) { response.setHeader("Allow", getAllowHeader()); return; } // Supported methods and required session checkRequest(request); // Header phase if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) { logger.trace("Resource not modified - returning 304"); return; } // Apply cache settings, if any prepareResponse(response); // Check the media type for the resource MediaType mediaType = getMediaType(request, resource); if (mediaType != null) { if (logger.isTraceEnabled()) { logger.trace("Determined media type '" + mediaType + "' for " + resource); } } else { if (logger.isTraceEnabled()) { logger.trace("No media type found for " + resource + " - not sending a content-type header"); } } // Content phase if (METHOD_HEAD.equals(request.getMethod())) { setHeaders(response, resource, mediaType); logger.trace("HEAD request - skipping content"); return; } ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response); if (request.getHeader(HttpHeaders.RANGE) == null) { Assert.state(this.resourceHttpMessageConverter != null, "Not initialized"); setHeaders(response, resource, mediaType); this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage); } else { Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized"); response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request); try { List<HttpRange> httpRanges = inputMessage.getHeaders().getRange(); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); this.resourceRegionHttpMessageConverter.write( HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage); } catch (IllegalArgumentException ex) { response.setHeader("Content-Range", "bytes */" + resource.contentLength()); response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); } } }
其中須要關係的部分是getResource
方法,由於找不到對應的Resource,而產生了404錯誤。咱們也找到了404錯誤的緣由。
找到404的緣由後,繼續分析。ResourceHttpRequestHandler
是負責處理靜態資源的。正常狀況下,咱們到控制器的請求不該該是由ResourceHttpRequestHandler
處理。所以,咱們獲得的Handler
並不是是咱們指望的。post
首先看DispatchServlet
的getHandler
方法。ui
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { //遍歷內部的HandlerMapping(內置處理器),返回該請求映射的處理器 for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } //返回處理器,並造成處理器鏈 HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } } return null; }
DispatcherServlet
在初始化時會建立內置的一些HandlerMapping
。常見的有SimpleUrlHandlerMapping
(映射請求和靜態資源),RequestMappingHandlerMapping
(映射請求和@RequestMapping
註解的Controller
中的方法),BeanNameUrlHandlerMapping
(映射請求和處理器bean,映射關係由bean Name肯定)等。
爲何RequestMappingHandlerMapping
沒可以爲咱們對應的處理器?瞭解下RequestMappingHandlerMapping
的getHandler
方法:this
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //調用內部獲取處理器的方法(模板模式) Object handler = getHandlerInternal(request); //若是處理器爲空 則使用默認的處理器 if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } //若是返回的處理器是bean Name,則獲取bean對象 // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } //造成處理器執行鏈(主要是添加攔截器) HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); //若是是跨域請求,則設置跨域的配置 if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
查找處理器的邏輯主要是是在getHandlerInternal
方法中:url
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //根據請求解析路徑 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); if (logger.isDebugEnabled()) { logger.debug("Looking up handler method for path " + lookupPath); } this.mappingRegistry.acquireReadLock(); try { //獲取對應的處理器方法 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); if (logger.isDebugEnabled()) { if (handlerMethod != null) { logger.debug("Returning handler method [" + handlerMethod + "]"); } else { logger.debug("Did not find handler method for [" + lookupPath + "]"); } } return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } }
而lookupHandlerMethod
方法則是從MappingRegistry
中獲取匹配url的方法。在根據URL匹配的精度確認最後的方法。ReqeustMappingHandlerMapping
找不處處理器,說明MappingRegistry
並無解析到對應的處理器方法。
RequestMappingHandlerMapping
實現了InitializingBean
接口。在其afterPropertiesSet
方法中實現了將
處理器映射方法mappingRegistry
的邏輯。具體實如今其父類AbstractHandlerMethodMapping
中。
//初始化時檢測處理器方法 @Override public void afterPropertiesSet() { initHandlerMethods(); } //掃描上下文中的bean,註冊對應的處理器方法 protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } //獲取上下文中的bean name String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); //遍歷bean names for (String beanName : beanNames) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); } } //是否爲標準處理器(RequestMappingHandlerMapping的實現根據類上是否有@Controller或是@RequestMapping註釋) if (beanType != null && isHandler(beanType)) { //篩選對應的方法並註冊 detectHandlerMethods(beanName); } } } handlerMethodsInitialized(getHandlerMethods()); }
接下來就是在RequestMappingHandlerMapping
初始化的過程當中斷點調試,看看是什麼問題:
能夠看到相應的控制器被代理事後丟失了註釋。而這裏的代理並不是是AspectJ的建立的,而是com.sun.Proxy對象。
若是在啓動時觀察對應控制器的bean的建立狀況,能夠發現這個bean被加強了兩次:
第一次加強:
第二次加強:
能夠看到第二次加強事後bean丟失了@Controller
的註釋。
咱們已經知道形成404的真正緣由是Controller初始化時被加強了兩次。並在第二次加強時丟掉了註釋。致使了該Controller沒法被正常映射。所以咱們只須要關閉一次加強過程便可。事實上,因爲已經存在了ProxyCreator,所以ShiroAnnotationProcessorAutoConfiguration
中的DefaultAdvisorAutoProxyCreator
就再也不須要了。
因此能夠經過在配置文件中將shiro.annotations.enabled
屬性設置爲false
。或者是直接在項目的配置中exclude掉ShiroAnnotationProcessorAutoConfiguration
。而後再聲明AuthorizationAttributeSourceAdvisor
便可。