What is the use of consumer token services in spring boot oauth2? - spring-boot

I used JDBC token store in authorization server. But I don't know about the consumer token services. Can anyone explain it? How to revoke the access token using consumer token services?
Authorization Config
#Configuration
#EnableAuthorizationServer
public class OAuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
AuthenticationManager authenticationManager;
#Autowired
Environment environment;
#Autowired
#Qualifier("dataSourceApi")
DataSource dataSource;
#Primary
#Bean
public ConsumerTokenServices defaultTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
#Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
public JdbcClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}
}

Old post, but this should have an answer for future devs.
When a user logs out, the access token should (must?!) be revoked. This can be done via
tokenServices.revokeToken(tokenId);
See eg. http://www.baeldung.com/logout-spring-security-oauth

Related

Spring ouath2Authserver oauth/token returns internal server Error for grant client_credentials

Im trying to implement Authorisation server with password and client_credentials grant
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
public PasswordEncoder passwordEncoder;
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore jwtTokenStore;
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private TokenEnhancer jwtTokenEnhancer;
#Bean
public TokenEnhancer jwtTokenEnhancer(){
return new JWTokenEnhancer();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer(), jwtAccessTokenConverter));
endpoints
.authenticationManager(authenticationManager)
.tokenStore(jwtTokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
jcsb.passwordEncoder(passwordEncoder);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder)
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
}
web config file
Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "userService")
private UserDetailsService userDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/api-docs/**").permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Allow eureka client to be accessed without authentication
web.ignoring().antMatchers("/*/")//
.antMatchers("/eureka/**")//
.antMatchers(HttpMethod.OPTIONS, "/**"); // Request type options should be allowed.
}
}
#Configuration
public class JwtTokenConfig {
#Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("dev");
return accessTokenConverter;
}
}
i have configured client details to pick up from database -
When i try to get access token based on password grant im able to get the access token
but when i try to get access token based on grnat_type client credentials - im getting internal server error .
Please help to check on what is wrong with my implementation.
enter image description here
In your class OAuthConfiguration, check client configuration present in configure(ClientDetailsServiceConfigurer clients) method. It appears that the JDBC client details service is not able to find any client details.
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.listClientDetails(); // This probably would be empty.
If so, configure JDBC client details service something like this:
clients.jdbc(dataSource).withClient(CLIEN_ID)
.secret(encoder.encode(CLIENT_SECRET))
.authorizedGrantTypes("password", "refresh_token", "client_credentials")
.scopes("read", "write")
.resourceIds(RESOURCE_ID).and().build();
Found the Issue .
public class JWTokenEnhancer implements TokenEnhancer{
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
info.put("user-info", "user additional information...");
// User user = (User) authentication.getPrincipal();
// info.put("isAdmin", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()).contains("ROLE_ADMIN"));
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
The commented line was causing the issue as there was no user in case of client_credentials

Spring Boot OAuth 2 - expiring refresh tokens when password changed

I've created an API using Spring Boot/OAuth. It's currently set so that access_tokens are valid for 30 days, and refresh_tokens are valid for 5 years. It's been requested that OAuth work this way so that a single refresh_token can be used over and over again. What we also need to do is implement some way of expiring refresh tokens when a user changes their password, this is what I'm struggling with as we're not using a token store as we're using JWTs, so there's no need to store the tokens, and even when we were storing that in a database we regularly got 'Invalid refresh token' errors, so removed the token store.
My question is, how to you handle expiring refresh tokens, say, when a user changes their password (as suggested by OAuth).
My client has specifically requested that the returned refresh_token be long-life, but I'm concerned that a long-life refresh token isn't very secure, as if anyone gets hold of that token they can access the users account until that token naturally expires. Personally I'd prefer to set a shorter expiry on refresh_tokens at say 45 days, forcing the client to store a new refresh_token every 45 days at least.
Here's are some of my configuration classes for security to show how I currently have things setup;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private AccountAuthenticationProvider accountAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(accountAuthenticationProvider);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(env.getProperty("jwt.secret"));
return jwtAccessTokenConverter;
}
}
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "myapi";
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(RESOURCE_ID)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/view/**").permitAll()
.anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore tokenStore;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
//.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.accessTokenConverter(jwtAccessTokenConverter);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
}
}
Revoking token is not supported if JWT is used. If you would like to have this functionality implemented, you should consider using JdbcTokenStore instead.
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource());
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource jdbcdataSource = new DriverManagerDataSource();
jdbcdataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
jdbcdataSource.setUrl(env.getProperty("jdbc.url"));//connection String
jdbcdataSource.setUsername(env.getProperty("jdbc.user"));
jdbcdataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
}
When the user changes password, you should invoke the revokeToken API
#Resource(name="tokenServices")
ConsumerTokenServices tokenServices;
#RequestMapping(method = RequestMethod.POST, value = "/tokens/revoke/{tokenId:.*}")
#ResponseBody
public String revokeToken(#PathVariable String tokenId) {
tokenServices.revokeToken(tokenId);
return tokenId;
}
JDBCTokenStore also exposes a method using which you can invalidate the refresh token
#RequestMapping(method = RequestMethod.POST, value = "/tokens/revokeRefreshToken/{tokenId:.*}")
#ResponseBody
public String revokeRefreshToken(#PathVariable String tokenId) {
if (tokenStore instanceof JdbcTokenStore){
((JdbcTokenStore) tokenStore).removeRefreshToken(tokenId);
}
return tokenId;
}

How to make requests to an endpoint to be authorised by client id and secret in Spring?

By default when OAuth2 authorisation is enabled in Spring framework (see the configuration below) and we make a call to /oauth/token to issue an access token, the following request is being sent:
POST /oauth/token
Authorization: Basic Y34tcF9ib3VpOg==
POST data:
grant_type=password&username=myuser&password=mypass
The basic authorisation above is client-id and client's secret in the following form:
myclient:secret123
I can then send this request to Spring's /oauth/check_token:
POST /oauth/check_token
Authorization: Basic Y34tcF9ib3VpOg==
POST data:
token=the_token_retrieved_from_last_request
This works fine and it does basic authorisation before serving my request.
Note that the basic authorisation here goes to Spring's JdbcClientDetailsService in which it looks up a table named oauth_client_details, this is fine too.
Now for some reason I need to have a customised endpoint instead of Spring's /token/check_access. So I have created a controller similar to the Spring's CheckTokenEndpoint.java and named it TokenIntrospectionEndpoint. The URL pattern for my endpoint is set to be /oauth/introspect:
#Controller
#RequestMapping("/oauth")
public class TokenIntrospectionEndpointImpl {
private RestTemplate restTemplate;
#RequestMapping(value = "/introspect")
#ResponseBody
#Override
public Map<String, ?> introspect(#RequestParam("token") String token) {
// return data
}
}
Now the problem is, the request to this endpoint is being served without considering basic authorisation. So I've added this line in the configuration:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/introspect").access("isAuthenticated()");
}
Now Spring security kicks in but it doesn't treat this request the same way it does for /oauth/check_token and by that I mean it doesn't look up table oauth_client_details automatically just like the same way it does for other oauth related requests. As such, I get 401 http error code.
I think I am missing something here to tell Spring that this is oauth2 request so that it considers client-id/secret and authenticate it automatically. Any hint would be appreciated.
My configurations:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorisationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private MySecuritySettings securitySetting;
#Autowired
private DataSource dataSource;
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
//TODO I'd rather not to override Spring's endpoint URL but had issues with authentication.
.pathMapping("/oauth/check_token", "/oauth/introspect").tokenStore(this.tokenStore())
.authenticationManager(authenticationManager)
.tokenServices(tokenServices())
.accessTokenConverter(tokenConverter())
.requestValidator(createOAuth2RequestValidator());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(myClientDetailsService());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.checkTokenAccess("isAuthenticated()")
.tokenKeyAccess("permitAll()")
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
public MyClientDetailsService myClientDetailsService(){
MyClientDetailsService myClientDetailsService = new MyClientDetailsService(dataSource);
myClientDetailsService.setPasswordEncoder(passwordEncoder());
return myClientDetailsService;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(tokenConverter());
}
#Bean
public JwtAccessTokenConverter tokenConverter() {
final JwtAccessTokenConverter jwtAccessTokenConverter = new CompJwtAccessTokenConverter();
DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
defaultAccessTokenConverter.setUserTokenConverter(new CompPrincipalExtractor());
jwtAccessTokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource(securitySetting.getKeystoreFileName()),
securitySetting.getStorepass().toCharArray())
.getKeyPair(securitySetting.getKeyAlias(),
securitySetting.getKeypass().toCharArray());
jwtAccessTokenConverter.setKeyPair(keyPair);
return jwtAccessTokenConverter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(securitySetting.isRefreshAccessToken());
tokenServices.setReuseRefreshToken(securitySetting.isReuseRefreshToken());
tokenServices.setTokenEnhancer(tokenConverter());
tokenServices.setAccessTokenValiditySeconds(securitySetting.getAccessTokenValiditySeconds());
return tokenServices;
}
#Bean
#Primary
public OAuth2RequestValidator createOAuth2RequestValidator() {
return new ExpressionBasedOAuth2RequestValidator();
}
}
AND this:
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "auth_serv";
#Autowired
TokenStore tokenStore;
#Autowired
MySecuritySettings mySecuritySettings;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(RESOURCE_ID)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/introspect").access("isAuthenticated()")
.and()
.authorizeRequests()
.antMatchers("/api/**/*").access("#oauth2.hasScope('" + mySecuritySettings.getAuthserverScopenameAllAccess() + "')");
}
}

