spring-projects / spring-projects/spring-security
The browser accesses a nonexistent resource during login, leading to /error after login
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.6k
- Forks
- 6.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 52
Description
Describe the bug
When the user is not logged in and the browser accesses the protected resource, the browser will jump to the login page. If the browser automatically visits a nonexistent resource (e.g., favicon.ico) on the login page, the browser will automatically navigate to the /error path after login.
To Reproduce
Create a Spring Boot Project with dependencies 'org.springframework.boot:spring-boot-starter-web' and 'org.springframework.boot:spring-boot-starter-security'.
In the security configuration, a form login is provided and requires login to access all paths except the /favicon.ico path, which allows an anonymous login.
The most important thing is that there is no favicon.ico in this project.
Then visit 127.0.0.1:8080/hello or other secured resources in your browser. The browser will be redirect to /login. Enter username and password(admin:admin) and click the login button. The browser will be redirected to /error instead of /hello.
Expected behavior
After logged in, the browser redirects to the url before /login or home page instead of /error.
Sample
Here's the contents of the application.yml file. It provides a user with a username and password of admin.
spring:
security:
user:
name: admin
password: admin
Here are the security configuration classes. It's configured for form login. It allows anonymous access to /favicon.ico and requires a login for all other paths.
package example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(Customizer.withDefaults());
http.authorizeRequests(authorize -> authorize
.requestMatchers("/favicon.ico").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
A controller.
package org.anonym.authorizationserver.conntroller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "hello";
}
}
Analyse
org.springframework.security.web.savedrequest.HttpSessionRequestCache#saveRequest(HttpServletRequest, HttpServletResponse) method is the key to this problem. When an anonymous user accesses a protected resource, the backend will store the path to the protected resource in the RequestCache and instruct the browser to navigate to the /login path. Some request paths the RequestCache will not save are /favicon.ico requests, XHR requests, JSON requests, and file upload requests.
When the browser navigates to /login, it will automatically request /favicon.ico for a website icon that doesn't exist in the application. The application then forwards the request to the /error path. Since the /error path requires a login to access it, the /error path is stored in the RequestCache instead of the pre-login path. When the user logs in, the application fetches the saved /error path from the RequestCache and tells the browser to jump to it.
Improvement
The value of the Sec-Fetch-Dest header for active requests initiated by the browser is document, and the Sec-Fetch-Dest header for non-active requests is not document. Also, the dispatcherType value forwarded to the /error path is ERROR. The above two conditions can be used to determine whether the request path should be saved.
The change to HttpSessionRequestCache is in the public void saveRequest(HttpServletRequest, HttpServletResponse) method.
package org.springframework.security.web.savedrequest;
// other code...
public class HttpSessionRequestCache implements RequestCache {
// other code...
@Override
public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
boolean activeRequest = "document".equals(request.getHeader("Sec-Fetch-Dest"));
boolean dispatchToError = DispatcherType.ERROR.equals(request.getDispatcherType());
if ((!activeRequest && dispatchToError) || !this.requestMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger
.trace(LogMessage.format("Did not save request since it did not match [%s]", this.requestMatcher));
}
return;
}
// other code...
}
// other code...
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the login flow from the issue using the sample Spring Boot configuration, then inspect HttpSessionRequestCache#saveRequest(HttpServletRequest, HttpServletResponse). Check the existing RequestCache tests and add coverage for the missing favicon/error-dispatch case; done means the original protected URL or home page is restored after login rather than /error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100