Expected Behavior

DefaultLoginPageGeneratingFilter supports manual path prefix, such as ${spring.mvc.servlet.path} prefix.

As the issue #14188 that I submitted, DefaultLoginPageGeneratingFilter doesn't work while ${spring.mvc.servlet.path} property is not '/'. due to DefaultLoginPageGeneratingFilter only process fixed url path: /login.

My current solution is to hack FormLoginConfigurer class, like bellow codes:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception
{
    // Management ENdpoints Base Path is '/__admin'
    String endpointsWebBasePath = AppConfigHolder.getManagementEndpointsBasePath();

    if(GeneralHelper.isStrEmpty(endpointsWebBasePath) || endpointsWebBasePath.equals("/"))
        http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests.anyRequest().permitAll()).build();
    else
    {
        // Spring MVC Servlet Path is '/aaa'
        String mvcServletPath = AppConfigHolder.getSpringMvcServletPath();
        // prefix == '/aaa'
        String prefix = (GeneralHelper.isStrNotEmpty(mvcServletPath) && !mvcServletPath.equals("/")) ? mvcServletPath : "";
        // managementBasePath == "/aaa/__admin/**"
        String managementBasePath = prefix + endpointsWebBasePath + "/**";

        http
        .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
            .requestMatchers(AntPathRequestMatcher.antMatcher(managementBasePath)).authenticated()
            .requestMatchers(AntPathRequestMatcher.antMatcher("/**")).permitAll())
        .csrf((csrf) -> csrf.disable())
        .httpBasic(Customizer.withDefaults())
        .formLogin(new Customizer<FormLoginConfigurer<HttpSecurity>>()
        {
            @Override
            public void customize(FormLoginConfigurer<HttpSecurity> cfg)
            {
                if(GeneralHelper.isStrEmpty(prefix))
                    return;

                // if prefix == '/aaa', then set login page to '/aaa/login'
                cfg.loginPage(prefix + DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL);

                try
                {
                    final String FIELD_NAME = "customLoginPage";
                    Field customLoginPage   = AbstractAuthenticationFilterConfigurer.class.getDeclaredField(FIELD_NAME);

                    // Because loginPage() set the field 'customLoginPage' of FormLoginConfigurer to true automatically, DefaultLoginPageGeneratingFilter would be ignored.  
                    // Cheat FormLoginConfigurer and reset it's 'customLoginPage' to false.
                    // Ugly, but work fine.
                    customLoginPage.setAccessible(true);
                    customLoginPage.set(cfg, false);
                }
                catch(Exception e)
                {
                    throw new RuntimeException("set actuator login page fail", e);
                }
            }
        });
    }

    return http.build();
}

Conclusion

FormLoginConfigurer provides another loginPage() like loginPage(String loginPage, boolean customLoginPage) to replace the hack code above, and then DefaultLoginPageGeneratingFilter would supports manual path prefix.

Comment From: jzheaux

Thanks for the suggestion, @ldcsaa.

Can you clarify what makes it important in your use case for the paths to be the same? Historically, most applications are satisfied to either use the default endpoint or install a custom login page, and I want to make sure I'm providing the right help.

Comment From: ldcsaa

For my case: I provide microservice framework to many users, most of users in tend to use default actuator endpoint. but a few of user applications customize their ${spring.mvc.servlet.path} property for some reason, cause default actuator endpoint don't work.

Comment From: jzheaux

That makes sense why you are using ${spring.mvc.servlet.path}, but I'm not clear on what that has to do with the login page.

Can you clarify why the login page must have the servlet path as well? It's an uncommon request; most applications (even ones that use ${spring.mvc.servlet.path}) either stick with the default /login endpoint or have a custom login page.

Comment From: ldcsaa

Yes, this is not a regular requirement. This problem only occurs when you customize the ${spring.mvc.servlet.path} property and want to use the default login page. The current problem is that when the ${spring.mvc.servlet.path} property is customized, the default login page cannot be used.

Comment From: jzheaux

I see, sorry for my earlier misunderstanding.

One workaround for you is to also add server.servlet.register-default-servlet=true so that traffic not prefixed by the servlet path is also allowed. For that to work, you must also use requestMatchers(RequestMatcher) instead of requestMatchers(String) when specifying authorization rules. Can you confirm that this works for you?

Either way, I think this is worth considering, though it's a larger effort that I think should be taken up across the project. Endpoints like /login, /oauth/**, and /saml2/** all fail under this arrangement, so I'd want those to work too. I'll leave this ticket open to take that under consideration.