比如我们有一个最简单的UserDetails实现类:
@Data
public class MyUser implements UserDetails {
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
}
编写一个测试
@Test
void test_2022_01_16_18_31_35() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
MyUser user = new MyUser();
user.setUsername("闰土");
user.setPassword("cha");
user.setAuthorities(Collections.singletonList(new SimpleGrantedAuthority("ADMIN")));
String jsonStr = objectMapper.writeValueAsString(user);
System.out.println(jsonStr);
objectMapper.readValue(jsonStr, MyUser.class);
}
输出为
{"username":"闰土","password":"cha","authorities":[{"authority":"ADMIN"}],"accountNonExpired":false,"accountNonLocked":false,"credentialsNonExpired":false,"enabled":false}
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.security.core.GrantedAuthority` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"username":"闰土","password":"cha","authorities":[{"authority":"ADMIN"}],"accountNonExpired":false,"accountNonLocked":false,"credentialsNonExpired":false,"enabled":false}"; line: 1, column: 50] (through reference chain: com.example.testdemo.MyUser["authorities"]->java.util.ArrayList[0])
...
进程已结束,退出代码为 -1
虽然MyUser可以被正确序列化,但是无法被正确反序列化,这是因为jackson在反序列化时,遇到GrantedAuthority
这个接口字段,不知道选择哪个实现类去构造对象。
当然我们也可以直接使用实现类如SimpleGrantedAuthority
。但实际上SimpleGrantedAuthority
本身也会引起同样的报错,这是因为SimpleGrantedAuthority
没有默认无参构造器。这时候我们就需要Mixin或者@JsonDeserialize了
巧的是,springSecurity本身内置了一个MixIn org.springframework.security.jackson2.SimpleGrantedAuthorityMixin
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class SimpleGrantedAuthorityMixin {
/**
* Mixin Constructor.
* @param role the role
*/
@JsonCreator
public SimpleGrantedAuthorityMixin(@JsonProperty("authority") String role) {
}
}
我们使用这个Mixin并将字段改为实现类试试
@Data
public class MyUser2 implements UserDetails {
private String username;
private String password;
private Collection<SimpleGrantedAuthority> authorities;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
}
我们在ObjectMapper 添加一行objectMapper.addMixIn(SimpleGrantedAuthority.class,SimpleGrantedAuthorityMixin.class);
@Test
void test_2022_01_16_18_31_35() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(SimpleGrantedAuthority.class,SimpleGrantedAuthorityMixin.class);
MyUser2 user = new MyUser2();
user.setUsername("闰土");
user.setPassword("cha");
user.setAuthorities(Collections.singletonList(new SimpleGrantedAuthority("ADMIN")));
String jsonStr = objectMapper.writeValueAsString(user);
System.out.println(jsonStr);
objectMapper.readValue(jsonStr, MyUser2.class);
}
{"username":"闰土","password":"cha","authorities":[{"@class":"org.springframework.security.core.authority.SimpleGrantedAuthority","authority":"ADMIN"}],"accountNonExpired":false,"accountNonLocked":false,"credentialsNonExpired":false,"enabled":false}
进程已结束,退出代码为 0
MyUser2正确序列化和反序列化了,但是json字符串里多了一个@class
的属性,这是因为官方提供的Mixin中有一个注解@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
这会将类的完全限定名作为属性包括在内,如果有多个GrantedAuthority
输出就会像
{
"username": "闰土",
"password": "cha",
"authorities": [
{
"@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
"authority": "ADMIN"
},
{
"@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
"authority": "ADMIN2"
},
{
"@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
"authority": "ADMIN3"
}
],
"accountNonExpired": false,
"accountNonLocked": false,
"credentialsNonExpired": false,
"enabled": false
}
如果我们想要去除这个@class
或者像MyUser直接使用GrantedAuthority
接口来兼容不同实现类,并且要求反序列化的时候根据属性自动转换为不同的实现类,我们需要一个自定义的JsonDeserializer
了
比如我们需要自动反序列化OidcUserAuthority
、OAuth2UserAuthority
我们知道OidcUserAuthority
有区别于其他实现类独有的字段userInfo
OAuth2UserAuthority
有区别于其他实现类独有的字段attributes
(OidcUserAuthority也有,所以我们需要按照先后顺序检测)
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.util.*;
/**
* 通用GrantedAuthority反序列化器,用于自动序列化其实现类
*/
@Slf4j
public class GenericAuthorityJsonDeserializer extends JsonDeserializer<GrantedAuthority> {
private static final Map<String, TypeReference<? extends GrantedAuthority>> GrantedAuthorityTypes = new LinkedHashMap<>();
private static final TypeReference<SimpleGrantedAuthority> defaultTypeReference = new TypeReference<SimpleGrantedAuthority>() {};
static {
ClassLoader classLoader = GenericAuthorityJsonDeserializer.class.getClassLoader();
if (ClassUtils.isPresent("org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority", classLoader)) {
GrantedAuthorityTypes.put("idToken", new TypeReference<OidcUserAuthority>() {});
}
if (ClassUtils.isPresent("org.springframework.security.oauth2.core.user.OAuth2UserAuthority", classLoader)) {
GrantedAuthorityTypes.put("attributes", new TypeReference<OAuth2UserAuthority>() {});
}
GrantedAuthorityTypes.put("authority", defaultTypeReference);
}
@Override
public GrantedAuthority deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
ObjectNode authorityNode = mapper.readTree(jsonParser);
return getGrantedAuthority(mapper, authorityNode);
}
@SuppressWarnings("unchecked")
private <T extends GrantedAuthority> T getGrantedAuthority(ObjectMapper mapper, ObjectNode authorityNode) {
for (String key : GrantedAuthorityTypes.keySet()) {
if (authorityNode.has(key)) {
return mapper.convertValue(authorityNode, (TypeReference<T>) GrantedAuthorityTypes.get(key));
}
}
return mapper.convertValue(authorityNode, (TypeReference<T>) defaultTypeReference);
}
}
这个GenericAuthorityJsonDeserializer
的作用就是检测是否有其特别字段来自动选择实现类,如果找不到就选择SimpleGrantedAuthority
作为默认实现类
然后我们实现一个SimpleModule
可以将反序列化和mixin一同注册,由于GenericAuthorityJsonDeserializer
解析的是接口,所以不能以Mixin类上加@JsonDeserialize(using = GenericAuthorityJsonDeserializer.class)
注解的方式注册,只能在JacksonModule中注册或者直接把@JsonDeserialize(using = GenericAuthorityJsonDeserializer.class)
放在对应字段上,否则会导致无限循环
public class GrantedAuthorityJacksonModule extends SimpleModule {
public GrantedAuthorityJacksonModule() {
super(GrantedAuthorityJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void setupModule(SetupContext context) {
ClassLoader classLoader = GrantedAuthorityJacksonModule.class.getClassLoader();
this.addDeserializer(GrantedAuthority.class,GenericAuthorityJsonDeserializer.class);
super.setupModule(context);
if (ClassUtils.isPresent("org.springframework.security.oauth2.core.user.OAuth2UserAuthority", classLoader)) {
context.setMixInAnnotations(OAuth2UserAuthority.class, OAuth2UserAuthorityMixin.class);
}
if (ClassUtils.isPresent("org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority", classLoader)) {
context.setMixInAnnotations(OidcUserAuthority.class, OidcUserAuthorityMixin.class);
}
if (ClassUtils.isPresent("org.springframework.security.core.authority.SimpleGrantedAuthority", classLoader)) {
context.setMixInAnnotations(SimpleGrantedAuthority.class, SimpleGrantedAuthorityMixin.class);
}
}
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract static class OAuth2UserAuthorityMixin {
@JsonCreator
OAuth2UserAuthorityMixin(@JsonProperty("authority") String authority,
@JsonProperty("attributes") Map<String, Object> attributes) {
}
}
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(value = {"attributes"}, ignoreUnknown = true)
abstract static class OidcUserAuthorityMixin {
@JsonCreator
OidcUserAuthorityMixin(@JsonProperty("authority") String authority, @JsonProperty("idToken") OidcIdToken idToken,
@JsonProperty("userInfo") OidcUserInfo userInfo) {
}
}
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract static class SimpleGrantedAuthorityMixin {
@JsonCreator
public SimpleGrantedAuthorityMixin(@JsonProperty("authority") String role) {
}
}
}
这样我们通过注册这个模块,就可以正确的自动选择实现类来序列化了
@Test
void test_2022_01_16_18_31_35() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new GrantedAuthorityJacksonModule());
MyUser user = new MyUser();
user.setUsername("闰土");
user.setPassword("cha");
user.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("ADMIN"), new SimpleGrantedAuthority("ADMIN2"), new SimpleGrantedAuthority("ADMIN3")));
String jsonStr = objectMapper.writeValueAsString(user);
System.out.println(jsonStr);
objectMapper.readValue(jsonStr, MyUser.class);
}
在springboot中,我们可以这样注册我们的模块:
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> builder.modules(new GrantedAuthorityJacksonModule());
}