3030
3131import jakarta .servlet .DispatcherType ;
3232import jakarta .servlet .Filter ;
33- import jakarta .servlet .ServletException ;
3433import jakarta .servlet .ServletRequest ;
3534import jakarta .servlet .http .HttpServletRequest ;
3635import jakarta .servlet .http .HttpServletRequestWrapper ;
7574 * <p>Note that this is primarily an SPI to allow Spring Security
7675 * to align its pattern matching with the same pattern matching that would be
7776 * used in Spring MVC for a given request, in order to avoid security issues.
78- * Use of this introspector should be avoided for other purposes because it
79- * incurs the overhead of resolving the handler for a request.
8077 *
81- * <p>Alternative security filter solutions that also rely on
82- * {@link HandlerMappingIntrospector} should consider adding an additional
83- * {@link jakarta.servlet.Filter} that invokes
84- * {@link #setCache(HttpServletRequest)} and {@link #resetCache(ServletRequest, CachedResult)}
85- * before and after delegating to the rest of the chain. Such a Filter should
86- * process all dispatcher types and should be ordered ahead of security filters.
78+ * <p>Use of this component incurs the performance overhead of mapping the
79+ * request, and should not be repeated multiple times per request.
80+ * {@link #createCacheFilter()} exposes a Filter to cache the results.
81+ * Applications that rely on Spring Security don't need to deploy this Filter
82+ * since Spring Security doe that. However, other custom security layers, used
83+ * in place of Spring Security that use this component should deploy the cache
84+ * Filter with requirements described in the Javadoc for the method.
8785 *
8886 * @author Rossen Stoyanchev
8987 * @since 4.3.1
@@ -192,16 +190,9 @@ public List<HandlerMapping> getHandlerMappings() {
192190 */
193191 public Filter createCacheFilter () {
194192 return (request , response , chain ) -> {
195- HandlerMappingIntrospector .CachedResult previous = setCache ((HttpServletRequest ) request );
196- try {
197- chain .doFilter (request , response );
198- }
199- catch (Exception ex ) {
200- throw new ServletException ("HandlerMapping introspection failed" , ex );
201- }
202- finally {
203- resetCache (request , previous );
204- }
193+ CachedResult previous = setCache ((HttpServletRequest ) request );
194+ chain .doFilter (request , response );
195+ resetCache (request , previous );
205196 };
206197 }
207198
@@ -212,26 +203,39 @@ public Filter createCacheFilter() {
212203 * {@link #getCorsConfiguration(HttpServletRequest)} to avoid repeated lookups.
213204 * @param request the current request
214205 * @return the previous {@link CachedResult}, if there is one from a parent dispatch
215- * @throws ServletException thrown the lookup fails for any reason
216206 * @since 6.0.14
217207 */
218208 @ Nullable
219- private CachedResult setCache (HttpServletRequest request ) throws ServletException {
209+ private CachedResult setCache (HttpServletRequest request ) {
220210 CachedResult previous = (CachedResult ) request .getAttribute (CACHED_RESULT_ATTRIBUTE );
221211 if (previous == null || !previous .matches (request )) {
212+ HttpServletRequest wrapped = new AttributesPreservingRequest (request );
213+ CachedResult result ;
222214 try {
223- HttpServletRequest wrapped = new AttributesPreservingRequest ( request );
224- CachedResult result = doWithHandlerMapping (wrapped , false , (mapping , executionChain ) -> {
215+ // Try to get both in one lookup (with ignoringException=false)
216+ result = doWithHandlerMapping (wrapped , false , (mapping , executionChain ) -> {
225217 MatchableHandlerMapping matchableMapping = createMatchableHandlerMapping (mapping , wrapped );
226- CorsConfiguration corsConfig = getCorsConfiguration (wrapped , executionChain );
227- return new CachedResult (request , matchableMapping , corsConfig );
218+ CorsConfiguration corsConfig = getCorsConfiguration (executionChain , wrapped );
219+ return new CachedResult (request , matchableMapping , corsConfig , null , null );
228220 });
229- request .setAttribute (CACHED_RESULT_ATTRIBUTE ,
230- (result != null ? result : new CachedResult (request , null , null )));
231221 }
232- catch (Throwable ex ) {
233- throw new ServletException ("HandlerMapping introspection failed" , ex );
222+ catch (Exception ex ) {
223+ try {
224+ // Try CorsConfiguration at least with ignoreException=true
225+ AttributesPreservingRequest requestToUse = new AttributesPreservingRequest (request );
226+ result = doWithHandlerMapping (requestToUse , true , (mapping , executionChain ) -> {
227+ CorsConfiguration corsConfig = getCorsConfiguration (executionChain , wrapped );
228+ return new CachedResult (request , null , corsConfig , ex , null );
229+ });
230+ }
231+ catch (Exception ex2 ) {
232+ result = new CachedResult (request , null , null , ex , new IllegalStateException (ex2 ));
233+ }
234+ }
235+ if (result == null ) {
236+ result = new CachedResult (request , null , null , null , null );
234237 }
238+ request .setAttribute (CACHED_RESULT_ATTRIBUTE , result );
235239 }
236240 return previous ;
237241 }
@@ -256,7 +260,7 @@ private void resetCache(ServletRequest request, @Nullable CachedResult cachedRes
256260 */
257261 @ Nullable
258262 public MatchableHandlerMapping getMatchableHandlerMapping (HttpServletRequest request ) throws Exception {
259- CachedResult result = CachedResult .forRequest (request );
263+ CachedResult result = CachedResult .getResultFor (request );
260264 if (result != null ) {
261265 return result .getHandlerMapping ();
262266 }
@@ -284,7 +288,7 @@ private MatchableHandlerMapping createMatchableHandlerMapping(HandlerMapping map
284288 @ Override
285289 @ Nullable
286290 public CorsConfiguration getCorsConfiguration (HttpServletRequest request ) {
287- CachedResult result = CachedResult .forRequest (request );
291+ CachedResult result = CachedResult .getResultFor (request );
288292 if (result != null ) {
289293 return result .getCorsConfig ();
290294 }
@@ -293,16 +297,16 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
293297 boolean ignoreException = true ;
294298 AttributesPreservingRequest requestToUse = new AttributesPreservingRequest (request );
295299 return doWithHandlerMapping (requestToUse , ignoreException ,
296- (handlerMapping , executionChain ) -> getCorsConfiguration (requestToUse , executionChain ));
300+ (handlerMapping , executionChain ) -> getCorsConfiguration (executionChain , requestToUse ));
297301 }
298302 catch (Exception ex ) {
299- // HandlerMapping exceptions have been ignored. Some more basic error perhaps like request parsing
303+ // HandlerMapping exceptions are ignored. More basic error like parsing the request path.
300304 throw new IllegalStateException (ex );
301305 }
302306 }
303307
304308 @ Nullable
305- private static CorsConfiguration getCorsConfiguration (HttpServletRequest request , HandlerExecutionChain chain ) {
309+ private static CorsConfiguration getCorsConfiguration (HandlerExecutionChain chain , HttpServletRequest request ) {
306310 for (HandlerInterceptor interceptor : chain .getInterceptorList ()) {
307311 if (interceptor instanceof CorsConfigurationSource source ) {
308312 return source .getCorsConfiguration (request );
@@ -371,13 +375,22 @@ private static final class CachedResult {
371375 @ Nullable
372376 private final CorsConfiguration corsConfig ;
373377
378+ @ Nullable
379+ private final Exception failure ;
380+
381+ @ Nullable
382+ private final IllegalStateException corsConfigFailure ;
383+
374384 private CachedResult (HttpServletRequest request ,
375- @ Nullable MatchableHandlerMapping mapping , @ Nullable CorsConfiguration config ) {
385+ @ Nullable MatchableHandlerMapping mapping , @ Nullable CorsConfiguration config ,
386+ @ Nullable Exception failure , @ Nullable IllegalStateException corsConfigFailure ) {
376387
377388 this .dispatcherType = request .getDispatcherType ();
378389 this .requestURI = request .getRequestURI ();
379390 this .handlerMapping = mapping ;
380391 this .corsConfig = config ;
392+ this .failure = failure ;
393+ this .corsConfigFailure = corsConfigFailure ;
381394 }
382395
383396 public boolean matches (HttpServletRequest request ) {
@@ -386,12 +399,18 @@ public boolean matches(HttpServletRequest request) {
386399 }
387400
388401 @ Nullable
389- public MatchableHandlerMapping getHandlerMapping () {
402+ public MatchableHandlerMapping getHandlerMapping () throws Exception {
403+ if (this .failure != null ) {
404+ throw this .failure ;
405+ }
390406 return this .handlerMapping ;
391407 }
392408
393409 @ Nullable
394410 public CorsConfiguration getCorsConfig () {
411+ if (this .corsConfigFailure != null ) {
412+ throw this .corsConfigFailure ;
413+ }
395414 return this .corsConfig ;
396415 }
397416
@@ -405,11 +424,10 @@ public String toString() {
405424 * Return a {@link CachedResult} that matches the given request.
406425 */
407426 @ Nullable
408- public static CachedResult forRequest (HttpServletRequest request ) {
427+ public static CachedResult getResultFor (HttpServletRequest request ) {
409428 CachedResult result = (CachedResult ) request .getAttribute (CACHED_RESULT_ATTRIBUTE );
410429 return (result != null && result .matches (request ) ? result : null );
411430 }
412-
413431 }
414432
415433
0 commit comments