4.1 KiB
2. Who resolves the CorsConfigurationSource
Prev: 1. Two layers, one word · Next: 3. The three identical 403s
There are two lookups involved in getting a CORS configuration into the filter chain, and they disagree about what they are looking for. One searches by type. The other searches by name.
Lookup 1: should the CORS configurer run at all?
HttpSecurityConfiguration.applyCorsIfAvailable(HttpSecurity), disassembled from
spring-security-config 7.1.1:
4: ldc // class org/springframework/web/cors/UrlBasedCorsConfigurationSource
6: invokeinterface // ApplicationContext.getBeanNamesForType:(Ljava/lang/Class;)[Ljava/lang/String;
11: arraylength
12: ifle 23
19: invokevirtual // HttpSecurity.cors:(Customizer)HttpSecurity
23: return
By type, and the test is ifle — "branch if less than or equal to zero". One bean is
enough. So is five.
The reference documentation says: "If you have more than one
CorsConfigurationSourcebean, Spring Security won't automatically configure CORS support for you, because it cannot decide which one to use." That is not what 7.1.1 does.docs/output/06-two-sources.txthas two such beans, CORS configured, and one of them serving traffic.
Lookup 2: which source does the configurer use?
CorsConfigurer.getCorsConfigurationSource(ApplicationContext):
7: ldc // String corsConfigurationSource
9: invokeinterface // ApplicationContext.containsBeanDefinition:(Ljava/lang/String;)Z
20: ldc // String corsConfigurationSource
24: invokeinterface // ApplicationContext.getBean:(String,Class)Object
34: invokestatic // MvcCorsFilter.getMvcCorsConfigurationSource:(ApplicationContext)CorsConfigurationSource
By name. The literal string corsConfigurationSource. If no bean definition carries that name,
it falls through to Spring MVC's registrations. (There is a similar name check first, for a
CorsFilter bean named corsFilter.)
The gap between them
Name a UrlBasedCorsConfigurationSource bean anything other than corsConfigurationSource and
you land between the two lookups: CORS is switched on by the type lookup, and the
configuration you wrote is ignored by the name lookup.
That is the misnamed profile, and it produces the worst diagnostic in this whole subject:
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
...
Two hundred. No Access-Control-Allow-Origin. The browser blocks the request and reports a CORS
error; your access log shows a successful OPTIONS; nothing anywhere is red.
Why 200 rather than the 401 from chapter 1? Because CorsFilter is now in the chain, and its
if (!isValid || isPreFlightRequest(request)) return; fires on the second clause. The request
never reaches AuthorizationFilter. The only trace is one DEBUG line:
o.s.web.cors.DefaultCorsProcessor : Skip: no CORS configuration has been provided
Full transcript: docs/output/05-misnamed-bean.txt.
What is in the context that you did not put there
/diag/cors-sources on a stock Boot web application:
{ "corsConfigurationSourceBeans": { "mvcHandlerMappingIntrospector": "HandlerMappingIntrospector" } }
HandlerMappingIntrospector implements CorsConfigurationSource. It is always there, it is not
a UrlBasedCorsConfigurationSource, and it is the object the MVC fallback returns. That is why
the fallback path never throws in a normal application — and why the failure is silent
rather than loud.
Rules that follow
- Name the bean
corsConfigurationSource. Exactly that. - If you want several, pass them per chain with
.cors(c -> c.configurationSource(..)), which bypasses both lookups. NoSuchBeanDefinitionException: Failed to find a bean that implements \CorsConfigurationSource`` names three fixes and does not mention the fourth one, which is usually the right one: rename your bean.
Prev: 1. Two layers, one word · Next: 3. The three identical 403s