Reputation: 2959
I have configured my spring boot application to to provide oauth2 authorization.
@Configuration
public class OAuth2Configuration {
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.csrf()
.disable()
.headers()
.frameOptions().disable()
.exceptionHandling().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/api/v1/login/**").permitAll()
.antMatchers("/api/v1/admin/**").permitAll()
.antMatchers("/api/v1/test/**").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/api/**").authenticated();
}
}
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(Authorities.ROLE_USER.name())
.authorizedGrantTypes("password", "refresh_token", "authorization_code", "implicit")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(
propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))
.refreshTokenValiditySeconds(100000);
}
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public CustomPasswordEncoder passwordEncoder() {
return new CustomPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**").antMatchers("/api/login/**");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.httpBasic().realmName("WebServices").and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.requestMatchers().antMatchers("/oauth/authorize").and()
.authorizeRequests().antMatchers("/oauth/authorize")
.authenticated();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
}
public class UserDetailsServiceImpl implements UserDetailsService {
@Inject
private AccountDao accountDao;
@Override
@Transactional
public UserDetails loadUserByUsername(final String login) {
Account userFromDatabase = null;
String lowercaseLogin = login.toLowerCase();
if (lowercaseLogin.contains("@")) {
userFromDatabase = accountDao.getByEmailId(lowercaseLogin);
} else {
userFromDatabase = accountDao.getByPhoneNumber(lowercaseLogin);
}
if (userFromDatabase != null) {
if (!userFromDatabase.getActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = userFromDatabase.getRoles().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getRoleName())).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(userFromDatabase.getAccountName(),
userFromDatabase.getAccountPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " + "database");
}
}
}
Now whenever I try to get the refresh token after the access token expires, I always get
2017-07-10 00:57:40.797 INFO 68115 --- [nio-9090-exec-4] o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: NoSuchClientException, No client with requested id: 12345678
Though there is a row in the db with the column phone number 12345678 and account name as 12345678.
I have the header set to Authorization: Basic xxx
xxx
is the same that I use to get the access_token
so I am assuming it works fine.
But the output is always this
{ "error": "unauthorized", "error_description": "User 12345678 was not found in the database" }
Upvotes: 13
Views: 24903
Reputation: 1350
Just add UserDetailsService then it will work
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
and request
http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=myclient&client_secret=secret&refresh_token=<token>
Upvotes: 2
Reputation: 2002
You should be passing clientId and client secret (these are different from userId and password) while fetching access token
using refresh token
. Not sure what are passing in authorisation
headers.
You seem to be having two different issues. When do you get the below error:
{ "error": "unauthorized", "error_description": "User 12345678 was not found in the database" }
Can you verify if the user is successfully authenticated and if the service returned access token and refresh token? You may place debug pointer in UserDetailsService
and check the flow.
Try to validate the configuration by following below steps:
Get Refresh Token, assuming you are using
curl -vu clientId:clientSecret 'http://your_domain_url/api/oauth/token?username=userName&password=password&grant_type=password'
here username and password are different from client ID and client secret This should return you refresh token and access token in the response
{"access_token":"d5deb98a-75fc-4f3a-bbfd-e5c87ca2ca6f","token_type":"bearer","refresh_token":"b2be4291-57e9-4b28-b114-feb3406e030d","expires_in":2,"scope":"read write"}
The above response has got access token and refresh token. Whenever the access token expires, you can use refresh token to fetch access token like below:
curl -vu clientId:clientSecret 'http://your_domain_url/api/oauth/token?grant_type=refresh_token&refresh_token=refresh_token_value'
Response:
{"access_token":"13fd30f9-f0c5-414e-9fbd-a5e2f9f3e4a7","token_type":"bearer","refresh_token":"b2be4291-57e9-4b28-b114-feb3406e030d","expires_in":2,"scope":"read write"}
Now you can use the access token make your service calls
curl -i -H "Authorization: Bearer 13fd30f9-f0c5-414e-9fbd-a5e2f9f3e4a7" http://your_domain_url/api/mySecureApi
Upvotes: 7
Reputation: 24518
I think for the password grant_type
, a clientId and clientSecret are required. You pass the Base64 encoded clientId and clientSecret instead of the Access Token in the Authorization
header. Like so:
curl -H "Authorization: Bearer [base64encode(clientId:clientSecret)]" "https://yourdomain.com/oauth/token?grant_type=refresh_token&refresh_token=[yourRefreshToken]"
I'm assuming you first get the token like this (which you didn't say even though I asked):
curl --data "grant_type=password&username=user&password=pass&client_id=my_client" http://localhost:8080/oauth/token"
Also, put a breakpoint in loadUserByUsername
and check if it's invoked for the failed refresh attempt.
Upvotes: 2