Spring Authorization Server优化篇:Redis值序列化器添加Jackson Mixin,解决Redis反序列化失败问题

前言

    在授权码模式的前后端分离的那篇文章中使用了Redis来保存用户的认证信息,在Redis的配置文件中配置的值序列化器是默认的Jdk序列化器,虽然这样也可以使用,但是在Redis客户端中查看时是乱码的(看起来是),如果切换为Jackson提供的值序列化器时又会在反序列化时失败,这样是不符合实际的,在项目框架搭建完毕或在已有项目中这些配置实际上应该都已经配置好了的,不能说为了这么一个功能去改原有配置,所以这里要跟大家说一声对不起,因为在下学艺不精而导致这么一个大缺陷一直留到了现在。😭

问题分析

    当时用到的地方就是在登录成功和初始化SecurityContextHolderFilter中初始化认证信息的地方存、取SecurityContext(认证信息),存的时候倒是没有问题,但是取的时候就会因为框架内的类不提供默认的构造器从而造成反序列化失败的问题,或者是类型转换异常

    Jackson 只能识别java基本类型,遇到复杂类型时,Jackson 就会先序列化成 LinkedHashMap,然后再尝试强转为所需类别,这样大部分情况下会强转失败,异常信息如下

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class org.springframework.security.core.context.SecurityContext

这种情况需要添加一个配置,如下

objectMapper.activateDefaultTyping(  
    objectMapper.getPolymorphicTypeValidator(),  
    ObjectMapper.DefaultTyping.NON_FINAL,  
    JsonTypeInfo.As.PROPERTY);

但是当添加了这个配置后重启后再次尝试发现还是有异常,不过这时就是因为框架中的类没有提供默认构造器造成的,异常如下:

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `org.springframework.security.authentication.UsernamePasswordAuthenticationToken` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (byte[])"{"@class":"org.springframework.security.core.context.SecurityContextImpl","authentication":{"@class":"org.springframework.security.authentication.UsernamePasswordAuthenticationToken","authorities":["java.util.Collections$UnmodifiableRandomAccessList",[{"@class":"com.example.model.security.CustomGrantedAuthority","authority":"system"},{"@class":"com.example.model.security.CustomGrantedAuthority","authority":"app"},{"@class":"com.example.model.security.CustomGrantedAuthority","authority":"web"}]],"[truncated 893 bytes]; line: 1, column: 184] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"])

异常提示问题在SecurityContextImplauthentication属性上,因为该属性的实例是UsernamePasswordAuthenticationToken,这个类并没有一个默认的构造器,所以在反序列化时直接报错了,最开始时我的想法是写一个实现类,然后存取的时候用自定义的类中转一下,但是后来又发现了Json Mixin这个东西,发现这个玩意儿更方便,于是就实现了一下,写了一个UsernamePasswordAuthenticationMixin类来实现自定义反序列化逻辑,但是昨天突然发现这东西其实框架已经实现了😰就很尴尬,要将这些东西加进来添加一下框架提供的CoreJackson2Module就行,配置如下:

// 添加Security提供的Jackson Mixin  
objectMapper.registerModule(new CoreJackson2Module());

解决方案

Redis配置文件中配置的RedisTemplate添加值序列化器,值序列化器使用的ObjectMapper添加以上提到的那些配置,包括复杂类型映射、Security提供的Json Mixin,完整的Redis配置类如下

package com.example.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.security.jackson2.CoreJackson2Module;

/**
 * Redis的key序列化配置类
 *
 * @author vains
 */
@Configuration
@RequiredArgsConstructor
public class RedisConfig {

    private final Jackson2ObjectMapperBuilder builder;

    /**
     * 默认情况下使用
     *
     * @param connectionFactory redis链接工厂
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 字符串序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 创建ObjectMapper并添加默认配置
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();

        // 序列化所有字段
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        // 此项必须配置,否则如果序列化的对象里边还有对象,会报如下错误:
        //     java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
        objectMapper.activateDefaultTyping(
                objectMapper.getPolymorphicTypeValidator(),
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);

        // 添加Security提供的Jackson Mixin
        objectMapper.registerModule(new CoreJackson2Module());

        // 存入redis时序列化值的序列化器
        Jackson2JsonRedisSerializer<Object> valueSerializer =
                new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

        // 设置值序列化
        redisTemplate.setValueSerializer(valueSerializer);
        // 设置hash格式数据值的序列化器
        redisTemplate.setHashValueSerializer(valueSerializer);
        // 默认的Key序列化器为:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(stringRedisSerializer);
        // 设置字符串序列化器
        redisTemplate.setStringSerializer(stringRedisSerializer);
        // 设置hash结构的key的序列化器
        redisTemplate.setHashKeySerializer(stringRedisSerializer);

        // 设置连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);

        return redisTemplate;
    }

    /**
     * 操作hash的情况下使用
     *
     * @param connectionFactory redis链接工厂
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<Object, Object> redisHashTemplate(RedisConnectionFactory connectionFactory) {

        return redisTemplate(connectionFactory);
    }

}

扩展说明

从上边的配置可以看出Spring对于三方框架内部没有默认构造器的类的反序列化支持还是挺好的,如果集成其它框架时遇到这种情况时就可以仿照Security框架提供的Mixin类实现一个自己的Mixin类以支持反序列化,当然也可以找找在框架中是否有类似的Jackson2Module类;当自己封装一个starter时也可以提供Jackson2Module类来映射类,不过这个就按照个人的喜好来了,自己封装自由度很高的。

当然在使用Security时如果遇到其它反序列化失败的类,都可以在框架中找找有没有其它的Jackson2Module类,如果提供的有那就不用自己封装了,直接往ObjectMapper添加一个Module就行。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
好的,以下是几个 Jackson Mixin 的功能示例: 1. 为某个类添加序列序列规则: ``` @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "dog"), @JsonSubTypes.Type(value = Cat.class, name = "cat") }) public abstract class AnimalMixin { @JsonCreator public AnimalMixin(@JsonProperty("name") String name) {} @JsonProperty("name") abstract String getName(); } ``` 2. 为某个属性添加序列序列规则: ``` public class Person { private String name; private int age; // getters and setters } public interface PersonMixin { @JsonProperty("fullName") String getName(); @JsonIgnore int getAge(); } ObjectMapper mapper = new ObjectMapper(); mapper.addMixIn(Person.class, PersonMixin.class); ``` 3. 为某个类添加自定义序列序列规则: ``` public class User { private String username; private String password; // getters and setters } public abstract class UserMixin { @JsonCreator public UserMixin(@JsonProperty("username") String username, @JsonProperty("password") String password) {} @JsonProperty("username") abstract String getUsername(); @JsonProperty("password") abstract String getPassword(); } public class UserDeserializer extends StdDeserializer<User> { public UserDeserializer() { super(User.class); } @Override public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = p.getCodec().readTree(p); String username = node.get("username").asText(); String password = node.get("password").asText(); return new User(username.toUpperCase(), password); } } ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.setMixInAnnotation(User.class, UserMixin.class); module.addDeserializer(User.class, new UserDeserializer()); mapper.registerModule(module); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

天玺-vains

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

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

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

打赏作者

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

抵扣说明:

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

余额充值