我是靠谱客的博主 勤劳大山,最近开发中收集的这篇文章主要介绍spring security oauth2中redis下client_id_to_access命名空间过期数据过多导致内存溢出问题,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Spring security的TokenStore redis实现中,命名空间client_id_to_access与uname_to_access下list数据过期时间是伴随着整个list的,redis的策略就是新增list数据就会刷新整个key的过期时间,这就会导致与同一个clientId相关的token过期后改命名空间下的key却没有被销毁,导致内存溢出。

解决方法

利用redis监听过期key事件回调方法处理client_id_to_access中的过期数据

1、启动Redis的过期key监听功能

在redis.conf配置文件中把#notify-keyspace-events Ex这一行注释打开;

2、改造TokenStore 的redis缓存实现

主要是在RedisTokenStore的storeAccessToken方法中增加一个与access命名空间相同的数据,过期时间要适当的比access命名空间中的key要长一点,可以自定义命名,比如access_bak。这样做的目的是在access的过期key触发监听器的时候可以从相应的access_bak中获取到相同的内容,因为Redis过期key触发回调后只能在监听器获取到key的值,但是内容就已经被删除了,但是我们需要相应key的内容来删除client_id_to_access中的数据。

相关代码省略,对storeAccessToken方法进行重写即可。

3、增加redis过期key监听器,获取过期数据并删除过期数据

利用redis销毁过期key的回调机制,新增一个监听器处理过期数据。

/**
 *
 * redis过期key监听器
 * @author zlt
 *
 */
@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    @Autowired
    private RedisRepository redisRepository;
    private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
    private final RedisConnectionFactory connectionFactory;

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer, RedisConnectionFactory connectionFactory) {
        super(listenerContainer);
        this.connectionFactory = connectionFactory;
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        if (message == null) {
            log.debug("message不能为空");
            return;
        }
        //获取失效的的key
        String expiredKey = message.toString();
        if (StringUtils.isEmpty(expiredKey)) {
            log.debug("expiredKey不能为空");
            return;
        }
        String accesskey = expiredKey.substring(0, expiredKey.indexOf(":") + 1);
        if (!"access:".equals(accesskey)) {
            log.debug("非需要监听key,跳过");
            return;
        }
        String accessValue = expiredKey.substring(expiredKey.indexOf(":") + 1);
        // 分布式集群部署下防止一个过期被多个服务重复消费
        String qc = "qc:" + accessValue;
        String oldLock = redisRepository.getAndSet(qc, "1");
        if (StringUtils.isNotEmpty(oldLock) && "1".equals(oldLock)) {
            log.debug("其他节点已经处理了该数据,跳过");
            return;
        }
        byte[] accessBakKey = serializeKey(SecurityConstants.ACCESS_BAK + accessValue);
        byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + accessValue);
        RedisConnection conn = getConnection();
        try {
            byte[] access = conn.get(accessBakKey);
            byte[] auth = conn.get(authKey);
            OAuth2Authentication authentication = deserializeAuthentication(auth);
            if (authentication != null) {
                byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
                byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
                conn.openPipeline();
                conn.lRem(unameKey, 1, access);
                conn.lRem(clientId, 1, access);
                conn.closePipeline();
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            conn.del(oldLock.getBytes());
            conn.close();
        }

    }

    private byte[] serializeKey(String object) {
        return serialize("" + object);
    }

    private byte[] serialize(String string) {
        return serializationStrategy.serialize(string);
    }

    private RedisConnection getConnection() {
        return connectionFactory.getConnection();
    }

    private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
        return serializationStrategy.deserialize(bytes, OAuth2Authentication.class);
    }

    private static String getApprovalKey(OAuth2Authentication authentication) {
        String userName = authentication.getUserAuthentication() == null ? ""
                : authentication.getUserAuthentication().getName();
        return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
    }

    private static String getApprovalKey(String clientId, String userName) {
        return clientId + (userName == null ? "" : ":" + userName);
    }
}

配置监听器:

/**
 *
 * redis过期key监听器配置类
 * @author zlt
 *
 */
@Configuration
public class RedisListenerConfig {
    @Bean
    @Primary
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        return container;
    }
}

最后

以上就是勤劳大山为你收集整理的spring security oauth2中redis下client_id_to_access命名空间过期数据过多导致内存溢出问题的全部内容,希望文章能够帮你解决spring security oauth2中redis下client_id_to_access命名空间过期数据过多导致内存溢出问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(50)

评论列表共有 0 条评论

立即
投稿
返回
顶部