实现UserDetails导致的Jackson的序列化与反序列化问题

首先我还是以一个我遇到的新的问题来引发相关的讨论:

@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "SystemUser对象", description = "")
public class SystemUser implements Serializable, UserDetails {
........省略一些私有的字段
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

   // private void setAuthorities(Collection ignored){} 没有解决反序列化问题


    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled==0;
    }
}

在这个代码片段中,我使用了User实体类去实现了SpringSecurity中的UserDetails接口,为了方便,将用户表的实现类同时作为安全框架中用户的详细信息类,可以看到在UserDetails接口源码中主要有五个方法:

									```java
									public interface UserDetails extends Serializable {
									   //获取用户权限列表,且返回的权限必须带有"ROLE_"开头
									    Collection getAuthorities();
									   //获取密码
									    String getPassword();
									   //获取用户名
									    String getUsername();
									   //账户未过期
									    boolean isAccountNonExpired();
									   //账户未锁定
									    boolean isAccountNonLocked();
									   //凭证是否过期
									    boolean isCredentialsNonExpired();
									   //用户是否启用
									    boolean isEnabled();
									}	
									```

当我实现接口时,同样在SystemUser类中也重写了这五个方法,且以public修饰,而我们的用户类SystemUser本身是需要在网络中传输的,所以它实现了序列化接口Serializable,到目前为止这看似没有任何问题。

但是当我写好了用户的添加接口,使用swagger进行添加用户操作时就出问了:看看请求体的内容

	{
				  "accountNonExpired": true,
				  "accountNonLocked": true,
				  "authorities": [
				    {
				      "authority": "string"
				    }
				  ],
				  "createTime": "2022-05-06T10:54:48.786Z",
				  "credentialsNonExpired": true,
				  "email": "290789798@qdqoidh",
				  "enabled": 0,
				  "headPortrait": "/loacalhost/protrait/",
				  "id": 1,
				  "lastPasswordResetTime": "2022-05-06T10:54:48.786Z",
				  "nickName": "张三",
				  "password": "123456",
				  "phone": "18324182141",
				  "sex": "男",
				  "username": "zkboort"
				}			
	首先我们发现请求对象中,它多了三个属性,分别对应了UserDetails的用户未过期、用户未锁定、和一个用户权限列表数组;其他的属性都是我们用户实体类的属性。当我们填好信息点击测试接口时,我却得到了如下的返回结果:
{
  "timestamp": "2022-05-06T13:56:51.675+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/systemUser/addSystemUser"
}

状态码是500,说明是后端的问题,我们将目光回到后台控制台看看信息:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Problem deserializing 'setterless' property 'authorities': get method returned null
 at [Source: (PushbackInputStream); line: 4, column: 18] (through reference chain: com.example.carleasingclub_monolith.entity.SystemUser["authorities"])
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.12.5.jar:2.12.5]
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764) ~[jackson-databind-2.12.5.jar:2.12.5]
	at com.fasterxml.jackson.databind.deser.impl.SetterlessProperty.deserializeAndSet(SetterlessProperty.java:130) ~[jackson-databind-2.12.5.jar:2.12.5]
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:402) ~[jackson-databind-2.12.5.jar:2.12.5]
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:195) ~[jackson-databind-2.12.5.jar:2.12.5]
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322) ~[jackson-databind-2.12.5.jar:2.12.5]
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593) ~[jackson-databind-2.12.5.jar:2.12.5]
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3601) ~[jackson-databind-2.12.5.jar:2.12.5]
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:378) ~[spring-web-5.3.12.jar:5.3.12]
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342) ~[spring-web-5.3.12.jar:5.3.12]
	at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:185) ~[spring-webmvc-5.3.12.jar:5.3.12]
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:160) ~[spring-webmvc-5.3.12.jar:5.3.12]
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:133) ~[spring-webmvc-5.3.12.jar:5.3.12]
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-5.3.12.jar:5.3.12]
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179) ~[spring-web-5.3.12.jar:5.3.12]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146) ~[spring-web-5.3.12.jar:5.3.12]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.12.jar:5.3.12]
	此时遇到一个奇怪的序列化转换失败的问题,根据信息'setterless' property 'authorities': get method returned null,我大致猜测是由于权限数组的反序列化问题,"serterless“表明可能与set()和get()方法有关,这里的反序列化是由jackson去做的,所以在分析为什么这样做会出现问题之前,需要了解jsckson。

