30
30
31
31
import jakarta .servlet .DispatcherType ;
32
32
import jakarta .servlet .Filter ;
33
- import jakarta .servlet .ServletException ;
34
33
import jakarta .servlet .ServletRequest ;
35
34
import jakarta .servlet .http .HttpServletRequest ;
36
35
import jakarta .servlet .http .HttpServletRequestWrapper ;
75
74
* <p>Note that this is primarily an SPI to allow Spring Security
76
75
* to align its pattern matching with the same pattern matching that would be
77
76
* 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.
80
77
*
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.
87
85
*
88
86
* @author Rossen Stoyanchev
89
87
* @since 4.3.1
@@ -192,16 +190,9 @@ public List<HandlerMapping> getHandlerMappings() {
192
190
*/
193
191
public Filter createCacheFilter () {
194
192
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 );
205
196
};
206
197
}
207
198
@@ -212,26 +203,39 @@ public Filter createCacheFilter() {
212
203
* {@link #getCorsConfiguration(HttpServletRequest)} to avoid repeated lookups.
213
204
* @param request the current request
214
205
* @return the previous {@link CachedResult}, if there is one from a parent dispatch
215
- * @throws ServletException thrown the lookup fails for any reason
216
206
* @since 6.0.14
217
207
*/
218
208
@ Nullable
219
- private CachedResult setCache (HttpServletRequest request ) throws ServletException {
209
+ private CachedResult setCache (HttpServletRequest request ) {
220
210
CachedResult previous = (CachedResult ) request .getAttribute (CACHED_RESULT_ATTRIBUTE );
221
211
if (previous == null || !previous .matches (request )) {
212
+ HttpServletRequest wrapped = new AttributesPreservingRequest (request );
213
+ CachedResult result ;
222
214
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 ) -> {
225
217
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 );
228
220
});
229
- request .setAttribute (CACHED_RESULT_ATTRIBUTE ,
230
- (result != null ? result : new CachedResult (request , null , null )));
231
221
}
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 );
234
237
}
238
+ request .setAttribute (CACHED_RESULT_ATTRIBUTE , result );
235
239
}
236
240
return previous ;
237
241
}
@@ -256,7 +260,7 @@ private void resetCache(ServletRequest request, @Nullable CachedResult cachedRes
256
260
*/
257
261
@ Nullable
258
262
public MatchableHandlerMapping getMatchableHandlerMapping (HttpServletRequest request ) throws Exception {
259
- CachedResult result = CachedResult .forRequest (request );
263
+ CachedResult result = CachedResult .getResultFor (request );
260
264
if (result != null ) {
261
265
return result .getHandlerMapping ();
262
266
}
@@ -284,7 +288,7 @@ private MatchableHandlerMapping createMatchableHandlerMapping(HandlerMapping map
284
288
@ Override
285
289
@ Nullable
286
290
public CorsConfiguration getCorsConfiguration (HttpServletRequest request ) {
287
- CachedResult result = CachedResult .forRequest (request );
291
+ CachedResult result = CachedResult .getResultFor (request );
288
292
if (result != null ) {
289
293
return result .getCorsConfig ();
290
294
}
@@ -293,16 +297,16 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
293
297
boolean ignoreException = true ;
294
298
AttributesPreservingRequest requestToUse = new AttributesPreservingRequest (request );
295
299
return doWithHandlerMapping (requestToUse , ignoreException ,
296
- (handlerMapping , executionChain ) -> getCorsConfiguration (requestToUse , executionChain ));
300
+ (handlerMapping , executionChain ) -> getCorsConfiguration (executionChain , requestToUse ));
297
301
}
298
302
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.
300
304
throw new IllegalStateException (ex );
301
305
}
302
306
}
303
307
304
308
@ Nullable
305
- private static CorsConfiguration getCorsConfiguration (HttpServletRequest request , HandlerExecutionChain chain ) {
309
+ private static CorsConfiguration getCorsConfiguration (HandlerExecutionChain chain , HttpServletRequest request ) {
306
310
for (HandlerInterceptor interceptor : chain .getInterceptorList ()) {
307
311
if (interceptor instanceof CorsConfigurationSource source ) {
308
312
return source .getCorsConfiguration (request );
@@ -371,13 +375,22 @@ private static final class CachedResult {
371
375
@ Nullable
372
376
private final CorsConfiguration corsConfig ;
373
377
378
+ @ Nullable
379
+ private final Exception failure ;
380
+
381
+ @ Nullable
382
+ private final IllegalStateException corsConfigFailure ;
383
+
374
384
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 ) {
376
387
377
388
this .dispatcherType = request .getDispatcherType ();
378
389
this .requestURI = request .getRequestURI ();
379
390
this .handlerMapping = mapping ;
380
391
this .corsConfig = config ;
392
+ this .failure = failure ;
393
+ this .corsConfigFailure = corsConfigFailure ;
381
394
}
382
395
383
396
public boolean matches (HttpServletRequest request ) {
@@ -386,12 +399,18 @@ public boolean matches(HttpServletRequest request) {
386
399
}
387
400
388
401
@ Nullable
389
- public MatchableHandlerMapping getHandlerMapping () {
402
+ public MatchableHandlerMapping getHandlerMapping () throws Exception {
403
+ if (this .failure != null ) {
404
+ throw this .failure ;
405
+ }
390
406
return this .handlerMapping ;
391
407
}
392
408
393
409
@ Nullable
394
410
public CorsConfiguration getCorsConfig () {
411
+ if (this .corsConfigFailure != null ) {
412
+ throw this .corsConfigFailure ;
413
+ }
395
414
return this .corsConfig ;
396
415
}
397
416
@@ -405,11 +424,10 @@ public String toString() {
405
424
* Return a {@link CachedResult} that matches the given request.
406
425
*/
407
426
@ Nullable
408
- public static CachedResult forRequest (HttpServletRequest request ) {
427
+ public static CachedResult getResultFor (HttpServletRequest request ) {
409
428
CachedResult result = (CachedResult ) request .getAttribute (CACHED_RESULT_ATTRIBUTE );
410
429
return (result != null && result .matches (request ) ? result : null );
411
430
}
412
-
413
431
}
414
432
415
433
0 commit comments