如何在 spring 安全性中更新刷新令牌的到期时间而不生成新的刷新令牌?

How to update expiry time of refresh-token without generating new refresh token in spring security?

到目前为止,我已经在我的应用程序中配置了 密码授予类型,并且工作正常。当我点击 /oauth/token 并传递 CI & CS & 用户名 & 密码以及密码授予类型时,我得到 access_token, refresh_token, expiry time, and some other information.

access_token 过期时,应用程序使用 refresh_token.

获得新的 access_token

但我的问题是当应用程序使用refresh_token获取新的access_token[=36时如何延长refresh_token的到期时间=]。例如,如果用户在过去 30 天内未使用该应用程序,则只有 refresh_token 应该过期。

谷歌搜索后我找到了出路。

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.tokenStore(redisTokenStore).userApprovalHandler(userApprovalHandler)
            .authenticationManager(authenticationManager)
            .tokenEnhancer(tokenEnhancer)

            .reuseRefreshTokens(false);
}
当我们尝试使用 refresh_token 获取 access_token 时,

reuseRefreshTokens(false) 将生成新的 refresh_tokenaccess_token。当生成新的 refresh_token 时,它会设置一个新的到期时间(比方说从生成这个新的 refresh_token 之时起 30 天)。但我不想更改 refresh_token。

我怎样才能做到这一点?

我找到了解决这个问题的方法。我实现了 CustomTokenService 而不是 DefaultTokenService。 RefreshTokenGranter 将调用 getTokenServices().refreshAccessToken(refreshToken, tokenRequest)

public class RefreshTokenGranter extends AbstractTokenGranter {

private static final String GRANT_TYPE = "refresh_token";

... some code

@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
    String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
    return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
  }

}

这将调用我的 CustomTokenService :

@Component
public class CustomTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean{

private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.

private int accessTokenValiditySeconds = 60 * 60 * 24; // default 24 hours.

private boolean supportRefreshToken = true;

private boolean reuseRefreshToken = true;

private boolean updateRefreshTokenLastLoginTime = true;

@Autowired
private TokenStore tokenStore;

@Autowired
private ClientDetailsService clientDetailsService;

@Autowired
private TokenEnhancer accessTokenEnhancer;

@Autowired
private AuthenticationManager authenticationManager;

/**
 * Initialize these token services. If no random generator is set, one will be created.
 */
public void afterPropertiesSet() throws Exception {
    Assert.notNull(tokenStore, "tokenStore must be set");
}

@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

    OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
    OAuth2RefreshToken refreshToken = null;
    if (existingAccessToken != null) {
        if (existingAccessToken.isExpired()) {
            if (existingAccessToken.getRefreshToken() != null) {
                refreshToken = existingAccessToken.getRefreshToken();
                // The token store could remove the refresh token when the
                // access token is removed, but we want to
                // be sure...
                tokenStore.removeRefreshToken(refreshToken);
            }
            tokenStore.removeAccessToken(existingAccessToken);
        }
        else {
            // Re-store the access token in case the authentication has changed
            tokenStore.storeAccessToken(existingAccessToken, authentication);
            return existingAccessToken;
        }
    }

    // Only create a new refresh token if there wasn't an existing one
    // associated with an expired access token.
    // Clients might be holding existing refresh tokens, so we re-use it in
    // the case that the old access token
    // expired.
    if (refreshToken == null) {
        refreshToken = createRefreshToken(authentication);
    }
    // But the refresh token itself might need to be re-issued if it has
    // expired.
    else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
        ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
        if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
            refreshToken = createRefreshToken(authentication);
        }
    }

    OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    tokenStore.storeAccessToken(accessToken, authentication);
    // In case it was modified
    refreshToken = accessToken.getRefreshToken();
    if (refreshToken != null) {
        tokenStore.storeRefreshToken(refreshToken, authentication);
    }
    return accessToken;

}

