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() } } } }
可能的错误原因及排查方向
JWT元数据格式错误
RSocket的JWT认证要求元数据内容为Bearer <token>格式,而非直接传入Token字符串。当前测试代码中直接传递了token值,缺少Bearer前缀,导致Spring Security无法正确解析认证信息。修改测试代码中的元数据部分:.metadata("Bearer ${token.token!!}", authenticationMimeType)授权规则顺序问题
配置中的.anyRequest().authenticated().anyExchange().permitAll()顺序有误,anyRequest()已经匹配所有请求并要求认证,后续的anyExchange().permitAll()不会生效。如果需要开放某些路由,应该先配置具体路由的授权规则,再设置默认规则:authorize .route("api.v1.messages.stream").authenticated() .anyExchange().permitAll()JWT转换器与Token生成不匹配
检查TokenUtils中JWT的生成逻辑,确保:- 签名算法与
jwtDecoder()中使用的一致(比如都用HS256) - Token中包含正确的权限字段(默认JwtGrantedAuthoritiesConverter会读取
scope或scp字段,若你的权限存储在其他字段,需要自定义转换器) - 解码器使用的密钥与生成Token的密钥完全一致
- 签名算法与
@AuthenticationPrincipal类型不匹配
控制器中@AuthenticationPrincipal jwt: String的类型错误,默认情况下,Spring Security会将认证后的Principal解析为Jwt对象,而非字符串。修改为:@AuthenticationPrincipal jwt: Jwt若需要获取Token字符串,可以通过
jwt.tokenValue获取。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

