Home > Back-end >  Spring Boot Thymeleaf custom login isn't redirecting properly
Spring Boot Thymeleaf custom login isn't redirecting properly

Time:11-22

I have a Spring Boot Thymeleaf project that I'm trying to introduce a custom login page in following https://www.baeldung.com/spring-security-login. When I browse to the application root it redirects to http://localhost:8080/login.html but instead of the login form I get the error:

The page isn’t redirecting properly. Firefox has detected that the server is redirecting the request for this address in a way that will never complete.

Before adding the login customization I had a generic login popup which worked. It's something in the additions I made with customizing login. My WebSecurityConfig:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http
            .authorizeRequests()
            .antMatchers("/login*", "/", "/search", "/browse", "/recipes/**", "/tags/**", "/edit/**", "/delete/**")
            .hasRole("ADMIN")
            .antMatchers("/login*", "/", "/search", "/browse", "/recipes/**", "/tags/**")
            .hasRole("USER")
            .anyRequest()
            .authenticated()
            .and()
            .formLogin().loginPage("/login.html")
            .defaultSuccessUrl("/", true)
            .failureUrl("/login.html?error=true");
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withUsername("foote").password(passwordEncoder().encode("userpassword")).roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin").password(passwordEncoder().encode("adminpassword"))
                .roles("USER", "ADMIN").build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}

My login.html (as per the article):

<html>
<head></head>
<body>
    <h1>Login</h1>
    <form name='f' action="login" method='POST'>
        <table>
            <tr>
                <td>User:</td>
                <td><input type='text' name='username' value=''></td>
            </tr>
            <tr>
                <td>Password:</td>
                <td><input type='password' name='password' /></td>
            </tr>
            <tr>
                <td><input name="submit" type="submit" value="submit" /></td>
            </tr>
        </table>
    </form>
</body>
</html>

The application log with "logging.level.org.springframework.security=DEBUG" in application.properties:

2022-11-19T10:07:06.116 [http-nio-8080-exec-1] [DEBUG] [o.s.security.web.FilterChainProxy] Securing GET /
2022-11-19T10:07:06.119 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Set SecurityContextHolder to empty SecurityContext
2022-11-19T10:07:06.121 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.a.AnonymousAuthenticationFilter] Set SecurityContextHolder to anonymous SecurityContext
2022-11-19T10:07:06.127 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.a.i.FilterSecurityInterceptor] Failed to authorize filter invocation [GET /] with attributes [hasRole('ROLE_USER')]
2022-11-19T10:07:06.135 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.s.HttpSessionRequestCache] Saved request http://localhost:8080/ to session
2022-11-19T10:07:06.135 [http-nio-8080-exec-1] [DEBUG] [o.s.s.web.DefaultRedirectStrategy] Redirecting to http://localhost:8080/login.html
2022-11-19T10:07:06.136 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.HttpSessionSecurityContextRepository] Did not store empty SecurityContext
2022-11-19T10:07:06.137 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.HttpSessionSecurityContextRepository] Did not store empty SecurityContext
2022-11-19T10:07:06.137 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Cleared SecurityContextHolder to complete request
2022-11-19T10:07:06.169 [http-nio-8080-exec-2] [DEBUG] [o.s.security.web.FilterChainProxy] Securing GET /login.html
2022-11-19T10:07:06.169 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Set SecurityContextHolder to empty SecurityContext
2022-11-19T10:07:06.170 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.a.AnonymousAuthenticationFilter] Set SecurityContextHolder to anonymous SecurityContext
2022-11-19T10:07:06.170 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.a.i.FilterSecurityInterceptor] Failed to authorize filter invocation [GET /login.html] with attributes [hasRole('ROLE_USER')]
2022-11-19T10:07:06.171 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.s.HttpSessionRequestCache] Saved request http://localhost:8080/login.html to session
2022-11-19T10:07:06.171 [http-nio-8080-exec-2] [DEBUG] [o.s.s.web.DefaultRedirectStrategy] Redirecting to http://localhost:8080/login.html
2022-11-19T10:07:06.171 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.c.HttpSessionSecurityContextRepository] Did not store empty SecurityContext
2022-11-19T10:07:06.171 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.c.HttpSessionSecurityContextRepository] Did not store empty SecurityContext
2022-11-19T10:07:06.171 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Cleared SecurityContextHolder to complete request
2022-11-19T10:07:06.189 [http-nio-8080-exec-3] [DEBUG] [o.s.security.web.FilterChainProxy] Securing GET /login.html
2022-11-19T10:07:06.190 [http-nio-8080-exec-3] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Set SecurityContextHolder to empty SecurityContext
...

The last action, securing /login.html, repeats a dozen times before the error, cyclic I think.

What am I missing or doing wrong?

UPDATE: Not getting any response; is there more info I can add?

CodePudding user response:

So I think I've got this working. Turns out there was a few things missing.

In WebSecurityConfig I said use a custom login page but I didn't provide access to it. So I added permitAll() after declaring loginPage. And I provided access to required resources prior to login (first antMatcher). I was also missing some patterns in my roles' antMatchers. The final result:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig {

    /**
     * Configures HTTP security.
     *
     * @param http {@link HttpSecurity}
     * @return {@link SecurityFilterChain}
     * @throws Exception when the internet falls over.
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http
            .authorizeRequests()
            .antMatchers("/login*", "/resources/**", "/error")
            .permitAll()
            .antMatchers("/", "/login*", "/error", "/search", "/browse", "/recipes/**", "/tags/**",
                    "/resources/**", "/add", "/create", "/uploadImage", "/edit/**", "/delete/**")
            .hasRole("ADMIN")
            .antMatchers("/", "/login*", "/error", "/search", "/browse", "/recipes/**", "/tags/**",
                    "/resources/**")
            .hasRole("USER")
            .anyRequest()
            .authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .permitAll()
            .defaultSuccessUrl("/", true)
            .failureUrl("/login?error=true");
        return http.build();
    }

    /**
     * Use password encoding.
     *
     * @return {@link PasswordEncoder}
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * Username and password and roles.
     *
     * @return {@link InMemoryUserDetailsManager}
     */
    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withUsername("user").password(passwordEncoder().encode("password")).roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin").password(passwordEncoder().encode("password"))
                .roles("USER", "ADMIN").build();
        return new InMemoryUserDetailsManager(user, admin);
    }

    /**
     * Specify resources to allow without credentials.
     *
     * @return {@link WebSecurityCustomizer}
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowUrlEncodedPercent(true);
        return web -> web.httpFirewall(firewall)
                .ignoring()
                .antMatchers("/styles/**", "/js/**", "/images/**", "/fonts/**", "/resources/**");
    }
}
  • Related