Spring OAuth with JWT - authorization to be performed only based on JWT in authorization server

In my system I use JWT tokens so that authorization of requests could be performed based on JWT only, with no need to call database to fetch roles etc. I use OAuth, and my services are Resource servers which works fine and I'm satisfied with that. The problem I have is with my uaa-service, which is both Authorization and Resource server.
I've noticed that when I send requests to uaa-service I can inject #AuthenticatedPrincipal into my controller method and I have access to all user fields, not only those which are present in JWT. It means that Spring somehow maintains session or maybe in the background fetches user data from database. Here are my settings:
#Configuration
#EnableResourceServer
#Slf4j
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
log.info("Configuring resource server");
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/register").permitAll().anyRequest().authenticated();
}
#Autowired
public void setJwtAccessTokenConverter(JwtAccessTokenConverter jwtAccessTokenConverter) {
jwtAccessTokenConverter.setAccessTokenConverter(bitcoinTokenConverter());
}
#Bean
DefaultAccessTokenConverter bitcoinTokenConverter() {
return new CustomTokenConverter();
}
And
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource(keystoreName), keystorePassword.toCharArray())
.getKeyPair(keystoreAlias);
converter.setKeyPair(keyPair);
return converter;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public DefaultAccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(customUserDetailsService)
.tokenEnhancer(tokenEnhancerChain);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
and
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Bean
FilterRegistrationBean forwardedHeaderFilter() {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
filterRegBean.setFilter(new ForwardedHeaderFilter());
filterRegBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegBean;
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/register").permitAll().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
auth.authenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));
}
Where did I make a mistake? In my SecurityConfig.java I have
auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
so that the login could be performed by fetchining user from database and validating password but it looks like it may also cause that incoming requests are not handled only based on JWT.

