Spring Authorization Server优化篇:持久化JWKSource,解决重启后无法解析AccessToken问题

问题描述

自从第二章将认证服务搭建起来以后就很少更改基础配置了,直到今天有位读者提了一个问题,对我发出了灵魂拷问:重启后jwks的配置发生了变化,无法正确解析token,那Auth服务重启后有办法规避这种情况吗?假如生产环境这样,服务重新部署后之前所有登录的用户必须重新登陆。原问题见下方附图
问题截图

解决方案分析

当时本人想到的是将生成的JWKSource保存至redis,每次重启从redis中获取,这样不管重不重启生成的都是同一个,也就不会出现服务重启后无法解析在有效期内的AccessToken问题了,但是又怕不能序列化,就去查看代码了,最终功夫不负有心人,经过一番查找后发现最主要的配置是来自JWKSet的实例,在JWKSet找到了toString方法和对应的parse方法,toString方法是将jwks的信息转为json字符串,而parse就是读取并解析json字符串,将其转为JWKSet实例

JWKSet中的toString方法

JWKSet中的toString方法

方法内部将jwks转为一个json字符串返回

JWKSet中的parse方法

JWKSet中的parse方法

方法内部根据给定的字符串解析并返回一个JWKSet。

编码解决问题

根据上边的分析得出解决方案,在配置JWKSource之前先从redis中尝试获取一下,获取不到就生成并存入redis;获取到直接解析并生成一个JWKSet;修改AuthorizationConfig类中的JWKSource配置,如下所示

/**
 * 配置jwk源,使用非对称加密,公开用于检索匹配指定选择器的JWK的方法
 *
 * @return JWKSource
 */
@Bean
@SneakyThrows
public JWKSource<SecurityContext> jwkSource() {
    // 先从redis获取
    String jwkSetCache = redisOperator.get(RedisConstants.AUTHORIZATION_JWS_PREFIX_KEY);
    if (ObjectUtils.isEmpty(jwkSetCache)) {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        // 生成jws
        JWKSet jwkSet = new JWKSet(rsaKey);
        // 转为json字符串
        String jwkSetString = jwkSet.toString(Boolean.FALSE);
        // 存入redis
        redisOperator.set(RedisConstants.AUTHORIZATION_JWS_PREFIX_KEY, jwkSetString);
        return new ImmutableJWKSet<>(jwkSet);
    }
    // 解析存储的jws
    JWKSet jwkSet = JWKSet.parse(jwkSetCache);
    return new ImmutableJWKSet<>(jwkSet);
}

附一下RedisConstants类中的AUTHORIZATION_JWS_PREFIX_KEY常量

/**
 * jwk set缓存前缀
 */
public static final String AUTHORIZATION_JWS_PREFIX_KEY = "authorization_jws";

修改配置后启动认证服务,使用oauth2的几种方式获取一下token,然后重启服务携带token请求一下看看认证服务能否解析AccessToken,如果有什么问题可以在评论区中提出。

代码已提交至Gitee

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
Spring Authorization Server RedisTokenStore 默认使用 JDK 序列化方式来序列化和反序列化 Token,但是 JDK 序列化方式有一些缺点,比如序列化后的字节数组长度大、序列化速度慢、可读性差等。为了解决这些问题,可以使用其他序列化方式,比如 JSON 序列化方式。 具体实现步骤如下: 1. 添加 Redis 相关依赖,比如 jedis、lettuce 等。 2. 在 Spring Security 配置类中创建 RedisConnectionFactory 和 RedisTemplate,代码如下: ``` @Configuration public class RedisConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory("localhost", 6379); } @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } } ``` 3. 修改 TokenStore 的实现类 RedisTokenStore 中的序列化代码,将 JDK 序列化方式改为 JSON 序列化方式,代码如下: ``` public class RedisTokenStore implements TokenStore { private static final String ACCESS_TOKEN_PREFIX = "access:"; private static final String REFRESH_TOKEN_PREFIX = "refresh:"; private final RedisTemplate<String, Object> redisTemplate; public RedisTokenStore(RedisConnectionFactory connectionFactory) { Assert.notNull(connectionFactory, "connectionFactory cannot be null"); this.redisTemplate = new RedisTemplate<>(); this.redisTemplate.setConnectionFactory(connectionFactory); this.redisTemplate.setKeySerializer(new StringRedisSerializer()); this.redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); this.redisTemplate.setHashKeySerializer(new StringRedisSerializer()); this.redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); this.redisTemplate.afterPropertiesSet(); } // ... } ``` 这样就可以使用 JSON 序列化方式来序列化和反序列化 Token 了。
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天玺-vains

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值