Spring 安全 OAuth2:清除 TokenStore

Spring Security OAuth2: Purge TokenStore

有什么方法可以配置 Spring Security OAuth2 以便自动清除 TokenStore?

我想不时地删除过期的令牌。我看过 InMemoryTokenStore 代码,它时不时地执行刷新。

但是 JdbcTokenStore 不执行任何清除,所以谁负责从存储中删除过期的令牌?

我已经实现了一个使用 MongoDB 作为存储的 TokenStore,但我遇到了同样的问题。没有人从存储中删除过期的令牌。

不幸的是,JdbcTokenStore 不会自动清除过期的令牌。清除旧令牌取决于您。 这是我将如何添加这种机制的想法。

Expiration dateOAuth2AccessToken 的一部分,它作为序列化的 java 对象保存在数据库中。 为了检测 OAuth2AccessToken 是否符合删除条件,您需要从数据库中读取它并对其进行反序列化。 这可能会导致性能下降,您需要清除大量 OAuth2AccessTokens。

我的建议是将 oauth_access_token table 扩展为 TIMESTAMP(H2 方言)类型的列 expiration 以保存到期日期。

create table oauth_access_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256),
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication LONGVARBINARY,
  refresh_token VARCHAR(256),
  expiration TIMESTAMP
);

扩展 JdbcTokenStore 并覆盖 storeAccessToken 方法。不要忘记更改 insertAccessTokenSql 以支持插入语句中的新列 expiration

public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
    String refreshToken = null;
    if (token.getRefreshToken() != null) {
        refreshToken = token.getRefreshToken().getValue();
    }

    if (readAccessToken(token.getValue())!=null) {
        removeAccessToken(token.getValue());
    }

    jdbcTemplate.update(insertAccessTokenSql, new Object[] { extractTokenKey(token.getValue()),
            new SqlLobValue(serializeAccessToken(token)), authenticationKeyGenerator.extractKey(authentication),
            authentication.isClientOnly() ? null : authentication.getName(),
            authentication.getOAuth2Request().getClientId(),
            new SqlLobValue(serializeAuthentication(authentication)), extractTokenKey(refreshToken), token.getExpiration() }, new int[] {
            Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.TIMESTAMP });
}

启用 Spring 的 Task Execution and Scheduling 并添加一个清除旧令牌的预定方法。

@Scheduled(fixedRate = 10000)
public void purgeOldTokens() {
    java.util.Date now = new Date();
    jdbcTemplate.update("delete from oauth_access_token where expiration <?", now);
}

注意。这段代码只是展示了我的想法。我不能保证没有错误。您的代码可能而且应该与我的示例代码有所不同。

@ksokol 非常感谢!

这是使用自定义 JdbcTokenStore 的工作示例

public class CustomJdbcTokenStore extends JdbcTokenStore {
    private final JdbcTemplate jdbcTemplate;
    private static final String INSERT_ACCESS_TOKEN_WITH_EXPIRATION_SQL = "insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token, expiration) values (?, ?, ?, ?, ?, ?, ?,?)";
    private static final String INSERT_REFRESH_TOKEN_WITH_EXPIRATION_SQL = "insert into oauth_refresh_token (token_id, token, authentication, expiration) values (?, ?, ?, ?)";
    
    public CustomJdbcTokenStore(DataSource dataSource) {
        super(dataSource);
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
        String refreshToken = getRefreshToken(token);
        if (this.readAccessToken(token.getValue()) != null) {
            this.removeAccessToken(token.getValue());
        }
        final DefaultAuthenticationKeyGenerator defaultAuthenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();

        this.jdbcTemplate.update(
                INSERT_ACCESS_TOKEN_WITH_EXPIRATION_SQL,
                new Object[]{this.extractTokenKey(token.getValue()),
                        new SqlLobValue(this.serializeAccessToken(token)),
                        defaultAuthenticationKeyGenerator.extractKey(authentication),
                        authentication.isClientOnly() ? null : authentication.getName(),
                        authentication.getOAuth2Request().getClientId(),
                        new SqlLobValue(this.serializeAuthentication(authentication)),
                        this.extractTokenKey(refreshToken), token.getExpiration()},
                new int[]{Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.TIMESTAMP});
    }

    private String getRefreshToken(OAuth2AccessToken token) {
        if (token.getRefreshToken() != null) {
            return token.getRefreshToken().getValue();
        }
        return null;
    }

    @Override
    public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
        final DefaultExpiringOAuth2RefreshToken oAuth2RefreshToken = (DefaultExpiringOAuth2RefreshToken) refreshToken;
        this.jdbcTemplate.update(INSERT_REFRESH_TOKEN_WITH_EXPIRATION_SQL, new Object[]{this.extractTokenKey(refreshToken.getValue()), new SqlLobValue(this.serializeRefreshToken(refreshToken)), new SqlLobValue(this.serializeAuthentication(authentication)), oAuth2RefreshToken.getExpiration()}, new int[]{Types.VARCHAR, Types.BLOB, Types.BLOB, Types.TIMESTAMP});
    }
   
}}