Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OAuth2-SpringBoot - Refresh token

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.

https://myTestWebServices/oauth/token?grant_type=refresh_token&refresh_token=f4cc8213-3f2b-4a30-965b-6feca898479e

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" }

like image 747
i_raqz Avatar asked Jul 09 '17 20:07

i_raqz


3 Answers

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>
like image 181
Sandeep Bhardwaj Avatar answered Oct 18 '22 02:10

Sandeep Bhardwaj


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
like image 7
Shiva Avatar answered Oct 18 '22 03:10

Shiva


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.

like image 2
Abhijit Sarkar Avatar answered Oct 18 '22 02:10

Abhijit Sarkar