Spring Webflux Websocket 安全性 - 基本身份验证 [英] Spring Webflux Websocket Security - Basic Authentication

查看:156
本文介绍了Spring Webflux Websocket 安全性 - 基本身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题:我没有让带有 Websockets 的 Spring Security 在 Webflux 项目中工作.

PROBLEM: I am not getting Spring Security with Websockets to work in a Webflux project.

注意:我使用的是 Kotlin 而不是 Java.

NOTE: I am using Kotlin instead of Java.

依赖:

  • Spring Boot 2.0.0

  • Spring Boot 2.0.0

Spring 安全 5.0.3

Spring Security 5.0.3

Spring WebFlux 5.0.4

Spring WebFlux 5.0.4

重要更新:我提出了一个 Spring 问题错误(3 月 30 日)此处,其中一位 Spring 安全维护者表示不支持,但他们可以将其添加到 Spring Security 5.1.0 M2.

IMPORTANT UPDATE: I have raised a Spring Issue bug (March 30) here and one of the Spring security maintainers said its NOT SUPPORTED but they can add it for Spring Security 5.1.0 M2.

链接: 添加 WebFlux WebSocket 支持 #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");
// ...

观察

  1. 在 websocket 处理程序中,主体显示 null

客户端无需身份验证即可连接.如果我在没有基本身份验证的情况下执行 WebSocket("ws://localhost:8000/event") 它仍然有效!因此 Spring Security 不会对任何内容进行身份验证.

The client can connect without being authenticated. If I do WebSocket("ws://localhost:8000/event") without the Basic Auth it stills works! So Spring Security does not authenticate anything.

我错过了什么?我做错了什么?

What I am missing? What I do wrong?

推荐答案

我建议你实现你的自己的身份验证机制,而不是利用 Spring Security.

I could advise you to implement your own authentication mechanism instead of exploiting Spring Security.

WebSocket 连接即将建立时,它使用handshake 机制伴随着一个UPGRADE 请求.基于此,我们的想法是使用我们自己的处理程序来处理请求并在那里执行身份验证.

When WebSocket connection is about to establish it uses handshake mechanism accompanied by an UPGRADE request. Base on that, our idea would be to use our own handler for the request and perform authentication there.

幸运的是,Spring Boot 有 RequestUpgradeStrategy 用于此目的.最重要的是,根据您使用的应用程序服务器,Spring 提供了这些策略的默认实现.当我使用 Netty 时,类将是 ReactorNettyRequestUpgradeStrategy.

Fortunately, Spring Boot has RequestUpgradeStrategy for such purpose. On top of that, based on the application server what you use, Spring provides a default implementation of those strategies. As I use Netty bellow the class would be 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逻辑,那么我建议你去掉它,甚至根本不要使用它.

Moreover, if you do not have crucial logical dependencies onto Spring Security in the project such as complex ACL logic, then I advise you to get rid of it and even do not use it at all.

这样做的原因是我认为 Spring Security 违反了响应式方法,因为它,我会说,MVC 传统思维.它将您的应用程序与大量额外配置和非表面"纠缠在一起.调整并迫使工程师维护这些配置,使它们变得越来越复杂.在大多数情况下,完全不接触 Spring Security 就可以非常顺利地实现.只需创建一个组件并以适当的方式使用它即可.

The reason for that is that I see Spring Security as a violator of the reactive approach due to its, I would say, MVC legacy mindset. It entangles your application with tons of extra configurations and "not-on-the-surface" tunings and forces engineers to maintain those configurations, making them more and more complex. In most cases, things could be implemented very smoothly without touching Spring Security at all. Just create a component and use it in a proper way.

希望有帮助.

这篇关于Spring Webflux Websocket 安全性 - 基本身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