Redis反序列化的一次问题

2024-01-07 23:49:48

redis反序列化的一次问题

1. 问题描述

springboot+redis不少用,但是一直没遇到什么问题,直接代码拷贝上去就用了。这次结合spring-security,将自定义的spring-security的UserDetails接口的实现类SecurityUser,反序列化取出时报错。

在这里插入图片描述

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `com.jankin.inoteadmin.security.entity.SecurityUser` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
	......
org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:165)

2. 问题解读

我猜肯定有人骂我了,上面问题描述不是很清晰了吗 no Creators, like default constructor, exist 。我的回答是,该骂,大骂特骂~

作为一名不称职的代码搬砖工,我遇到问题时,第一是间是查看是否实现了Serializable接口。确认父接口UserDetails实现了该接口还不死心,然后在本类中添加该接口,保存运行,报错依旧。慌了,按道理没道理啊,之前项目也是直接这样引依赖,建个RedisConfig类就行了啊,然后就打开百度……

结果还百度不出……(估计是没有人连这种问题需要百度,所以搜不到)

好,言归正传,问题就如上述所说,没有默认够构造器,也就是无参构造器。

我依稀记得大学教我java的老师,说到构造器时,反复强调,无论你建多少个有参构造器,都必须带上无参构造器,毕业后其实一直遵守老师所说的,每次建造构造方法时,都会建无参构造方法,lombok的 @AllArgsConstructor@NoArgsConstructor我一直都是连着用的。这次没用不是因为忘记了,而是SpringSecurity中自带的UserDetails实现类org.springframework.security.core.userdetails.User 本身就没带无参构造器,并且,其中的类属性还带上了final关键字,意思是在声明时或者构造方法中必须定义初始值。

好嘛,SpringSecurity默认的User都没问题,我自定义的SecurityUser仿照应该问题也不大。

然后就出这档子事儿了。

扯远了,总而言之啊,就是spring中redis的反序列化需要依赖无参构造器

3. 问题解决

那就加上无参构造器呗,SpringSecurity的自定义的SecurityUser还需要将对象属性的final关键字去掉。

public class SecurityUser implements UserDetails{

    private static final long serialVersionUID = 1L;

    private String userId;
    private String password;
    private String username;
    private Set<GrantedAuthority> authorities;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;
    private boolean enabled;
    
    /**
     * 无参构造器
     */
    public SecurityUser(){}

    /**
     * Simple Param Constructor
     * @param userId 用户id
     * @param password  用户密码
     * @param username  用户名
     * @param authorities   权限列表
     */
    public SecurityUser(String userId, String username, String password, Set<GrantedAuthority> authorities) {
        this(userId,username,password,authorities,
                true,true,true,true);
    }

    /**
     * AllParamConstructor
     * @param userId 用户id
     * @param password  用户密码
     * @param username  用户名
     * @param authorities   权限列表
     * @param accountNonExpired 账号没过期
     * @param accountNonLocked  账号没被锁
     * @param credentialsNonExpired 凭证没过期
     * @param enabled   正常
     */
    public SecurityUser(String userId, String username, String password, Set<GrantedAuthority> authorities,
                        boolean accountNonExpired, boolean accountNonLocked,
                        boolean credentialsNonExpired, boolean enabled) {
        this.userId = userId;
        this.username = username;
        this.password = password;
        this.authorities = Collections.unmodifiableSet(authorities);
        this.accountNonExpired = accountNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.credentialsNonExpired = credentialsNonExpired;
        this.enabled = enabled;
    }
    // other todo
}

一点点题外话

作者还没做过什么大型项目,单体应用上看,前后端分离的情况下SpringSecurity的认证和授权根本上就是两个Filter和AOP做注解鉴权就行,感觉没有必要用SpringSecurity,之前用的shiro,也用过sa-token,相比之下,SpringSecurity是真的复杂,前后端分离情况下,SpringSecurity的UserDetailsService也拉,不好做缓存认证。

说到SpringSecurity的认证,网上很多方案都是使用jwt做token认证标识,对于jwt做下线功能该怎么做呢,jwt一旦颁发就只能到期失效。这样token被盗用的话一点办法都没有。有人说在缓存或数据库中添加signature的加密串,或者用账户密码做加密串,这样下线时或者更改密码时修改加密串就行……这样做确实能解决jwt的失效问题,但是jwt的主要特点(优点)就是无状态,每次认证只需要依赖jwt本体就行,这样数据库或者缓存添加jwt的加密串,岂不是违背了原本jwt的初衷?这样和使用普通token,用token做key缓存用户登录状态信息有什么区别。

虽然言语略带偏激,但是确实是心中疑问,有朋友看到且有见解的话,还请指教一下。

文章来源:https://blog.csdn.net/weixin_42510217/article/details/135446519
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。