Spring中的Websocket身份验证和授权

简介: - http和WebSocket的安全链和安全配置是完全独立的。- SpringAuthenticationProvider根本不参与 Websocket 身份验证。- 将要给出的示例中,身份验证不会发生在 HTTP 协商端点上,因为 JavaScript STOMP(websocket)库不会随 HTTP 请求一起发送必要的身份验证标头。- 一旦在 CONNECT 请求上设置,用户( simpUser) 将被存储在 websocket 会话中,并且以后的消息将不再需要进行身份验证。

一、需要了解的事项

  • http和WebSocket的安全链和安全配置是完全独立的。
  • SpringAuthenticationProvider根本不参与 Websocket 身份验证。
  • 将要给出的示例中,身份验证不会发生在 HTTP 协商端点上,因为 JavaScript STOMP(websocket)库不会随 HTTP 请求一起发送必要的身份验证标头。
  • 一旦在 CONNECT 请求上设置,用户( simpUser) 将被存储在 websocket 会话中,并且以后的消息将不再需要进行身份验证。

二、依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-messaging</artifactId>
</dependency>

三、WebSocket 配置

3.1 、简单的消息代理

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(final MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(final StompEndpointRegistry registry) {
        registry.addEndpoint("stomp"); 
        setAllowedOrigins("*")
    }
}

3.2 、Spring安全配置

由于 Stomp 协议依赖于第一个 HTTP 请求,因此需要授权对 stomp 握手端点的 HTTP 调用。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception
        http.httpBasic().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().antMatchers("/stomp").permitAll()
                .anyRequest().denyAll();
    }
}

然后创建一个负责验证用户身份的服务。

@Component
public class WebSocketAuthenticatorService {
    public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final String  username, final String password) throws AuthenticationException {
        if (username == null || username.trim().isEmpty()) {
            throw new AuthenticationCredentialsNotFoundException("Username was null or empty.");
        }
        if (password == null || password.trim().isEmpty()) {
            throw new AuthenticationCredentialsNotFoundException("Password was null or empty.");
        }
    
        if (fetchUserFromDb(username, password) == null) {
            throw new BadCredentialsException("Bad credentials for user " + username);
        }

      
        return new UsernamePasswordAuthenticationToken(
                username,
                null,
                Collections.singleton((GrantedAuthority) () -> "USER") // 必须给至少一个角色
        );
    }
}

接着需要创建一个拦截器,它将设置“simpUser”标头或在 CONNECT 消息上抛出“AuthenticationException”。

@Component
public class AuthChannelInterceptorAdapter extends ChannelInterceptor {
    private static final String USERNAME_HEADER = "login";
    private static final String PASSWORD_HEADER = "passcode";
    private final WebSocketAuthenticatorService webSocketAuthenticatorService;

    @Inject
    public AuthChannelInterceptorAdapter(final WebSocketAuthenticatorService webSocketAuthenticatorService) {
        this.webSocketAuthenticatorService = webSocketAuthenticatorService;
    }

    @Override
    public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException {
        final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

        if (StompCommand.CONNECT == accessor.getCommand()) {
            final String username = accessor.getFirstNativeHeader(USERNAME_HEADER);
            final String password = accessor.getFirstNativeHeader(PASSWORD_HEADER);

            final UsernamePasswordAuthenticationToken user = webSocketAuthenticatorService.getAuthenticatedOrFail(username, password);

            accessor.setUser(user);
        }
        return message;
    }
}
请注意:preSend() 必须返回 UsernamePasswordAuthenticationToken,Spring 安全链中会对此进行测试。如果UsernamePasswordAuthenticationToken构建没有通过GrantedAuthority,则身份验证将失败,因为没有授予权限的构造函数自动设置authenticated = false 这是一个重要的细节,在 spring-security 中没有记录。

最后再创建两个类来分别处理授权和身份验证。

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketAuthenticationSecurityConfig extends  WebSocketMessageBrokerConfigurer {
    @Inject
    private AuthChannelInterceptorAdapter authChannelInterceptorAdapter;
    
    @Override
    public void registerStompEndpoints(final StompEndpointRegistry registry) {
        // 这里不用给任何东西
    }

    @Override
    public void configureClientInboundChannel(final ChannelRegistration registration) {
        registration.setInterceptors(authChannelInterceptorAdapter);
    }

}
请注意:这@Order是至关重要的,它允许我们的拦截器首先在安全链中注册。
@Configuration
public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
    @Override
    protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) {
        // 添加自己的映射
        messages.anyMessage().authenticated();
    }

    // 这里请自己按需求修改
    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

之后编写客户端进行连接,我们就可以这样指定客户端进行消息的发送。

  @MessageMapping("/greeting")
    public void greetingReturn(@Payload Object ojd){
        simpMessagingTemplate.convertAndSendToUser(username,"/topic/greeting",ojd);
    }
目录
相关文章
|
4月前
|
前端开发 网络协议 JavaScript
在Spring Boot中实现基于WebSocket的实时通信
在Spring Boot中实现基于WebSocket的实时通信
|
24天前
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
247 1
|
29天前
|
前端开发 Java C++
RSocket vs WebSocket:Spring Boot 3.3 中的两大实时通信利器
本文介绍了在 Spring Boot 3.3 中使用 RSocket 和 WebSocket 实现实时通信的方法。RSocket 是一种高效的网络通信协议,支持多种通信模式,适用于微服务和流式数据传输。WebSocket 则是一种标准协议,支持全双工通信,适合实时数据更新场景。文章通过一个完整的示例,展示了如何配置项目、实现前后端交互和消息传递,并提供了详细的代码示例。通过这些技术,可以大幅提升系统的响应速度和处理效率。
|
2月前
|
JavaScript 前端开发 Java
【颠覆传统】Spring框架如何用WebSocket技术重塑实时通信格局?揭秘背后的故事与技术细节!
【9月更文挑战第4天】随着Web应用对实时交互需求的增长,传统的HTTP模型已无法满足现代应用的要求,特别是在需要持续、双向通信的场景下。WebSocket协议由此诞生,提供全双工通信渠道,使服务器与客户端能实时互发消息。作为Java开发中最受欢迎的框架之一,Spring通过其WebSocket模块支持这一协议,简化了WebSocket在Spring应用中的集成。
55 0
|
3月前
|
JavaScript 前端开发 网络协议
WebSocket在Java Spring Boot+Vue框架中实现消息推送功能
在现代Web应用中,实时消息提醒是一项非常重要的功能,能够极大地提升用户体验。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实现实时消息提醒提供了高效且低延迟的解决方案。本文将详细介绍如何在Java Spring Boot后端和Vue前端框架中利用WebSocket实现消息提醒功能。
157 0
|
3月前
|
Java Spring
Spring Boot Admin 授权配置
Spring Boot Admin 授权配置
31 0
|
4月前
|
安全 Java 数据安全/隐私保护
使用Java和Spring Security实现身份验证与授权
使用Java和Spring Security实现身份验证与授权
|
4月前
|
监控 网络协议 Java
如何在Spring Boot中使用WebSocket
如何在Spring Boot中使用WebSocket
|
4月前
|
监控 前端开发 网络协议
如何使用Spring Boot实现WebSocket通信
如何使用Spring Boot实现WebSocket通信
|
5月前
|
存储 安全 Java
Spring Boot中的OAuth2认证与授权
Spring Boot中的OAuth2认证与授权