Spring 无法使用 JWT 验证 RSocket 流,但能够验证响应请求

Spring unable to authenticate RSocket streams using JWT, while able to auth response requests

预期结果:从包含身份验证信息作为元数据的前端连接到基于 RSocket websocket 的端点将触发 PayloadSocketAcceptorInterceptor 的 jwt 身份验证系统。

实际结果:仅在从JS前端发送responseRequest时出现,在streamRequest发送时失败。没有错误。在下面的 类 中没有调用任何一种与身份验证相关的方法。我已经记录了所有这些。

RSocketConfig 代码:

@Configuration
@EnableRSocketSecurity
@EnableReactiveMethodSecurity
class RSocketConfig {
  @Autowired
  lateinit var rSocketAuthenticationManager: RSocketAuthenticationManager

  @Bean
  fun rSocketMessageHandler(strategies: RSocketStrategies?): RSocketMessageHandler? {
    val handler = RSocketMessageHandler()
    handler.argumentResolverConfigurer.addCustomResolver(AuthenticationPrincipalArgumentResolver())
    handler.rSocketStrategies = strategies!!
    return handler
  }

  @Bean
  fun authorization(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
    rsocket.authorizePayload { authorize: AuthorizePayloadsSpec ->
      authorize
          .route("flux-stream").authenticated()
          .anyRequest().authenticated()
          .anyExchange().permitAll()
    }
        .jwt { jwtSpec: RSocketSecurity.JwtSpec ->
          try {
            jwtSpec.authenticationManager(rSocketAuthenticationManager)
          } catch (e: Exception) {
            throw RuntimeException(e)
          }
        }

    return rsocket.build()
  }

  @Bean
  fun rSocketRequester(strategies: RSocketStrategies, props: RSocketProperties): Mono<RSocketRequester> =
      RSocketRequester.builder()
          .rsocketStrategies(strategies)
          .connectWebSocket(getUri(props))


  fun getUri(props: RSocketProperties): URI =
      URI.create(String.format("ws://localhost:${props.server.port}${props.server.mappingPath}"))
}

RSocketAuthenticationManager 代码:

@Component
class RSocketAuthenticationManager(): ReactiveAuthenticationManager {
  @Autowired
  lateinit var cognitoConfig: CognitoConfig

  @Override
  override fun authenticate(authentication: Authentication): Mono<Authentication> {
    val authToken: String = authentication.credentials.toString()

    try {
      return if(isTokenValid(authToken)) {
        val decoded = JWT.decode(authToken)
        decoded.claims.entries.forEach { (key, value) -> println("$key = ${value.asString()}") }
        val authorities: MutableList<GrantedAuthority> = ArrayList()
        println("authentication successful!")
        Mono.just(UsernamePasswordAuthenticationToken(decoded.subject, null, authorities))
      } else {
        println("invalid authentication token")
        Mono.empty<Authentication>();
      }
    } catch (e: Exception) {
      println("authentication errored")
      e.printStackTrace()
      return Mono.empty<Authentication>()
    }
  }

  @Throws(Exception::class)
  fun isTokenValid(token: String): Boolean {
    // code borrowed from
    // https://github.com/awslabs/cognito-proxy-rest-service/blob/2f9a9ffcc742c8ab8a694b7cf39dc5d8b3247898/src/main/kotlin/com/budilov/cognito/services/CognitoService.kt#L41

    // Decode the key and set the kid
    val decodedJwtToken = JWT.decode(token)
    val kid = decodedJwtToken.keyId

    val http = UrlJwkProvider(URL(cognitoConfig.jwksUrl))
    // Let's cache the result from Cognito for the default of 10 hours
    val provider = GuavaCachedJwkProvider(http)
    val jwk = provider.get(kid)

    val algorithm = Algorithm.RSA256(jwk.publicKey as RSAKey)
    val verifier = JWT.require(algorithm)
        .withIssuer(cognitoConfig.jwtTokenIssuer)
        .build() //Reusable verifier instance

    val jwt = try {
      verifier.verify(token)
    } catch (e: Exception) {
      false
    }

    return (jwt != null)
  }
}

与问题相关的依赖项:

implementation("org.springframework.boot:spring-boot-starter-webflux:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-websocket:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-configuration-processor:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-rsocket:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-integration:2.3.0.RELEASE")

implementation("org.springframework.boot:spring-boot-starter-security:2.3.0.RELEASE")
implementation("org.springframework.security:spring-security-rsocket:5.4.2")
implementation("org.springframework.security:spring-security-messaging:5.4.2")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:2.3.0.RELEASE")

implementation("com.auth0:java-jwt:3.3.0")
implementation("com.auth0:jwks-rsa:0.1.0")

我不太熟悉 Spring 安全性,所以我可能遗漏了一些明显的东西。

您应该有一个注释的服务方法来接收身份验证主体。

  @MessageMapping("runCommand")
  suspend fun runCommand(request: CommandRequest, rSocketRequester: RSocketRequester, @AuthenticationPrincipal jwt: String): Flow<CommandResponse> {

你能提取一个更简单的项目,你可以在 github 上分享它来解决它为什么不工作吗?

这里有一个完整的例子https://spring.io/blog/2020/06/17/getting-started-with-rsocket-spring-security

问题已解决,有需要的人请查看回购历史修复:https://github.com/Braffolk/spring-rsocket-stream-jwt-authentication/commits/master