You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot RSocket集成JWT Security遇Access Denied错误求助

Spring Boot RSocket集成JWT Security出现Access Denied错误排查

我在Spring Boot RSocket中集成基于JWT Token的Security功能时,遇到了Access Denied错误,没有其他可调试的有效信息,错误栈如下:

Access Denied.
ApplicationErrorException (0x201): Access Denied at
app//io.rsocket.exceptions.Exceptions.from(Exceptions.java:76) at
app//io.rsocket.core.RSocketRequester.handleFrame(RSocketRequester.java:261)
at
app//io.rsocket.core.RSocketRequester.handleIncomingFrames(RSocketRequester.java:211)
at
app//reactor.core.publisher.LambdaSubscriber.onNext(LambdaSubscriber.java:160)
at
app//io.rsocket.core.ClientServerInputMultiplexer$InternalDuplexConnection.onNext(ClientServerInputMultiplexer.java:248)
at
app//io.rsocket.core.ClientServerInputMultiplexer.onNext(ClientServerInputMultiplexer.java:129)
at
app//io.rsocket.core.ClientServerInputMultiplexer.onNext(ClientServerInputMultiplexer.java:48)
at
app//reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
at
app//reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:364)
at
app//reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:404)
at
app//reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:725)
at
app//reactor.netty.http.client.WebsocketClientOperations.onInboundNext(WebsocketClientOperations.java:161)
at
app//reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93)
at
app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at
app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at
app//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at
app//io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:327)
at
app//io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:299)
at
app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at
app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at
app//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at
app//io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at
app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at
app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at
app//io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at
app//io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at
app//io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:722)
at
app//io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:658)
at
app//io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:584)
at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496)
at
app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
at
app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at
app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base@11.0.14/java.lang.Thread.run(Thread.java:834)

相关代码

Security配置文件

@Configuration
@EnableRSocketSecurity
@EnableReactiveMethodSecurity
class SecurityConfig {

    @Bean
    fun authorization(rsocketSecurity: RSocketSecurity): PayloadSocketAcceptorInterceptor {
        val security: RSocketSecurity =
            rsocketSecurity.authorizePayload { authorize: RSocketSecurity.AuthorizePayloadsSpec ->
                authorize
                    .anyRequest().authenticated()
                    .anyExchange().permitAll()
            }
                .jwt { jwtSpec ->
                    jwtSpec.authenticationManager(jwtReactiveAuthenticationManager(jwtDecoder()))
                }
        return security.build()
    }

    @Bean
    fun jwtDecoder(): ReactiveJwtDecoder {
        return TokenUtils.jwtAccessTokenDecoder()
    }

    @Bean
    fun jwtReactiveAuthenticationManager(decoder: ReactiveJwtDecoder): JwtReactiveAuthenticationManager {
        val converter = JwtAuthenticationConverter()
        val authoritiesConverter = JwtGrantedAuthoritiesConverter()
        authoritiesConverter.setAuthorityPrefix("ROLE_")
        converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter)
        val manager = JwtReactiveAuthenticationManager(decoder)
        manager.setJwtAuthenticationConverter(ReactiveJwtAuthenticationConverterAdapter(converter))
        return manager
    }

    @Bean
    fun rsocketMessageHandler() = RSocketMessageHandler().apply {
        argumentResolverConfigurer.addCustomResolver(AuthenticationPrincipalArgumentResolver())
        routeMatcher = PathPatternRouteMatcher()
        rSocketStrategies = rsocketStrategies()
    }

    @Bean
    fun rsocketStrategies() = RSocketStrategies.builder()
        .routeMatcher(PathPatternRouteMatcher())
        .build()
}

Message控制器

@MessageMapping("api.v1.messages")
@Controller
class MessageController {

    @MessageMapping("stream")
    suspend fun receive(
        @Payload inboundMessages: Flow<String>,
        @AuthenticationPrincipal jwt: String
    ) {
        println("MessageController: jwt: $jwt")
        println("MessageController: inbound message: " + inboundMessages.first())
    }
}

测试类

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MessageControllerTest(
    @Autowired val rsocketBuilder: RSocketRequester.Builder,
    @LocalServerPort val serverPort: Int
) {

    @ExperimentalTime
    @ExperimentalCoroutinesApi
    @Test
    fun `test that messages API streams latest messages`() {
        val admin = HelloUser(userId = "9527", password = "password", role = HelloRole.ADMIN)

        val token: UserToken = TokenUtils.generateAccessToken(admin)!!

        val authenticationMimeType: MimeType =
            MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)

        runBlocking {
            val rSocketRequester = rsocketBuilder.websocket(URI("ws://localhost:${serverPort}/rsocket"))

            launch {

                rSocketRequester.route("api.v1.messages.stream")
                    .metadata(token.token!!, authenticationMimeType)
                    .dataWithType(flow {
                        emit(
                            "Hey from test class"
                        )
                    })
                    .retrieveFlow<Void>()
                    .collect()
            }
        }
    }
}

可能的错误原因及排查方向

  1. JWT元数据格式错误
    RSocket的JWT认证要求元数据内容为Bearer <token>格式,而非直接传入Token字符串。当前测试代码中直接传递了token值,缺少Bearer 前缀,导致Spring Security无法正确解析认证信息。修改测试代码中的元数据部分:

    .metadata("Bearer ${token.token!!}", authenticationMimeType)
    
  2. 授权规则顺序问题
    配置中的.anyRequest().authenticated().anyExchange().permitAll()顺序有误,anyRequest()已经匹配所有请求并要求认证,后续的anyExchange().permitAll()不会生效。如果需要开放某些路由,应该先配置具体路由的授权规则,再设置默认规则:

    authorize
        .route("api.v1.messages.stream").authenticated()
        .anyExchange().permitAll()
    
  3. JWT转换器与Token生成不匹配
    检查TokenUtils中JWT的生成逻辑,确保:

    • 签名算法与jwtDecoder()中使用的一致(比如都用HS256)
    • Token中包含正确的权限字段(默认JwtGrantedAuthoritiesConverter会读取scope或scp字段,若你的权限存储在其他字段,需要自定义转换器)
    • 解码器使用的密钥与生成Token的密钥完全一致
  4. @AuthenticationPrincipal类型不匹配
    控制器中@AuthenticationPrincipal jwt: String的类型错误,默认情况下,Spring Security会将认证后的Principal解析为Jwt对象,而非字符串。修改为:

    @AuthenticationPrincipal jwt: Jwt
    

    若需要获取Token字符串,可以通过jwt.tokenValue获取。

  5. RSocket元数据解析配置缺失
    确保RSocketStrategies正确配置了元数据编码器/解码器,Spring Security RSocket默认需要支持message/x.rsocket.authentication.v0类型的元数据解析,可在rsocketStrategies()中添加:

    .metadataExtractorRegistry { registry ->
        registry.metadataToExtract(
            WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION,
            String::class.java,
            AuthenticationPayload::class.java
        )
    }
    

内容的提问来源于stack exchange,提问作者CJMobileApps

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 15:33:34