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缓存用户登录状态信息有什么区别。
虽然言语略带偏激,但是确实是心中疑问,有朋友看到且有见解的话,还请指教一下。