Skip to content

SwitchUserGrantedAuthority cannot be deserialized in Webflux #17041

Closed
@JohnNiang

Description

@JohnNiang

Describe the bug

I used SwitchUserWebfilter to implement an impersonation function, but it didn't work very well, pleas see the log below:

I found a similar issue in #11775, but the PR #11758 only resolved in Servlet.

Halo  | Caused by: com.fasterxml.jackson.databind.JsonMappingException: The class with org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority and name of org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"])
Halo  | 	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:401) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:360) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1964) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:401) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:220) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:170) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:136) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1382) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4931) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3970) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at org.springframework.data.redis.serializer.JacksonObjectReader.lambda$create$0(JacksonObjectReader.java:54) ~[spring-data-redis-3.4.5.jar:3.4.5]
Halo  | 	at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:309) ~[spring-data-redis-3.4.5.jar:3.4.5]
Halo  | 	at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:281) ~[spring-data-redis-3.4.5.jar:3.4.5]
Halo  | 	at org.springframework.data.redis.serializer.DefaultRedisElementReader.read(DefaultRedisElementReader.java:46) ~[spring-data-redis-3.4.5.jar:3.4.5]
Halo  | 	at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.read(RedisSerializationContext.java:277) ~[spring-data-redis-3.4.5.jar:3.4.5]
Halo  | 	at org.springframework.data.redis.core.DefaultReactiveHashOperations.readHashValue(DefaultReactiveHashOperations.java:292) ~[spring-data-redis-3.4.5.jar:3.4.5]
Halo  | 	at org.springframework.data.redis.core.DefaultReactiveHashOperations.deserializeHashEntry(DefaultReactiveHashOperations.java:307) ~[spring-data-redis-3.4.5.jar:3.4.5]
Halo  | 	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106) ~[reactor-core-3.7.5.jar:3.7.5]
Halo  | 	at reactor.core.publisher.FluxFlatMap$FlatMapMain.tryEmit(FluxFlatMap.java:547) ~[reactor-core-3.7.5.jar:3.7.5]
Halo  | 	at reactor.core.publisher.FluxFlatMap$FlatMapInner.onNext(FluxFlatMap.java:988) ~[reactor-core-3.7.5.jar:3.7.5]
Halo  | 	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.7.5.jar:3.7.5]
Halo  | 	at io.lettuce.core.RedisPublisher$ImmediateSubscriber.onNext(RedisPublisher.java:895) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.RedisPublisher$RedisSubscription.onNext(RedisPublisher.java:295) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.output.StreamingOutput$Subscriber.onNext(StreamingOutput.java:49) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.output.KeyValueListOutput.set(KeyValueListOutput.java:61) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.RedisStateMachine.safeSet(RedisStateMachine.java:814) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.RedisStateMachine.handleBytes(RedisStateMachine.java:601) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.RedisStateMachine$State$Type.handle(RedisStateMachine.java:210) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.RedisStateMachine.doDecode(RedisStateMachine.java:363) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.RedisStateMachine.decode(RedisStateMachine.java:324) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:844) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.CommandHandler.decode0(CommandHandler.java:795) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:769) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:661) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:601) ~[lettuce-core-6.4.2.RELEASE.jar:6.4.2.RELEASE/f4dfb40]
Halo  | 	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357) ~[netty-transport-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868) ~[netty-transport-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799) ~[netty-transport-classes-epoll-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:501) ~[netty-transport-classes-epoll-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:399) ~[netty-transport-classes-epoll-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998) ~[netty-common-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.119.Final.jar:4.1.119.Final]
Halo  | 	at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
Halo  | Caused by: java.lang.IllegalArgumentException: The class with org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority and name of org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
Halo  | 	at org.springframework.security.jackson2.SecurityJackson2Modules$AllowlistTypeIdResolver.typeFromId(SecurityJackson2Modules.java:293) ~[spring-security-core-6.4.5.jar:6.4.5]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:159) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:151) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:136) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:240) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializerNR.deserializeWithType(UntypedObjectDeserializerNR.java:112) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4904) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3036) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at org.springframework.security.jackson2.AbstractUnmodifiableCollectionDeserializer.deserialize(AbstractUnmodifiableCollectionDeserializer.java:51) ~[spring-security-core-6.4.5.jar:6.4.5]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:123) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromArray(AsArrayTypeDeserializer.java:56) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserializeWithType(CollectionDeserializer.java:285) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4904) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3061) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenDeserializer.deserialize(UsernamePasswordAuthenticationTokenDeserializer.java:78) ~[spring-security-core-6.4.5.jar:6.4.5]
Halo  | 	at org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenDeserializer.deserialize(UsernamePasswordAuthenticationTokenDeserializer.java:51) ~[spring-security-core-6.4.5.jar:6.4.5]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:170) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:136) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:138) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:399) ~[jackson-databind-2.18.3.jar:2.18.3]
Halo  | 	... 48 common frames omitted

To Reproduce

  1. Add additional web filter: SwitchUserWebFilter
        var filter = new SwitchUserWebFilter(userDetailsService, "/", "/login?error=impersonate");
        http.addFilterAfter(filter, SecurityWebFiltersOrder.AUTHORIZATION);
  1. Add spring-session-data-redis dependency
    implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'
    implementation 'org.springframework.session:spring-session-data-redis'
  1. Start a Redis instance
  2. Try to switch user
  3. See the error

Expected behavior

Should work with session deserialization.

I'm willing to propose a PR to resolve the issue.

Sample

import static org.junit.jupiter.api.Assertions.assertInstanceOf;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
import org.springframework.security.web.server.jackson2.WebServerJackson2Module;

class SwitchUserAuthorityTest {

    @Test
    void deserializeTest() throws JsonProcessingException {
        var objectMapper = Jackson2ObjectMapperBuilder.json()
            .modules(SecurityJackson2Modules.getModules(this.getClass().getClassLoader()))
            .indentOutput(true)
            .build();

        var authentication = UsernamePasswordAuthenticationToken.authenticated(
            "admin", "openadmin", AuthorityUtils.createAuthorityList("ROLE_ADMIN")
        );
        var switchUserGrantedAuthority =
            new SwitchUserGrantedAuthority("ADMIN", authentication);
        var extendedAuthorities = new ArrayList<>(authentication.getAuthorities());
        extendedAuthorities.add(switchUserGrantedAuthority);
        authentication = UsernamePasswordAuthenticationToken.authenticated(
            authentication.getPrincipal(), authentication.getCredentials(), extendedAuthorities
        );

        var json = objectMapper.writeValueAsString(authentication);
        var resolved = objectMapper.readValue(json, Authentication.class);
        assertInstanceOf(UsernamePasswordAuthenticationToken.class, resolved);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: webAn issue in web modules (web, webmvc)status: duplicateA duplicate of another issuetype: enhancementA general enhancement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions