I am new to Spring Boot and I am trying to configure OAuth 2.0. The problem I'm currently facing is that I keep getting the following message when I try to request an access token:
{"error": "invalid_grant", "error_description": "Bad credentials"}
An error message in the Spring Boot console indicates that the user cannot be found.
: authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider: User 'stromero' not found: Returning a cached instance of singleton bean 'authenticationAuditListener'
I implemented a user user that was already stored in the database using JPA, I canβt understand why Spring Security cannot find this user, this may be a problem with my logic or configuration. If someone with a lot of experience can look at my code and possibly direct me in the right direction, this will be very helpful.
This is an HTTP request:
POST / oauth / token HTTP / 1.1 Host: localhost: 8181 Authorization: Primary YnJvd3NlcjpzZWNyZXQ = Cache-Control: no-cache Content-Type: application / x-www-form-urlencoded username = stromero & password = password & client_id = browser & client_secret = secret & grant_type = password
These are the classes that I used to implement my user user and OAuth 2.0
@Repository public interface UserRepository extends CrudRepository<CustomUser, String> { public CustomUser findByUsername(String name); }
Below is the user user I created
@Entity @Table (name = "custom_user") public class CustomUser { @Id @Column(name = "id", nullable = false, updatable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(name = "username", unique=true, nullable = false) private String username; @Column(name = "password", nullable = false) private String password; @ElementCollection private List<String> roles = new ArrayList<>(); public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
The following is a user data service that reads user information from a database and returns it as a UserDetails object.
@Service @Transactional(readOnly = true) public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { CustomUser customUser = userRepository.findByUsername(s); boolean enabled = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; return new User( customUser .getUsername(), customUser .getPassword().toLowerCase(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities(customUser.getRoles())); } public Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) { List<GrantedAuthority> authList = getGrantedAuthorities(roles); return authList; } public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) { List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); for (String role : roles) { authorities.add(new SimpleGrantedAuthority(role)); } return authorities; } }
Below is a data structure that contains both UserDetailsService and ClientDetailsService
public class ClientAndUserDetailsService implements UserDetailsService, ClientDetailsService { private final ClientDetailsService clients; private final UserDetailsService users; private final ClientDetailsUserDetailsService clientDetailsWrapper; public ClientAndUserDetailsService(ClientDetailsService clients, UserDetailsService users) { super(); this.clients = clients; this.users = users; clientDetailsWrapper = new ClientDetailsUserDetailsService(this.clients); } @Override public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { return clients.loadClientByClientId(clientId); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails user = null; try{ user = users.loadUserByUsername(username); }catch(UsernameNotFoundException e){ user = clientDetailsWrapper.loadUserByUsername(username); } return user; } }
Below is my configuration for OAuth 2.0 using Spring Boot
@Configuration public class OAuth2SecurityConfiguration { @Configuration @EnableWebSecurity protected static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired protected void registerAuthentication( final AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } } @Configuration @EnableResourceServer protected static class ResourceServer extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.authorizeRequests().antMatchers("/oauth/token").anonymous(); // Require all GET requests to have client "read" scope http.authorizeRequests().antMatchers(HttpMethod.GET, "/**") .access("#oauth2.hasScope('read')"); // Require all POST requests to have client "write" scope http.authorizeRequests().antMatchers(HttpMethod.POST,"/**") .access("#oauth2.hasScope('write')"); } } @Configuration @EnableAuthorizationServer @Order(Ordered.LOWEST_PRECEDENCE - 100) protected static class AuthorizationServer extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; private ClientAndUserDetailsService combinedService; public AuthorizationServer() throws Exception { ClientDetailsService clientDetailsService = new InMemoryClientDetailsServiceBuilder() .withClient("browser") .secret("secret") .authorizedGrantTypes("password") .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT") .scopes("read","write") .resourceIds("message") .accessTokenValiditySeconds(7200) .and() .build(); // Create a series of hard-coded users. UserDetailsService userDetailsService = new CustomUserDetailsService(); combinedService = new ClientAndUserDetailsService(clientDetailsService, userDetailsService); } @Bean public ClientDetailsService clientDetailsService() throws Exception { return combinedService; } @Bean public UserDetailsService userDetailsService() { return combinedService; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService()); } } }
Below is my pom.xml file
<properties> <tomcat.version>8.0.8</tomcat.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.2-1002-jdbc4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.0.3.RELEASE</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>17.0</version> </dependency> </dependencies>