Describe the bug Hello All, when using UsernamePasswordAuthenticationTokenDeserializer via CoreJackson2Module from spring-security-core-5.5.3, the principal gets serialized correctly, but it is always deserialized into the empty string ("").

To Reproduce

CustomPrincipal.java:

    static class CustomPrincipal {
        private final String name;

        public CustomPrincipal(String name) {
            this.name = name;
        }
    }

SerializerTest.java:

    @Test
    @SneakyThrows
    void mapSecurityContext() {
        // the mapper that cannot map the principal in the security context correctly
        var mapper = new JsonMapper();
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
        mapper.registerModule(new CoreJackson2Module());
        return mapper;

        // the security context with a custom princiapal
        var authorities = Set.of(new SimpleGrantedAuthority("ROLE_ADMIN"));
        var principal = new CustomPrincipal("name");
        var securityContext = new SecurityContextImpl(new UsernamePasswordAuthenticationToken(principal, null, authorities));

        // let's serialize and see how it goes (note that it is serialized correclty in the logs below)
        var serialized = mapper.writeValueAsString(securityContext);
        System.out.printf("Serialized: %s", serialized);

        // now let's try to deserialize the security context and compare with the initial one
        var deserialized = mapper.readValue(serialized.getBytes(), SecurityContextImpl.class);
        assertEquals(securityContext, deserialized);
    }

Output:

Serialized: ["org.springframework.security.core.context.SecurityContextImpl",{"authentication":["org.springframework.security.authentication.UsernamePasswordAuthenticationToken",{"authorities":["java.util.Collections$UnmodifiableRandomAccessList",[["org.springframework.security.core.authority.SimpleGrantedAuthority",{"authority":"ROLE_ADMIN"}]]],"details":null,"authenticated":true,"principal":["io.myproject.CustomPrincipal",{"name":"name"}],"credentials":null}]}]
org.opentest4j.AssertionFailedError: 
Expected :SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=io.myproject.CustomPrincipal@32a68f4f, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN]]]
Actual   :SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN]]]

Expected behavior The principal should be deserialized from ["io.myproject.CustomPrincipal",{"name":"name"}]

Analysis After some research it seems like it is impossible to deserialize any principal by design.

In UsernamePasswordAuthenticationTokenDeserializer.java:75 the principal is extracted with getPrincipal. Then, in the getPrincipal method there is the principalNode.isObject() check which always returns false since JsonNode.java:124. Thus, the method returns principalNode.asText() and this is always inherited from ContainerNode.java:40 which always returns the empty string ("").

Comment From: jgrandja

@PiotrSliwa Take a look at the test UsernamePasswordAuthenticationTokenMixinTests.deserializeAuthenticatedUsernamePasswordAuthenticationTokenWithUserTest() and you will see that User is correctly deserialized within UsernamePasswordAuthenticationToken.

The important configuration to note is that User has an associated Mixin (UserMixin) registered in CoreJackson2Module. You will also need to register a custom Mixin for CustomPrincipal.

I'm going to close this as this will work if you correctly register your custom Mixin.