Describe the bug
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter doesn't work while ${spring.mvc.servlet.path} property is not '/'. due to DefaultLoginPageGeneratingFilter only process fixed url path: /login.
/* DefaultLoginPageGeneratingFilter codes */
public DefaultLoginPageGeneratingFilter(UsernamePasswordAuthenticationFilter authFilter) {
// DEFAULT_LOGIN_PAGE_URL == "/login"
this.loginPageUrl = DEFAULT_LOGIN_PAGE_URL;
this.logoutSuccessUrl = DEFAULT_LOGIN_PAGE_URL + "?logout";
this.failureUrl = DEFAULT_LOGIN_PAGE_URL + "?" + ERROR_PARAMETER_NAME;
if (authFilter != null) {
initAuthFilter(authFilter);
}
}
// ...
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
boolean loginError = isErrorPage(request);
boolean logoutSuccess = isLogoutSuccess(request);
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
// !!!!!!
// Never go into here, while ${spring.mvc.servlet.path} property is not '/'
// Because '/${spring.mvc.servlet.path}/login' not match '/login'
// !!!!!!
String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
return;
}
chain.doFilter(request, response);
}
To Reproduce
For example: the codes below would return 404 ('http://localhost:9018/login' page not found) when visit 'http://localhost:9018/bbb/__admin'
## spring config
spring:
mvc:
servlet:
load-on-startup: 1
# path is not '/'
path: /bbb
management:
endpoints:
enabled-by-default: true
jmx.exposure.exclude: "*"
# management endpoints base-path
web.base-path: /__admin
web.exposure.include: "*"
// Java Configuration
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception
{
String endpointsWebBasePath = AppConfigHolder.getManagementEndpointsBasePath();
if(GeneralHelper.isStrEmpty(endpointsWebBasePath) || endpointsWebBasePath.equals("/"))
http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests.anyRequest().permitAll());
else
{
// MVC Servlet Path
String mvcServletPath = AppConfigHolder.getSpringMvcServletPath();
String prefix = (GeneralHelper.isStrNotEmpty(mvcServletPath) && !mvcServletPath.equals("/")) ? mvcServletPath : "";
// managementBasePath = "/bbb/__admin/**"
String managementBasePath = prefix + endpointsWebBasePath + "/**";
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
// '/bbb/__admin/**' require login
.requestMatchers(AntPathRequestMatcher.antMatcher(managementBasePath)).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/**")).permitAll())
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
}
http.csrf((csrf) -> csrf.disable());
return http.build();
}
Expected behavior
DefaultLoginPageGeneratingFilter can work regardless of ${spring.mvc.servlet.path} property
Comment From: sjohnr
@ldcsaa thanks for the report.
Please note that spring.mvc.servlet.path is a Spring Boot property that Spring Security is unaware of. In addition, because Spring Security operates at the servlet filter level, Spring MVC and the DispatcherServlet that ultimately handles requests is not yet in scope, so Spring Security does not have any direct knowledge of whether you intend your application to handle URLs on / or not. (This is a generalization, as there are some special circumstances where direct integration with Spring MVC is required.)
Because you have customized your application, you must customize Spring Security to match your intent, as the defaults no longer work for you. The easiest (and recommended) update is to specify a custom loginPage(). You'll likely want to specify the loginProcessingUrl() as well.
If you have further questions, please feel free to ask a question on StackOverflow and I'll be happy to take a look. I'm going to close this issue, as I don't believe this is a bug in Spring Security, and is a case of mis-configuration.