Describe the bug If a static resource in a page is the last blocked URL, SavedRequestAwareAuthenticationSuccessHandler redirects to the static resource after successfully submitting the login form, even if this static resource is a not a page, such as css files.

The JavaDoc says: This class is responsible for performing the redirect to the original URL if appropriate. But a css file shouldn't be appropriate imo. SavedRequestAwareAuthenticationSuccessHandler could avoid this kind of redirects if it would filter on HTTP requests that have text/html (or something similar) as HTTP Accept header.

(I hit on this problem while trying https://spring.io/blog/2016/11/21/the-joy-of-mustache-server-side-templates-for-the-jvm with some minor adjustments, such as bootstrap. After submitting the login form, it kept forwarding to urls like http://localhost:8080/bootstrap.min.css?continue, even though the previous page URL was http://localhost:8080/.)

To Reproduce Create a default Spring Boot application version 3.0.2 using Spring Initializr, with Kotlin and Spring Web and Spring Security.

Create a Kotlin file with the following content:

@Configuration
class SecConfig {
    @Bean
    fun filterChain(http: HttpSecurity, repository: SecurityContextRepository): SecurityFilterChain {
        http.authorizeHttpRequests()
//            .requestMatchers("/login", "/error", "/webjars/**", "/**.css").permitAll()
            .requestMatchers("/login", "/error").permitAll()
            .requestMatchers("/**").authenticated().and().exceptionHandling()
            .authenticationEntryPoint(LoginUrlAuthenticationEntryPoint("/login"))
            .and().csrf().disable()
        return http.build()
    }

    @Bean
    fun sessionRepository(): HttpSessionSecurityContextRepository {
        return HttpSessionSecurityContextRepository()
    }
}


@Controller
class MyController(
    private val repository: SecurityContextRepository
) {
    private val handler = SavedRequestAwareAuthenticationSuccessHandler()

    @GetMapping("/")
    @ResponseBody
    fun home() = "Home..."

    @GetMapping("/login")
    @ResponseBody
    fun form() = loginPage

    @GetMapping("/style.css", produces = ["text/css"])
    @ResponseBody
    fun style() = "body { background-color: green }"

    @PostMapping("/login")
    fun authenticate(@RequestParam map: Map<String, String>, req: HttpServletRequest, res: HttpServletResponse) {
        val authentication = UsernamePasswordAuthenticationToken(
            map["username"], "N/A",
            AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")
        )
        SecurityContextHolder.getContext().authentication = authentication
        repository.saveContext(SecurityContextHolder.getContext(), req, res)

        handler.onAuthenticationSuccess(req, res, authentication)
    }
}

val loginPage = """
    <!DOCTYPE html>
    <html>
    <head>
        <link rel="stylesheet" type="text/css" href="/style.css" />
    </head>
    <body>
        <h1>Login</h1>
        <form action="/login" method="post">
            Username: <input type="text" name="username" />
            Password: <input type="password" name="password" />

            <button type="submit">Submit</button>
        </form>
    </div>
    </body>
    </html>
""".trimIndent()

Then start the app and go to http://localhost:8080/ which will redirect to /login. Add a random username and pass, and submit the form. It will then unexpectedly redirect to http://localhost:8080/style.css?continue instead of http://localhost:8080/.

The debug logs:

2023-02-10T19:39:56.011+01:00 DEBUG 17344 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing GET /
2023-02-10T19:39:56.020+01:00 DEBUG 17344 --- [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2023-02-10T19:39:56.047+01:00 DEBUG 17344 --- [nio-8080-exec-1] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8080/?continue to session
2023-02-10T19:39:56.047+01:00 DEBUG 17344 --- [nio-8080-exec-1] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/login
2023-02-10T19:39:56.055+01:00 DEBUG 17344 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Securing GET /login
2023-02-10T19:39:56.056+01:00 DEBUG 17344 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Secured GET /login
2023-02-10T19:39:56.073+01:00 DEBUG 17344 --- [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2023-02-10T19:39:56.099+01:00 DEBUG 17344 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy        : Securing GET /style.css
2023-02-10T19:39:56.100+01:00 DEBUG 17344 --- [nio-8080-exec-3] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2023-02-10T19:39:56.101+01:00 DEBUG 17344 --- [nio-8080-exec-3] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8080/style.css?continue to session
2023-02-10T19:39:56.101+01:00 DEBUG 17344 --- [nio-8080-exec-3] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/login
2023-02-10T19:39:56.104+01:00 DEBUG 17344 --- [nio-8080-exec-4] o.s.security.web.FilterChainProxy        : Securing GET /login
2023-02-10T19:39:56.104+01:00 DEBUG 17344 --- [nio-8080-exec-4] o.s.security.web.FilterChainProxy        : Secured GET /login
2023-02-10T19:39:56.105+01:00 DEBUG 17344 --- [nio-8080-exec-4] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2023-02-10T19:39:56.109+01:00 DEBUG 17344 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : Securing GET /favicon.ico
2023-02-10T19:39:56.112+01:00 DEBUG 17344 --- [nio-8080-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2023-02-10T19:39:56.112+01:00 DEBUG 17344 --- [nio-8080-exec-5] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/login

Although I'm not sure why it didn't redirect to /favicon.ico, but did to the css file.

Expected behavior A redirect to http://localhost:8080/ or http://localhost:8080/?continue instead of http://localhost:8080/style.css

Comment From: twosom

Hi @devxzero 🤓 Can i take this issue?

Comment From: devxzero

Hi @devxzero 🤓 Can i take this issue?

@twosom Sure, go ahead

Comment From: marcusdacoregio

Hi @devxzero, thanks for the report.

I'd like to clarify one thing:

Your /login page is public, it doesn't require any authority to access it. Why does GET /style.css need to be authenticated? I believe that you should also make /style.css public.

Comment From: devxzero

@marcusdacoregio You're right. /style.css should be public in the end-product. But for this bug problem demo, when it's public, the problem won't show up. So the problem is that even though the browser will request /style.css as Accept: text/css, it is threated as a normal page by SavedRequestAwareAuthenticationSuccessHandler by redirecting to it after login, which is conflicting the redirect to the original URL if appropriate as specified in the JavaDoc. This problem indeed wouldn't surface if the developer would correctly permit all of such public resources (and the browser wouldn't try to request unexpected resources, such as icons), but when it's forgotten by the developer (a bug), then this other problem surfaces. (Although I don't know why it didn't do the forward for favicon.ico. Apple browers also have other icons that it can bruteforcely try out.) But there may be reasons for a developer to decide that a forward to /style.css or another resource (such as a protected but downloadable file), ís appropriate. In that case, this issue is not a bug, but a misunderstanding of me about the word "appropriate" in this context. So maybe it's not a bug after all.

Comment From: marcusdacoregio

I am not sure if we should add a matcher to exclude CSS files now, maybe there are use cases when that could be expected. I'll close this for now but we will keep an eye if more folks face this problem then we might consider adding it.

Thank you again for the report.