Search code examples
javaspringspring-bootspring-mvcspring-security

Can't figure out why my loginController can't authenticate the user. Spring Security driving me nuts


Made a lot of edits. Didn't set springsecuritycontext earlier. This is the error I'm getting:

2024-07-05T02:37:01.443-04:00 TRACE 46690 --- [fitBuddyApp] [nio-8080-exec-5] estMatcherDelegatingAuthorizationManager : Checking authorization on POST /login_success using AuthorityAuthorizationManager[authorities=[USER]]
2024-07-05T02:37:01.443-04:00 TRACE 46690 --- [fitBuddyApp] [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Did not find SecurityContext in HttpSession 78D83992116FD8443A13A27376BEA891 using the SPRING_SECURITY_CONTEXT session attribute
2024-07-05T02:37:01.444-04:00 TRACE 46690 --- [fitBuddyApp] [nio-8080-exec-5] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-05T02:37:01.444-04:00 TRACE 46690 --- [fitBuddyApp] [nio-8080-exec-5] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-05T02:37:01.444-04:00 TRACE 46690 --- [fitBuddyApp] [nio-8080-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=78D83992116FD8443A13A27376BEA891], Granted Authorities=[ROLE_ANONYMOUS]]
2024-07-05T02:37:01.444-04:00 TRACE 46690 --- [fitBuddyApp] [nio-8080-exec-5] o.s.s.w.a.ExceptionTranslationFilter     : Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=78D83992116FD8443A13A27376BEA891], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is denied

org.springframework.security.access.AccessDeniedException: Access Denied

This is my UserDetails via CustomUserDetails

public class CustomUserDetails implements UserDetails {

    private User user;
    private int id;

    private String username;

    Role user_role;

    private String email;

    @JsonIgnore
    private String password;

    private Collection<? extends GrantedAuthority> authorities;

    public CustomUserDetails(User user) {

    }


    public CustomUserDetails(int id, String getusername, String role, String password, String email, List<GrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.user_role = Role.valueOf("USER");
        this.password = password;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRoles().toString());
        return Arrays.asList(authority);
    }

    public String getPassword() {
        return user.getPassword();
    }

    public String getUsername() {
        return user.getusername();
    }

    public boolean isAccountNonExpired() {
        return true;
    }

    public boolean isAccountNonLocked() {
        return true;
    }

    public boolean isCredentialsNonExpired() {
        return true;
    }

    public boolean isEnabled() {
        return true;
    }

This is my CustomUserDetailsService.

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        return new org.springframework.security.core.userdetails.User(user.getusername(), user.getPassword(), getAuthorities(user));
    }

    private Collection<? extends GrantedAuthority> getAuthorities(User user) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        // Example: Fetching user roles and converting them to GrantedAuthority
        user.getRoles().forEach(role -> {
            authorities.add(new SimpleGrantedAuthority("USER"));
        });
        return authorities;
    }

This is my loginController. I'm trying to implement securityContext.

@Controller public class loginController {

private final AuthenticationManager authenticationManager;

private final SecurityContextHolderStrategy securityContextHolderStrategy =
        SecurityContextHolder.getContextHolderStrategy();

private final SecurityContextRepository securityContextRepository =
        new HttpSessionSecurityContextRepository();

private HttpServletRequest request;
private HttpServletResponse response;

PasswordEncoder bcrypt = new BCryptPasswordEncoder();

UserRepository userRepository;


public loginController(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
}

@RequestMapping("/login")
public String login(Model model) {
    return "login";
}


@PostMapping("/login_success")
public String processLogin(@RequestParam String username, @RequestParam String password,
HttpServletRequest request, HttpServletResponse response) {


    List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("USER");
    User user = new User();
    user.setusername(username);
    userRepository.findByUsername(username);
    user.setPassword(password);


    UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated(
            username, password, authorities);

    authenticationManager.authenticate(token);

    SecurityContext securityContext = this.securityContextHolderStrategy.createEmptyContext();
    securityContext.setAuthentication(token);
    this.securityContextHolderStrategy.setContext(securityContext);
    this.securityContextRepository.saveContext(securityContext, request, response);

    return "redirect:/login_success";
}

And here is my security configuration:

@EnableWebSecurity // have to add or it can't find HttpSecurity type
@Configuration
public class SecurityConfig {


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {



        http
                .csrf(Customizer.withDefaults())
                .authorizeHttpRequests((requests) -> requests
                        .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
                        .requestMatchers("/", "/home", "/confirmation").permitAll()
                        .requestMatchers("/signup").permitAll()
                        .requestMatchers("/login").permitAll()
                        .requestMatchers("/css/**").permitAll()
                        .requestMatchers("/login_success").hasAuthority("USER").anyRequest().authenticated()


                )

                .securityContext((securityContext) -> securityContext
                        .requireExplicitSave(true)
                )


                .formLogin(form ->
                        form
                                .loginPage("/login")
                                .usernameParameter("username")
                                .passwordParameter("password")
                                .loginProcessingUrl("/login")
                                .defaultSuccessUrl("/login_success")
                                .permitAll()

                )
                .logout(l -> l
                        .logoutSuccessUrl("/").permitAll()


                );

        return http.build();
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(UserDetailsService customUserDetailsService,
                                                       PasswordEncoder encoder) {

        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();

        authenticationProvider.setUserDetailsService(customUserDetailsService);
        authenticationProvider.setPasswordEncoder(encoder);

        return new ProviderManager(authenticationProvider);
    }

Is it better to implement the securitycontextholder in the security config?

Been following these docs and can't seem to find guidance on how to use for custom login point for spring 6.1+.

https://docs.spring.io/spring-security/reference/6.1/migration/servlet/session-management.html

https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-authenticationprovider

https://docs.spring.io/spring-security/reference/servlet/authentication/persistence.html#httpsecuritycontextrepository


Solution

  • I made a change to my login configuration

    public class loginController {
    
        @GetMapping("/login")
        public String login() {
            return "login";
        }
    }
    

    and the security config

     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    
    
            http
                    .csrf().disable()
                    .authorizeHttpRequests((requests) -> requests
                            .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
                            .requestMatchers("/", "/home", "/confirmation").permitAll()
                            .requestMatchers("/signup").permitAll()
                            .requestMatchers("/login").permitAll()
                            .requestMatchers("/css/**").permitAll()
                            .requestMatchers("/profile").permitAll()
                    )
    
                    .formLogin(form->
                            form
                                    .loginPage("/login")
                                    .loginProcessingUrl("/processlogin")
                                    .defaultSuccessUrl("/profile")
                                    .permitAll()
    
    
                    )
                    .logout(l -> l
                            .logoutSuccessUrl("/logout").invalidateHttpSession(true)
    

    Works now.