@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
        throws AuthenticationException {

    if (!supportRefreshToken) {
        throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
    }

    OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
    if (refreshToken == null) {
        throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
    }

    OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
    if (this.authenticationManager != null && !authentication.isClientOnly()) {
        // The client has already been authenticated, but the user authentication might be old now, so give it a
        // chance to re-authenticate.
        Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
        user = authenticationManager.authenticate(user);
        Object details = authentication.getDetails();
        authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
        authentication.setDetails(details);
    }
    String clientId = authentication.getOAuth2Request().getClientId();
    if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {
        throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
    }

    // clear out any access tokens already associated with the refresh
    // token.
    tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);

    if (isExpired(refreshToken)) {
        tokenStore.removeRefreshToken(refreshToken);
        throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);
    }

    authentication = createRefreshedAuthentication(authentication, tokenRequest);

    if (!reuseRefreshToken) {
        tokenStore.removeRefreshToken(refreshToken);
        refreshToken = createRefreshToken(authentication);
    }else if(updateRefreshTokenLastLoginTime) {
        String refreshTokn = refreshToken.getValue();
        tokenStore.removeRefreshToken(refreshToken);
        refreshToken = updateRefreshToken(authentication,refreshTokn);
    }

    OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    tokenStore.storeAccessToken(accessToken, authentication);
    if (!reuseRefreshToken) {
        tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
    }else if(updateRefreshTokenLastLoginTime) {
        tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
    }
    return accessToken;
}

public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
    return tokenStore.getAccessToken(authentication);
}

/**
 * Create a refreshed authentication.
 * 
 * @param authentication The authentication.
 * @param request The scope for the refreshed token.
 * @return The refreshed authentication.
 * @throws InvalidScopeException If the scope requested is invalid or wider than the original scope.
 */
private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) {
    OAuth2Authentication narrowed = authentication;
    Set<String> scope = request.getScope();
    OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request);
    if (scope != null && !scope.isEmpty()) {
        Set<String> originalScope = clientAuth.getScope();
        if (originalScope == null || !originalScope.containsAll(scope)) {
            throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope
                    + ".", originalScope);
        }
        else {
            clientAuth = clientAuth.narrowScope(scope);
        }
    }
    narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication());
    return narrowed;
}

protected boolean isExpired(OAuth2RefreshToken refreshToken) {
    if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
        ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;
        return expiringToken.getExpiration() == null
                || System.currentTimeMillis() > expiringToken.getExpiration().getTime();
    }
    return false;
}

public OAuth2AccessToken readAccessToken(String accessToken) {
    return tokenStore.readAccessToken(accessToken);
}

public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
        InvalidTokenException {
    OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
    if (accessToken == null) {
        throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
    }
    else if (accessToken.isExpired()) {
        tokenStore.removeAccessToken(accessToken);
        throw new InvalidTokenException("Access token expired: " + accessTokenValue);
    }

    OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
    if (result == null) {
        // in case of race condition
        throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
    }
    if (clientDetailsService != null) {
        String clientId = result.getOAuth2Request().getClientId();
        try {
            clientDetailsService.loadClientByClientId(clientId);
        }
        catch (ClientRegistrationException e) {
            throw new InvalidTokenException("Client not valid: " + clientId, e);
        }
    }
    return result;
}

public String getClientId(String tokenValue) {
    OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue);
    if (authentication == null) {
        throw new InvalidTokenException("Invalid access token: " + tokenValue);
    }
    OAuth2Request clientAuth = authentication.getOAuth2Request();
    if (clientAuth == null) {
        throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue);
    }
    return clientAuth.getClientId();
}

public boolean revokeToken(String tokenValue) {
    OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
    if (accessToken == null) {
        return false;
    }
    if (accessToken.getRefreshToken() != null) {
        tokenStore.removeRefreshToken(accessToken.getRefreshToken());
    }
    tokenStore.removeAccessToken(accessToken);
    return true;
}

private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
    if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
        return null;
    }
    int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
    String value = UUID.randomUUID().toString();
    if (validitySeconds > 0) {
        return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()
                + (validitySeconds * 1000L)));
    }
    return new DefaultOAuth2RefreshToken(value);
}

