4
votes

I am using spring security oauth2 for authentication for my android application clients.When the client request comes with grant_type as password the server issues the access token and refresh token.If the access token expires i can issue a new access token by sending a request with grant_type as refresh_token.Now what will i do if my refresh token expires?I dont want to prompt the users to authenticate again using his credentials.So is there a way to issue a new refresh token along with the new access token? or is there any provision to issue a refresh token with infinite validity or by sending a refresh token with single time use only and refresh the refresh token in each refresh_token grant_type request.Below is my configuration file for spring security oauth2.

      <?xml version="1.0" encoding="UTF-8" ?>
  <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:sec="http://www.springframework.org/schema/security" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd ">


    <!-- This is default url to get a token from OAuth -->
    <http pattern="/oauth/token" create-session="stateless"
      authentication-manager-ref="clientAuthenticationManager"
      xmlns="http://www.springframework.org/schema/security">
      <intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
      <anonymous enabled="false" />
      <http-basic entry-point-ref="clientAuthenticationEntryPoint" />
      <!-- include this only if you need to authenticate clients via request 
        parameters -->
      <custom-filter ref="clientCredentialsTokenEndpointFilter"
        after="BASIC_AUTH_FILTER" />
      <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>
    <!-- This is where we tells spring security what URL should be protected 
      and what roles have access to them -->
    <http pattern="/protected/**" create-session="never"
      entry-point-ref="oauthAuthenticationEntryPoint"
      access-decision-manager-ref="accessDecisionManager"
      xmlns="http://www.springframework.org/schema/security">
      <anonymous enabled="false" />
      <intercept-url pattern="/protected/**" access="ROLE_APP" />
      <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
      <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>

  <bean id="oauthAuthenticationEntryPoint"
      class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
      <property name="realmName" value="test" />
    </bean>

    <bean id="clientAuthenticationEntryPoint"
      class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
      <property name="realmName" value="test/client" />
      <property name="typeName" value="Basic" />
    </bean>

    <bean id="oauthAccessDeniedHandler"
      class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />

    <bean id="clientCredentialsTokenEndpointFilter"
      class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
      <property name="authenticationManager" ref="clientAuthenticationManager" />
    </bean>

    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"
      xmlns="http://www.springframework.org/schema/beans">
      <constructor-arg>
        <list>
          <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
          <bean class="org.springframework.security.access.vote.RoleVoter" />
          <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
        </list>
      </constructor-arg>
    </bean>

    <authentication-manager id="clientAuthenticationManager"
      xmlns="http://www.springframework.org/schema/security">
      <authentication-provider user-service-ref="clientDetailsUserService" />
    </authentication-manager>
    <authentication-manager alias="authenticationManager"
      xmlns="http://www.springframework.org/schema/security">
      <authentication-provider  user-service-ref="userService">
      </authentication-provider>
    </authentication-manager>

    <bean id="userService"
      class="com.example.myproject.ser.UserService">
    </bean>

    <bean id="clientDetailsUserService"
      class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
      <constructor-arg ref="clientDetails" />
    </bean>


    <!-- This defined token store, we have used inmemory tokenstore for now 
      but this can be changed to a user defined one -->
    <bean id="tokenStore"
      class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />

    <!-- This is where we defined token based configurations, token validity 
      and other things -->
    <bean id="tokenServices"
      class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
      <property name="tokenStore" ref="tokenStore" />
      <property name="supportRefreshToken" value="true" />
      <property name="accessTokenValiditySeconds" value="120" />  <!-- 2 hour 3600 -->
      <property name="refreshTokenValiditySeconds" value="420"></property>   <!-- 2 month 5270400 -->
      <property name="clientDetailsService" ref="clientDetails" />
    </bean>

    <bean id="userApprovalHandler"
      class="org.springframework.security.oauth2.provider.approval.TokenServicesUserApprovalHandler">
      <property name="tokenServices" ref="tokenServices" />
    </bean>
    <oauth:authorization-server
      client-details-service-ref="clientDetails" token-services-ref="tokenServices"
      user-approval-handler-ref="userApprovalHandler">
      <oauth:authorization-code />
      <oauth:implicit />
      <oauth:refresh-token />
      <oauth:client-credentials />
      <oauth:password />
    </oauth:authorization-server>

    <oauth:resource-server id="resourceServerFilter"
      resource-id="test" token-services-ref="tokenServices" />



   <bean id="clientDetails"
            class="com.example.myproject.ser.ClientService">
      </bean> 



    <sec:global-method-security
      pre-post-annotations="enabled" proxy-target-class="true">
      <!--you could also wire in the expression handler up at the layer of the 
        http filters. See https://jira.springsource.org/browse/SEC-1452 -->
      <sec:expression-handler ref="oauthExpressionHandler" />
    </sec:global-method-security>

    <oauth:expression-handler id="oauthExpressionHandler" />
    <oauth:web-expression-handler id="oauthWebExpressionHandler" />
  </beans>

In my android application i have the provision to authenticate the same user from multiple devices.That is one can authenticate in any device if already he is authenticated in other device.So the solution don't affect this case.

2

2 Answers

6
votes

You can set validity period for the refresh token either at the client level (see org.springframework.security.oauth2.provider.ClientDetails and org.springframework.security.oauth2.provider.ClientDetailsService). You'll need to set this on the client as it's loaded by the client details service.

public classs MyClientDetailsService implements ClientDetailsService {
    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
        BaseClientDetails client = new BaseClientDetails();
        client.setRefreshTokenValiditySeconds(Integer.MAX_VALUE);
        ...
        return client;
    }
}

Alternatively, you can set a default validity on org.springframework.security.oauth2.provider.token.DefaultTokenServices (assuming that is the implementation that you are using in your server) in your authorisation server configuration. You can do this by adding the following method to your authorisation server configuration class.

@Bean
public AuthorizationServerTokenServices authorizationServerTokenServices() throws Exception {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore);
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setRefreshTokenValiditySeconds(Integer.MAX_VALUE);
        return tokenServices;
}

Once that refresh token has expired though, I believe the only way to obtain a new one is for the user to re-authenticate.

4
votes

According to the source code for spring-security-oauth in the DefaultTokenServices passing a value less or equal to zero as the validity of the refresh token should be enough to make it last forever. Check it out here.

Then the code in the authorisation server configuration should be like this:

@Bean    
fun tokenServices(): DefaultTokenServices {
    val defaultTokenServices = DefaultTokenServices()
    defaultTokenServices.setTokenStore(tokenStore())
    defaultTokenServices.setRefreshTokenValiditySeconds(0)

    return defaultTokenServices
}

Or if you have a JdbcClientDetailsService you can set the refresh token expiry in the oauth_client_detailstable.