报错信息
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `org.springframework.security.core.authority.SimpleGrantedAuthority`
(although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"{"@class":"com.xgj.application.domain.LoginUser","user":{"@class":"com.xgj.application.entity.User","id":2,"userName":"user1","nickName":"One",
"password":"$2a$10$zu9.95bseAFDpVGfjTv/XejhEGJhXdA8EV9mZaGpfHX6THQGYOPNy","status":"0","email":"user1@example.com","phoneNumber":"12345678901","sex":"1","avatar":"avatar_url1","userType":"1","createBy":null,"createTime":["java.util.Date",1723900660000],"updateBy":null,"updateTime":["java.util.Date",1723900660000],"delFlag":0},
"permissions":["java.util.Arr"
[truncated 595 bytes]; line: 1, column: 670] (through reference chain: com.xgj.application.domain.LoginUser["authorities"]->java.util.ArrayList[0]); nested exception is
实体类中所有的有返回值的方法都会将返回的值序列化,但是反序列化时是根据set方法来实现的,所以当实体类中有非get,set方法的方法有返回值时,反序列化时就会出错。
例如
public class User implements UserDetails {
public String userid;
public String Uusername;
public String Ppassword;
public String role;
public String getUusername() {
return Uusername;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public void setUusername(String uusername) {
Uusername = uusername;
}
public String getPpassword() {
return Ppassword;
}
public void setPpassword(String ppassword) {
Ppassword = ppassword;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return Ppassword;
}
@Override
public String getUsername() {
return Uusername;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
在实体类user中只有userid,Uusername, Ppassword,role这四个属性都有get,set方法,但是其他方法有返回值,所以在序列化时,其他方法返回的值也会被序列化存在缓存中,此时不会出错,但是从缓存中反序列化时就因为有些值没有set方法,所以会出错。
解决方法一:
实体类中只放get,set方法或返回值为空的方法。
解决方法二:
但有时会碰到必须要有有返回值的其他方法,例如在整合security时自定义登录认证和权限认证时需要继承UserDetail,就必须有其他方法,这时候可以RedisTemplate的设置里加
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);具体如在下方
package com.xgj.application.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(connectionFactory);
//序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//om.activateDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 过期方法,采用下面代替
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
解决方法三:就是在有返回值的非get,set方法中加@JsonIgnore
package com.xgj.application.domain;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.xgj.application.entity.User;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @Auther: xgj
* @Desc:
* @Create: 2024/8/17
* @Verson: 1.0
*/
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions;
// 用户的权限列表,通常是GrantedAuthority的集合
private Collection<? extends GrantedAuthority> authorities;
// 实现UserDetails接口所需的方法
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
if (CollectionUtil.isNotEmpty(authorities)) {
return authorities;
}
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<>();
for (String permission : permissions) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
grantedAuthorities.add(simpleGrantedAuthority);
}
authorities = grantedAuthorities;
return authorities;
}
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true; // 假设status >= 0表示账户未过期
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true; // 假设账户总是未锁定的
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true; // 假设凭证总是未过期的
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true; // 假设status == 0表示账户启用
}
}
附上加了@JsonIgnore注解后,Redis中存的值没有那些isEnabled,isAccountNonLocked等值。Redis存的值如下
{"@class":"com.xgj.application.domain.LoginUser","user":{"@class":"com.xgj.application.entity.User","id":2,"userName":"user1",
"nickName":"One","password":"$2a$10$zu9.95bseAFDpVGfjTv/XejhEGJhXdA8EV9mZaGpfHX6THQGYOPNy","status":"0","email":"user1@example.com","phoneNumber":"12345678901","sex":"1",
"avatar":"avatar_url1","userType":"1","createBy":null,"createTime":["java.util.Date",1723900660000],"updateBy":null,"updateTime":["java.util.Date",1723900660000],"delFlag":0},"permissions":["java.util.ArrayList",["ROLE_test","ROLE_admin","ROLE_supadmin"]]}
tips: 权限信息(authorities)不会存到缓存Redis中获取值后permissions时会set到authorities中去。从Redis获取到的值如下
(user=User(id=2, userName=user1, nickName=One, password=$2a$10$zu9.95bseAFDpVGfjTv/XejhEGJhXdA8EV9mZaGpfHX6THQGYOPNy, status=0, email=user1@example.com, phoneNumber=12345678901, sex=1, avatar=avatar_url1, userType=1, createBy=null, createTime=Sat Aug 17 21:17:40 CST 2024, updateBy=null, updateTime=Sat Aug 17 21:17:40 CST 2024, delFlag=0), permissions=[ROLE_test, ROLE_admin, ROLE_supadmin], authorities=[ROLE_test, ROLE_admin, ROLE_supadmin])
Spring Boot低版本可以试一下加如下注解。
参考了很多博客总结出来的,感谢所有分享技术博客的朋友们。