## jackson概述

	Jackson是一个用于实现javaBean对象与JSON数据格式相互转换的工具,这样的常见的工具有四个:Jsonlib,Gson,fastjson,jackson。jackson使用其内部的readValue(json对象)方法将json数据格式转化为java对象,writeValue(参数1,Obj)将Obj对象转换为json对象并以参数1(File,Writer,OutPutString)指定的形式输出。
	jackson在做java对象转json数据格式时,要求Java对象是一个POJO,即javaBean对象,在这个javaBean对象当中的字段如果是使用private进行修饰的话,则必须有对应的getXXX()方法,否则使用public修饰。	
   **jackson将javaBean对象中的每一个字段属性映射为json的key,而将字段属性的getXXX()和setXXX()方法用来决定json的value**。json对象的值大致可以分为基本类型、数组、字典,他们应该分别对应java对象的基本类型、数组、链表。
   jackson的核心类是class ObjectMapper,其也实现了Versioned, Serializable 两个接口,这个类有许多的属性:

public class ObjectMapper extends ObjectCodec implements Versioned, Serializable {
private static final long serialVersionUID = 2L;
//
protected static final AnnotationIntrospector DEFAULT_ANNOTATION_INTROSPECTOR = new JacksonAnnotationIntrospector();
//
protected static final BaseSettings DEFAULT_BASE;
//
protected final JsonFactory _jsonFactory;
//
protected TypeFactory _typeFactory;
//
protected InjectableValues _injectableValues;
//
protected SubtypeResolver _subtypeResolver;
//
protected final ConfigOverrides _configOverrides;
//
protected final CoercionConfigs _coercionConfigs;
//
protected SimpleMixInResolver _mixIns;
//
protected SerializationConfig _serializationConfig;
//
protected DefaultSerializerProvider _serializerProvider;
//
protected SerializerFactory _serializerFactory;
//
protected DeserializationConfig _deserializationConfig;
//
protected DefaultDeserializationContext _deserializationContext;
//
protected Set _registeredModuleTypes;
//
protected final ConcurrentHashMap _rootDeserializers;
}

问题分析和解决方案

了解了jackson基本的特征后,针对上面的将json请求体的内容转换为java对象的反序列化问题,我们知道是请求体中的”authorities“数组,在实体类中缺少对应的setter方法导致反序列失败。
@Override public Collection getAuthorities() { return null; }
事实上最有可能的问题是,getAuthorites()方法指定返回的是一个权限Collection<>,返回值 return null有没有问题呢?,在通常情况下这其实没有什么问题,但是这里不能这样,jackson的key有相应的对应,java的Collection集合对象在前端json数据格式中对应的是一个数组对象,我们使用反序列化将json请求体中的元素:
“authorities”: [
{
“authority”: “string”
}
],

反序列化为Java对象时,如果Java实体类中的方法最终返回的是null值,他与前端的数组对象是不匹配的,所以真正的问题并不在于我们的反序列化方法依赖于javaBean对象的setter,而是此处的返回值导致的反序列化类型不匹配问题。
根据 UserDetails接口的getAuthorities()方法,我们的返回值应当是一个权限列表集合,且集合的元素皆是以”ROLE_“开头,在开发此用户相关的业务时,应当将权限角色等提前开发好,用户对象中具备权限相关的属性,保证当前用户对象可以获取到权限属性列表,
并且,重写Overide public Collection getAuthoritesa()方法时,需要在方法体中去获取当前用户对象的权限列表,以List<>,接收后返回。
这样在测试的时候才能,带上当前请求的数组参数,并且成功反序列化为Java集合对象。

你可能感兴趣的:(java,开发语言,spring,boot)