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" }
Best Answer
You should be passing clientId and client secret (these are different from userId and password) while fetching
access token
usingrefresh token
. Not sure what are passing inauthorisation
headers.You seem to be having two different issues. When do you get the below error:
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
here username and password are different from client ID and client secret This should return you refresh token and access token in the response
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:
Response:
Now you can use the access token make your service calls