Describe the bug Since Spring Security 2.6 throwing an AuthenticationException inside an AuthenticationProvider causes the public assets on the error page to be empty and delivered with a 401 status code.
Also occurs in 2.7.0-SNAPSHOT.
To Reproduce
* Create a custom error page (e.g. /error.html) with a static external css
* Configure error page and assets to be public (e.g. .antMatchers("/public/**", "/error").permitAll())
* Configure a custom AuthenticationProvider that throws an Exception during authenticate(...)
* Configure ExceptionEntryPoint so that the error page is displayed
* Assets will be empty and delivered with 401 even though .antMatchers("/public/**") is set.
Expected behavior Public error page assets should be delivered normally.
Workarounds are to downgrade to Spring Boot 2.5.8 or to explicitly add the static public assets to the ignoring AntMatchers under webSecurity.ignoring().antMatchers(...)
Sample https://github.com/duoduobingbing/springsecurityauthenticationbug-spring-boot-2.6
Comment From: sjohnr
Hi @duoduobingbing. Thank you so much for providing a sample! This is an interesting issue for sure.
I have dug into it a bit, and also discussed with @jzheaux, and I submitted duoduobingbing/springsecurityauthenticationbug#1 for your review. It demonstrates a variation on your sample that would be a more idiomatic way of developing your example authentication filter. Here's what I found when debugging your sample:
A change in 5.6 (commit b8d51725c78174141407725587ff0322ff158192) introduced improvements to how SecurityContext is used in the framework, which resulted in a behavior change that you are reporting here.
The idiomatic way to set a SecurityContext is as follows:
Authentication authenticationResult = this.authenticationManager.authenticate(token);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authenticationResult);
SecurityContextHolder.setContext(securityContext);
Very important in this snippet is the fact that the existing SecurityContext is never updated, only a fresh instance is created, populated with an Authentication and set on the SecurityContextHolder. Additionally, it's important that any authentication filter use the AuthenticationManager.
While the above snippet of code could be performed anywhere instead of using an AuthenticationManager, the location within the filter chain for when this is correct is critical. In your case, doing so prior to the FilterSecurityInterceptor triggered a very old condition in Spring Security, where the AuthenticationManager was invoked as a side-effect of authorization:
https://github.com/spring-projects/spring-security/blob/4f3072b3d9c6bf07b00b8ec050fff81123594910/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java#L315
This old code was being called in your sample in a situation where it shouldn't be. We don't actually want that to happen, and the PR I submitted shows how to avoid that happening. I've also added a custom AbstractHttpConfigurer to demonstrate how to obtain an AuthorizationManager when you need one in a filter. Again, this isn't the only way to do what you were demonstrating, but is a very nice way to do so.
At this time, I don't believe this is a bug but instead an issue with your sample that exercised a behavior change, so I'm going to close this issue. However, feel free to comment on this ticket and we can continue the conversation if you have questions.
Comment From: duoduobingbing
Thanks for looking into it!
Huge thanks for looking so thoroughly into the issue and for taking the time to enhance my sample to illustrate your findings.
I guess this was a case of "this shouldn't have worked in this first place", so I'm fine with having this closed as NOTABUG.
Comment From: sjohnr
😆 Yeah, no problem. It definitely seems like that is the case. But it's good for all of us that you pointed it out, in case there's an issue there. And hopefully this helps others if they stumble upon similar behavior changes. The point of this (for me at least) is that sometimes there is a right way and a wrong way to do certain things, and it's not always obvious, so better to be safe than sorry and share your findings in case it's an issue. Thanks for doing that!