shiro使用Jackson2JsonRedisSerializer整合redis实现认证与授权的坑

一、当认证与授权数据缓存至redis后,进行反序列化时,会出现反序列化失败:

shiro使用Jackson2JsonRedisSerializer整合redis实现认证与授权的坑_第1张图片

 分析
提示很清楚,就是shiro的SimplePrincipalCollection类中realmNames字段没有setter方法,没法反序列化。
来看看realmNames是什么,作为成熟的框架为什么不写setter?仔细一看,发现并不那么回事。类里面没有realmNames,只有个getRealmNames方法。
原来是个假getter,是由其他字段动态生成的,如下:

public Set getRealmNames() {
    if (realmPrincipals == null) {
        return null;
    } else {
        return realmPrincipals.keySet();
    }
}

看下redis里面存的值

 这个get方法被序列化了,并存到redis了,然后在反序列化的时候,又没有setter,就报了异常
尝试在反序列化时忽略
本以为objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)就能解决,但是跟踪了一圈源码之后,发现因为有getter,这个字段已经不算未知字段了。。。
其实这样的字段序列化和反序列化也没啥意义,仔细研究这个类之后,发现整个类值得存储的字段就只有realmPrincipals。
序列化时去掉无关字段
既然不能反序列化的时候解决,那就在序列化的时候控制需要序列化的字段就可以了。
常规的方式是在不需要的属性上面加注解@JsonIgnore就ok的,就是那么简单也办不到,因为这个类是框架jar包的类无法修改。那就只有寻找不常规的方法了。
最后终于找到了,ObjectMapper配置如下。

  @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        ObjectMapper objectMapper = ApplicationContextUtils.getBean(ObjectMapper.class).copy();

        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
        //设置输入时忽略JSON字符串中存在而Java对象实际没有的属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

        //只序列化必要shiro字段
        String[] needSerialize = {"realmPrincipals"};
        objectMapper.addMixIn(SimplePrincipalCollection.class, IncludeShiroFields.class);
        objectMapper.setFilterProvider(new SimpleFilterProvider().addFilter("shiroFilter", SimpleBeanPropertyFilter.filterOutAllExcept(needSerialize)));

        // 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 设置value的序列化规则和key的序列化规则
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    } 
  

核心就是objectMapper.addMixIn()和objectMapper.setFilters()两个方法
SimplePrincipalCollection是需要处理的类,IncludShiroFields就是一个简单的接口,如下:

@JsonFilter("shiroFilter")
interface IncludeShiroFields {

}

通过上面的配置间接控制SimplePrincipalCollection类中必要字段的序列化,从而解决了问题。

二、当认证与授权数据缓存至redis时,盐不能序列化:

shiro使用Jackson2JsonRedisSerializer整合redis实现认证与授权的坑_第2张图片

 分析

进入ByteSource类中,可以清楚的看到,该类没有实现Serializable,导致redis无法序列化至缓存。

shiro使用Jackson2JsonRedisSerializer整合redis实现认证与授权的坑_第3张图片

解决办法就是自定义加盐工具类,实现Serializable,代码如下:

public class SaltSourceUtils implements ByteSource, Serializable {

    private byte[] bytes;

    private String cachedHex;

    private String cachedBase64;

    public SaltSourceUtils() {

    }

    private SaltSourceUtils(byte[] bytes) {
        this.bytes = bytes;
    }

    public SaltSourceUtils(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    private SaltSourceUtils(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public SaltSourceUtils(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public SaltSourceUtils(File file) {
        this.bytes = (new SaltSourceUtils.BytesHelper()).getBytes(file);
    }

    public SaltSourceUtils(InputStream stream) {
        this.bytes = (new SaltSourceUtils.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    @Override
    public byte[] getBytes() {
        return this.bytes;
    }

    @Override
    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    @Override
    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    @Override
    public String toBase64() {
        if (this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    @Override
    public String toString() {
        return this.toBase64();
    }

    @Override
    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof ByteSource) {
            ByteSource bs = (ByteSource) o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    public static SaltSourceUtils bytes(byte[] bytes){
        return new SaltSourceUtils(bytes);
    }

    public static SaltSourceUtils bytes(String string){
        return new SaltSourceUtils(string);
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }

}

 在使用时只需要将加盐工具类换成自定义的即可:

shiro使用Jackson2JsonRedisSerializer整合redis实现认证与授权的坑_第4张图片

 

你可能感兴趣的:(后端,安全,shiro,redis,缓存,数据库)