Description
UsernamePasswordAuthenticationTokenDeserializer doesn't deserialize details to correct type
When using Spring Security and Spring Session with GenericJackson2JsonRedisSerializer, UsernamePasswordAuthenticationTokenDeserializer deserializes the details field of UsernamePasswordAuthenticationToken as a JsonNode, not the original Object such as WebAuthenticationDetails.
Actual Behavior
the details field of UsernamePasswordAuthenticationToken is deserialized as a com.fasterxml.jackson.databind.node.ObjectNode.
Expected Behavior
the details field of UsernamePasswordAuthenticationToken should be deserialized as a object of type @ class.
Configuration
I replace the default JdkSerializationRedisSerializer with GenericJackson2JsonRedisSerializer.
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// other beans and methods omitted
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModules(new CoreJackson2Module(), new WebJackson2Module());
return new GenericJackson2JsonRedisSerializer(mapper);
}
}
Version
I'm using Spring Security 4.2.2, but I also find the same issue in master branch.
Sample
The relevant code is as follows, and I add some comments in it.
class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<UsernamePasswordAuthenticationToken> {
@Override
public UsernamePasswordAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
UsernamePasswordAuthenticationToken token = null;
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
Boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
JsonNode principalNode = readJsonNode(jsonNode, "principal");
Object principal = null;
if(principalNode.isObject()) {
principal = mapper.readValue(principalNode.toString(), new TypeReference<User>() {});
} else {
principal = principalNode.asText();
}
Object credentials = readJsonNode(jsonNode, "credentials").asText();
List<GrantedAuthority> authorities = mapper.readValue(
readJsonNode(jsonNode, "authorities").toString(), new TypeReference<List<GrantedAuthority>>() {
});
if (authenticated) {
token = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
} else {
token = new UsernamePasswordAuthenticationToken(principal, credentials);
}
token.setDetails(readJsonNode(jsonNode, "details")); // it just makes details deserialized as a JsonNode
return token;
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}
the following code works.
JsonNode detailsNode = readJsonNode(jsonNode, "details");
Object details = mapper.readValue(detailsNode.toString(), new TypeReference<Object>() {});
token.setDetails(details);