问题:我没有使用Websockets的Spring Security在Webflux项目中工作。
注意:我使用的是Kotlin而不是Java。
依赖关系:
Spring Boot 2.0.0
Spring Security 5.0.3
Spring WebFlux 5.0.4
重要更新:我提出了一个Spring Issue错误(3月30日)here,其中一个Spring安全维护者说它不支持但是他们可以添加它Spring Security 5.1.0 M2
链接: Add WebFlux WebSocket Support #5188
Webflux安全配置
@EnableWebFluxSecurity
class SecurityConfig
{
@Bean
fun configure(http: ServerHttpSecurity): SecurityWebFilterChain
{
return http.authorizeExchange()
.pathMatchers("/").permitAll()
.anyExchange().authenticated()
.and().httpBasic()
.and().formLogin().disable().csrf().disable()
.build()
}
@Bean
fun userDetailsService(): MapReactiveUserDetailsService
{
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("pass")
.roles("USER")
.build()
return MapReactiveUserDetailsService(user)
}
}
Webflux Websocket配置
@Configuration
class ReactiveWebSocketConfiguration
{
@Bean
fun webSocketMapping(handler: WebSocketHandler): HandlerMapping
{
val map = mapOf(Pair("/event", handler))
val mapping = SimpleUrlHandlerMapping()
mapping.order = -1
mapping.urlMap = map
return mapping
}
@Bean
fun handlerAdapter() = WebSocketHandlerAdapter()
@Bean
fun websocketHandler() = WebSocketHandler { session ->
// Should print authenticated principal BUT does show NULL
println("${session.handshakeInfo.principal.block()}")
// Just for testing we send hello world to the client
session.send(Mono.just(session.textMessage("hello world")))
}
}
客户代码
// Lets create a websocket and pass Basic Auth to it
new WebSocket("ws://user:pass@localhost:8000/event");
// ...
观察
在websocket处理程序中,主体显示 null
客户端可以在未经过身份验证的情况下进行连接。如果我没有基本身份验证WebSocket("ws://localhost:8000/event")
它仍然有效!因此Spring Security不会对任何内容进行身份验证。
我缺少什么? 我做错了什么?
答案 0 :(得分:0)
我建议您实施own authenticator
。
基本思想如下:当WebSocket connection
即将建立时,它使用handshake mechanism
并附带UPGRADE request
。因此,在这种情况下,我们将对请求使用自己的处理程序并在其中执行身份验证。幸运的是,Spring Boot
具有RequestUpgradeStrategy用于此目的。最重要的是,基于服务器,您使用的Spring
提供了一个默认实现,下面我使用Netty
这是我们将重用ReactorNettyRequestUpgradeStrategy的类。这是建议的原型:
/**
* Based on {@link ReactorNettyRequestUpgradeStrategy}
*/
@Slf4j
@Component
public class BasicAuthRequestUpgradeStrategy implements RequestUpgradeStrategy {
private int maxFramePayloadLength = NettyWebSocketSessionSupport.DEFAULT_FRAME_MAX_SIZE;
private final AuthenticationService service;
public BasicAuthRequestUpgradeStrategy(AuthenticationService service) {
this.service = service;
}
@Override
public Mono<Void> upgrade(ServerWebExchange exchange, //
WebSocketHandler handler, //
@Nullable String subProtocol, //
Supplier<HandshakeInfo> handshakeInfoFactory) {
ServerHttpResponse response = exchange.getResponse();
HttpServerResponse reactorResponse = getNativeResponse(response);
HandshakeInfo handshakeInfo = handshakeInfoFactory.get();
NettyDataBufferFactory bufferFactory = (NettyDataBufferFactory) response.bufferFactory();
String originHeader = handshakeInfo.getHeaders()
.getOrigin();// you will get ws://user:pass@localhost:8080
return service.authenticate(originHeader)//returns Mono<Boolean>
.filter(Boolean::booleanValue)// filter the result
.doOnNext(a -> log.info("AUTHORIZED"))
.flatMap(a -> reactorResponse.sendWebsocket(subProtocol, this.maxFramePayloadLength, (in, out) -> {
ReactorNettyWebSocketSession session = //
new ReactorNettyWebSocketSession(in, out, handshakeInfo, bufferFactory, this.maxFramePayloadLength);
return handler.handle(session);
}))
.switchIfEmpty(Mono.just("UNATHORIZED")
.doOnNext(log::info)
.then());
}
private static HttpServerResponse getNativeResponse(ServerHttpResponse response) {
if (response instanceof AbstractServerHttpResponse) {
return ((AbstractServerHttpResponse) response).getNativeResponse();
} else if (response instanceof ServerHttpResponseDecorator) {
return getNativeResponse(((ServerHttpResponseDecorator) response).getDelegate());
} else {
throw new IllegalArgumentException("Couldn't find native response in " + response.getClass()
.getName());
}
}
}
此外,如果您在项目中没有对Spring Security的关键逻辑依赖性(例如复杂的ACL逻辑或类似的逻辑),那么我建议您摆脱它并且不要使用它。原因是我认为它是一种反应式方法违规者,因为我会说它是MVC遗留的心态(其后的Servlets世界起源于同步范例)。
希望有帮助。