private OAuth2RefreshToken updateRefreshToken(OAuth2Authentication authentication,String ref_tokn) {
    if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
        return null;
    }
    int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
    String value = ref_tokn;
    if (validitySeconds > 0) {
        return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()
                + (validitySeconds * 1000L)));
    }
    return new DefaultOAuth2RefreshToken(value);
}

private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
    DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
    int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
    if (validitySeconds > 0) {
        token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
    }
    token.setRefreshToken(refreshToken);
    token.setScope(authentication.getOAuth2Request().getScope());

    return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}

/**
 * The access token validity period in seconds
 * 
 * @param clientAuth the current authorization request
 * @return the access token validity period in seconds
 */
protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
    if (clientDetailsService != null) {
        ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
        Integer validity = client.getAccessTokenValiditySeconds();
        if (validity != null) {
            return validity;
        }
    }
    return accessTokenValiditySeconds;
}

/**
 * The refresh token validity period in seconds
 * 
 * @param clientAuth the current authorization request
 * @return the refresh token validity period in seconds
 */
protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) {
    if (clientDetailsService != null) {
        ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
        Integer validity = client.getRefreshTokenValiditySeconds();
        if (validity != null) {
            return validity;
        }
    }
    return refreshTokenValiditySeconds;
}

/**
 * Is a refresh token supported for this client (or the global setting if
 * {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set.
 * 
 * @param clientAuth the current authorization request
 * @return boolean to indicate if refresh token is supported
 */
protected boolean isSupportRefreshToken(OAuth2Request clientAuth) {
    if (clientDetailsService != null) {
        ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
        return client.getAuthorizedGrantTypes().contains("refresh_token");
    }
    return this.supportRefreshToken;
}

/**
 * An access token enhancer that will be applied to a new token before it is saved in the token store.
 * 
 * @param accessTokenEnhancer the access token enhancer to set
 */
public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {
    this.accessTokenEnhancer = accessTokenEnhancer;
}

/**
 * The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be
 * non-expiring.
 * 
 * @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token.
 */
public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) {
    this.refreshTokenValiditySeconds = refreshTokenValiditySeconds;
}

/**
 * The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client
 * details service is set the validity period will be read from he client, defaulting to this value if not defined
 * by the client.
 * 
 * @param accessTokenValiditySeconds The validity (in seconds) of the access token.
 */
public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {
    this.accessTokenValiditySeconds = accessTokenValiditySeconds;
}

/**
 * Whether to support the refresh token.
 * 
 * @param supportRefreshToken Whether to support the refresh token.
 */
public void setSupportRefreshToken(boolean supportRefreshToken) {
    this.supportRefreshToken = supportRefreshToken;
}

/**
 * Whether to reuse refresh tokens (until expired).
 * 
 * @param reuseRefreshToken Whether to reuse refresh tokens (until expired).
 */
public void setReuseRefreshToken(boolean reuseRefreshToken) {
    this.reuseRefreshToken = reuseRefreshToken;
}

/**
 * The persistence strategy for token storage.
 * 
 * @param tokenStore the store for access and refresh tokens.
 */
public void setTokenStore(TokenStore tokenStore) {
    this.tokenStore = tokenStore;
}

/**
 * An authentication manager that will be used (if provided) to check the user authentication when a token is
 * refreshed.
 * 
 * @param authenticationManager the authenticationManager to set
 */
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
}

/**
 * The client details service to use for looking up clients (if necessary). Optional if the access token expiry is
 * set globally via {@link #setAccessTokenValiditySeconds(int)}.
 * 
 * @param clientDetailsService the client details service
 */
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
    this.clientDetailsService = clientDetailsService;
}

}

此代码几乎与 DefaultTokenService 相同,除了我的自定义方法 updateRefreshToken(OAuth2Authentication authentication,String ref_tokn) 将刷新令牌的值设置为与旧的相同但具有更新时间。