Issue
I have the following issue where the user log in succeeds but continuously loops through a series of redirects until the browser shows: "The page isn’t redirecting properly".
I've set up my project following this Baeldung one using Spring Security 5 - Authorization Server: https://github.com/Baeldung/spring-security-oauth/tree/master/oauth-authorization-server
The redirect loop looks like this:
- http://auth-server:9000/oauth2/authorize?response_type=code...
- http://127.0.0.1:9090/login/oauth2/code/product-client-oidc?code=... (this is the client server, the documentation suggests that the redirect /login/oauth/code is the default redirect link when successfully authenticating, so the user authentication works)
- http://127.0.0.1:9090/oauth2/authorization/product-client-oidc?error (no actual error value is sent though)
No other errors or info are shown in the logs.
Through a series of eliminations I've figured that the problem might be my implementation of the user details service, because if I eliminate it and set the Baeldung one if works. Here's my implementation:
@Service
public class SimpleUserDetailsService implements UserDetailsService {
private final SimpleUserRepository userRepository;
public SimpleUserDetailsService(SimpleUserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<SimpleUser> userOpt = userRepository.findByUsername(username);
SimpleUserDetails details = userOpt.map(user -> {
Collection<GrantedAuthority> authorities = getAuthorities(user.getRoles());
String repoUsername = user.getUsername();
String password = user.getPassword();
boolean enabled = user.isEnabled();
return new SimpleUserDetails(repoUsername, password, enabled, true, true, true, authorities, user.getId());
}).orElseThrow();
return details;
}
private Collection<GrantedAuthority> getAuthorities(List<SimpleRole> roles) {
Collection<GrantedAuthority> simpleAuthorities = roles.stream()
.map(a -> new SimpleGrantedAuthority(getAuthorityFor(a.getRole()))).collect(Collectors.toList());
return simpleAuthorities;
}
private String getAuthorityFor(String role) {
return "history.read";
}
}
My Config
file:
@Bean
public CommandLineRunner demo(SimpleUserRepository repository) {
return (args) -> {
PasswordEncoder encoder = passwordEncoder();
SimpleUser john = new SimpleUser("john", encoder.encode("pass"), true, "[email protected]");
SimpleUser admin = new SimpleUser("admin", encoder.encode("123"), true, "[email protected]");
SimpleUser dan = new SimpleUser("dan", encoder.encode("123"), true, "[email protected]");
SimpleRole johnRole = new SimpleRole(john, SimpleRole.ADMIN);
SimpleRole johnRole2 = new SimpleRole(john, SimpleRole.USER);
SimpleRole adminRole = new SimpleRole(admin, SimpleRole.ADMIN);
SimpleRole danRole = new SimpleRole(dan, SimpleRole.ADMIN);
john.setRoles(Arrays.asList(johnRole, johnRole2));
admin.setRoles(List.of(adminRole));
dan.setRoles(List.of(danRole));
repository.save(john);
repository.save(admin);
repository.save(dan);
};
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults());
**http.authenticationProvider(authenticationProvider());**
return http.build();
}
If I remove the aforementioned code with:
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
then the application redirects normally to the desired link.
What I've tried:
- tried a custom implementation of
AuthenticationProvider
, despiteDaoAuthenticationProvider
authenticating properly, obviously didn't work
Solution
The problem was the password encoder, I am not sure if this is a bug but after swapping out my Password Encoder with the following, not it works.
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
By default, the password encoder is BCrypt, but using the concrete implementation of BCrypt will also create the same buggy behavior as described in the original post, so you specifically have to use this implementation.
Answered By - HornOfHattin
Answer Checked By - Clifford M. (JavaFixing Volunteer)