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
预期结果:从包含身份验证信息作为元数据的前端连接到基于 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