spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ...
audiences:
- ...
@Configuration
@EnableWebSecurity
class SecurityConfig(
@Autowired(required = false) private val requestLoggingFilter: RequestLoggingFilter?
) {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { it.disable() }
.oauth2ResourceServer { oauth2 ->
oauth2.jwt { }
}
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/qr/**").authenticated()
.anyRequest().permitAll()
}
.sessionManagement { session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
....
return http.build()
}
@Configuration
@EnableWebSocketMessageBroker
class WebsocketConfig : WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(config: MessageBrokerRegistry) {
config.enableSimpleBroker("/topic")
config.setApplicationDestinationPrefixes("/app")
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/gs-guide-websocket")
}
}
...
@MessageMapping("/hello")
@SendTo("/topic/greetings")
@Throws(Exception::class)
fun greeting(message: Any): Greeting {
....
}
现在我尝试添加安全性,因此我将以下内容添加到安全配置...
@Configuration
@EnableWebSecurity
@EnableWebSocketSecurity
class SecurityConfig(
@Autowired(required = false) private val requestLoggingFilter: RequestLoggingFilter?
) {
...
@Bean
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
messages
.simpDestMatchers("/topic/**").authenticated()
return messages.build()
}
}
但是当我尝试打电话给类似的东西时
SEND
destination:/app/hello
Authorization: Bearer ey...
{"name":"test"}
我仍然得到
Caused by: org.springframework.security.access.AccessDeniedException: Access Denied
at org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor.preSend(AuthorizationChannelInterceptor.java:75) ~[spring-security-messaging-6.4.2.jar:6.4.2]
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:181) ~[spring-messaging-6.2.2.jar:6.2.2]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:135) ~[spring-messaging-6.2.2.jar:6.2.2]
... 44 common frames omitted
完全没有标题。同样的令牌在常规的网络端点上正常工作,所以我缺少什么?当我添加以下内容...
logging:
level:
org.springframework.security: DEBUG
org.springframework.messaging: DEBUG
org.springframework.web.socket: DEBUG
org.springframework.security.oauth2: TRACE
我将终点更改为ws,但我不知道
2025-02-02T10:47:16.609-05:00 DEBUG 88460 --- [love-monkey] [nio-7080-exec-3] o.s.w.s.s.s.WebSocketHttpRequestHandler : GET /ws
2025-02-02T10:47:16.641-05:00 DEBUG 88460 --- [love-monkey] [nio-7080-exec-3] s.w.s.h.LoggingWebSocketHandlerDecorator : New StandardWebSocketSession[id=e28c3083-e231-c00d-dc20-2cdb4d01ecac, uri=ws://....net/ws]
2025-02-02T10:47:18.340-05:00 DEBUG 88460 --- [love-monkey] [nio-7080-exec-4] .s.m.a.i.AuthorizationChannelInterceptor : Authorizing message send
2025-02-02T10:47:18.341-05:00 DEBUG 88460 --- [love-monkey] [nio-7080-exec-4] .s.m.a.i.AuthorizationChannelInterceptor : Failed to authorize message with authorization manager org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager@5a4d7ce9 and result AuthorizationDecision [granted=false]
2025-02-02T10:47:18.341-05:00 DEBUG 88460 --- [love-monkey] [nio-7080-exec-4] o.s.w.s.m.StompSubProtocolHandler : Failed to send message to MessageChannel in session e28c3083-e231-c00d-dc20-2cdb4d01ecac
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]
也是一个示例项目,如果您想玩,也有相同的问题
中撰写this Repo的代码,并创建一个使用携带者令牌的测试环境。
update
成功创建了使用.httpBasic
创建设置后,我将此答案更新为使用.oauth2ResourceServer
非常简单;它允许所有重新要求添加并添加SecurityFilterChain
/ws
customcsrfChannelInterceptor&webSocketSecurityConfig
这有点棘手,因为CSRF-Header无法关闭,但是我在this this Answer.中发现了一个hack。
.oauth2ResourceServer
我们需要一个从携带者令牌中提取角色。可以在应用程序中完成。
@Configuration
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { csrf -> csrf.disable() }
.sessionManagement { session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.oauth2ResourceServer { oauth2 -> oauth2.jwt { } }
.authorizeHttpRequests { auth ->
auth
.requestMatchers(HttpMethod.GET, "/ws").permitAll()
.anyRequest().authenticated()
}
return http.build()
}
}
WebsocketControllerllet的log the the the Controller
// https://stackoverflow.com/a/77057255/14072498
@Component("csrfChannelInterceptor")
class CustomCsrfChannelInterceptor : ChannelInterceptor {
override fun preSend(
message: Message<*>,
channel: MessageChannel
): Message<*> {
return message
}
}
@Configuration
@EnableWebSocketSecurity
class WebSocketSecurityConfig {
@Bean
fun messageAuthorizationManager(
messages: MessageMatcherDelegatingAuthorizationManager.Builder
): AuthorizationManager<Message<*>> {
messages
// Next 2 lines are required for requests without auth.
// Remove these if all paths require auth
.simpTypeMatchers(SimpMessageType.CONNECT).permitAll()
.simpTypeMatchers(SimpMessageType.DISCONNECT).permitAll()
.simpDestMatchers("/app/hello").hasRole("USER")
.anyMessage().authenticated()
return messages.build()
}
}
测试
为了避免使用真正的携带者令牌,让我们在测试上下文中添加模拟JwtAuthenticationConverter
spring:
security:
oauth2:
resourceserver:
jwt:
authorities-claim-name: roles
authority-prefix: ROLE_
测试尚未提出任何断言,但可以让您进行一些测试。
Authentication