In Spring Security OAuth, how do you use a passwordEncoder for client secrets?

I'm trying to BCrypt the client secrets I'm storing in the database when using Spring Security Oauth2. I can see that JdbcClientDetailsService has a setPasswordEncoder method (as mentioned in this question). However, the ClientDetailsServiceConfigurer on AuthorizationServerConfigurerAdapter does not show any obvious way of setting the password encoder. Does anyone know how to do this? I've included the authorization server configuration:
#Configuration
#EnableAuthorizationServer
public static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
public TokenApprovalStore tokenApprovalStore() {
TokenApprovalStore tokenApprovalStore = new TokenApprovalStore();
tokenApprovalStore.setTokenStore(tokenStore);
return tokenApprovalStore;
}
#Bean
public UserApprovalHandler userApprovalHandler() {
LocalUserApprovalHandler handler = new LocalUserApprovalHandler();
handler.setApprovalStore(tokenApprovalStore());
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
handler.setUseApprovalStore(true);
return handler;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(tokenStore)
.userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.realm("abcdefgh/client");
}
}
As of release 2.0.5, passwordEncoder(...) methods are now available on both ClientDetailsServiceConfigurer and AuthorizationServerSecurityConfigurer, which are made available when extending AuthorizationServerConfigurerAdapter. Use the same PasswordEncoder implementation on both and the configuration is relatively easy.
ClientDetailsServiceConfigurer doesn't really need to encode passwords if they are already in the database. If you use a backend store you should just inject it into the configurer, and deal with creating the data in the back end as a separate problem.

Resources