Configuring multiple authentication providers in conjunction with oauth2+jwt - spring-boot

I am trying to configure my Spring Boot 2, OAuth2 with JWT Authorization Server, which needs to do the following:
take a username/password, build a CustomUserDetails object based on db data and salesforce data, and return a JWT token if authentication passes (this works)
take a refresh token and return a new JWT refresh and access token (this works)
(NEW) take a refresh token, check against the db for a stored token ID before returning a new JWT access + refresh token (this is the trouble spot) The point of this is to ensure only one device is logged in with the user's credentials at a time.
In order to do #3, I am trying to customize the PreAuthenticatedAuthenticationProvider by giving it a custom UserDetailsService, and the AuthenticationManagerBuilder bean needs to be passed both the customized PreAuthenticatedAuthenticationProvider and the DaoAuthenticationProvider.
Assuming I am going in the right direction with that, here is my configuration code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
//implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>
#Autowired
CustomPreauthenticatedUserDetailsService customPreauthenticatedUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/swagger-ui**","/webjars/**","/swagger-resources/**", "/v2/**").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/oauth/token/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManager) throws Exception {
authenticationManager.authenticationProvider(preauthAuthProvider());
authenticationManager.authenticationProvider(dbAuthProvider());
}
#Bean
#Qualifier(value = "authenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean(value="preAuthProvider")
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
LOGGER.info("Setting customPreauthenticatedUserDetailsService");
preauthAuthProvider.setPreAuthenticatedUserDetailsService(customPreauthenticatedUserDetailsService);
return preauthAuthProvider;
}
#Bean(value="dbAuthProvider")
public DaoAuthenticationProvider dbAuthProvider() {
DaoAuthenticationProvider dbAuthProvider = new DaoAuthenticationProvider();
dbAuthProvider.setUserDetailsService(userDetailsService);
dbAuthProvider.setPasswordEncoder(passwordEncoder);
return dbAuthProvider;
}
}
On the AuthorizationServerConfig side:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final Integer ACCESS_TOKEN_VALIDITY_SECONDS = 300;
private static final Integer REFRESH_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 60;
#Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManagerBean, PasswordEncoder passwordEncoder, CustomTokenEnhancer customTokenEnhancer, TokenStore tokenStore, JwtAccessTokenConverter accessTokenConverter) {
this.authenticationManagerBean = authenticationManagerBean;
this.passwordEncoder = passwordEncoder;
this.customTokenEnhancer = customTokenEnhancer;
this.tokenStore = tokenStore;
this.accessTokenConverter = accessTokenConverter;
}
private AuthenticationManager authenticationManagerBean;
private PasswordEncoder passwordEncoder;
private CustomTokenEnhancer customTokenEnhancer;
private JwtAccessTokenConverter accessTokenConverter;
private TokenStore tokenStore;
#Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) {
authorizationServerSecurityConfigurer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer.inMemory().withClient("someclient")
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write").accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS)
.secret(this.passwordEncoder.encode("somepassword"));
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, accessTokenConverter));
endpoints.tokenStore(tokenStore).tokenEnhancer(tokenEnhancerChain)
.authenticationManager(this.authenticationManagerBean);
}
}
Upon start up of the app, the first sign of trouble I see is:
s.c.a.w.c.WebSecurityConfigurerAdapter$3 : No authenticationProviders and no parentAuthenticationManager defined. Returning null.
Oddly, that appears to be a lie, or it's talking about something else, because when I try to login with username and password - it works, and I get both my refresh and access tokens. The debugger shows that the ProviderManager has both my custom authentication provider classes and uses them for authentication.
However, when I try to retrieve a new token with the refresh token, the debugger shows that the app appears to be taking a different ProviderManager path - this ProviderManager only has PreAuthenticatedAuthenticationProvider in its list, and that provider is NOT the one I configured. This PreAuthenticatedAuthenticationProvider is trying to retrieve a UserDetailsService from WebSecurityConfigurerAdapter$UserDetailsServiceDelegator, and the result is an error:
2019-06-08 13:27:24.764 ERROR 8731 --- [nio-8080-exec-3] o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
So, what configuration step am I missing? Why does the refresh token call go elsewhere? Is it the authenticationManagerBean that I'm passing in the AuthorizationServerConfig?

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

How to renew access token with the refresh token in oauth2 in spring?

I am very new to spring and it is my first attempt at spring security with oauth2. I have implemented OAuth2 with spring security and I do get the access token and the refresh token. However, while sending the refresh token to get the new access token I got "o.s.s.o.provider.endpoint.TokenEndpoint - IllegalStateException, UserDetailsService is required."
The solution to similar problem by other users appeared to be attaching UserDetailsService with the endpoint.
So I did the same and now when I try to send the request to with grant_type: refresh_token and refresh_token: THE TOKEN along with the client id and secret, I get an error that the user was not found.
Please refer the WebSecurityConfiguration class below:
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean ();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure (HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated();
}
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Please refer the AuthorizationServerConfiguration class below:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
.userDetailsService(userDetailsService);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
Please refer the ResourceServerConfiguration class below:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("scout").tokenStore(tokenStore());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests (). antMatchers ("/oauth/token", "/oauth/authorize **").permitAll();
// .anyRequest (). authenticated ();
http.requestMatchers (). antMatchers ("/api/patients/**") // Deny access to "/ private"
.and (). authorizeRequests ()
.antMatchers ("/api/patients/**"). access ("hasRole ('PATIENT')")
.and (). requestMatchers (). antMatchers ("/api/doctors/**") // Deny access to "/ admin"
.and (). authorizeRequests ()
.antMatchers ("/api/doctors/**"). access ("hasRole ('DOCTOR')");
}
}
The CustomUserDetailsService class for reference if required:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UsersRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<Users> usersOptional = userRepository.findByEmail(email);
Users user = null;
if(usersOptional.isPresent()) {
System.out.println(usersOptional.isPresent());
user = usersOptional.get();
}else {
throw new RuntimeException("Email is not registered!");
}
return new CustomUserDetails(user);
}
}
As I think, the server should only check for the validity of the refresh token as we don't pass the user details with refresh token. So I don't know why it requires the userDetails in the first place.
Please help and guide if I am missing something!
Thanks in advance.
I don't sure. But as I see your code in WebSecurityConfiguration could wired default InMemoryUserDetailsManager UserDetailsService .That could be reason why you have 2 different provider. In one you write, from the other you read users. Please try change your code as I show below and let me know if it help:
Was:
#Autowired
private UserDetailsService customUserDetailsService;
My vision how should be:
#Autowired
private CustomUserDetailsService customUserDetailsService;

Spring Boot OAuth doesn't return a refresh token for clients

I have an API that I've developed in Spring Boot, and I've just noticed that it's not returning a refresh token when you request an access token.
The response from the API looks like this;
{
"access_token": "ed0bdc62-dccf-4f58-933c-e28ad9598843",
"token_type": "bearer",
"expires_in": 2589494,
"scope": "read write"
}
My configuration looks like this;
#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);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
}
}
I previously had the project setup to use JWT for access tokens and that did return a refresh token, however I had to remove JWT as it wasn't compatible with using the token store.
To confirm, it returns a refresh token when the grant_type = password, but not when it's set to 'client_credentials'.
Does anyone have any suggestions why my configuration doesn't return a refresh token?
4.3.3. Access Token Response in RFC 6749 (The OAuth 2.0 Authorization Framework) says "A refresh token SHOULD NOT be included." Therefore, most implementations of OAuth 2.0 authorization servers do not generate a refresh token in Client Credentials flow.
I got the same issue, then I changed in this method, I added REFRESH_TOKEN then in the response I am getting refresh_token value.
static final String REFRESH_TOKEN = "refresh_token";
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(CLIENT_ID)
.secret(messageDigestPasswordEncoder.encode(CLIENT_SECRET))
.authorizedGrantTypes(GRANT_TYPE,REFRESH_TOKEN)
.scopes(SCOPE_READ, SCOPE_WRITE ,TRUST)
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
}

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() + "')");
}
}

Resources