Spring中的Websocket身份验证和授权

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

@TOC

一、需要了解的事项

  • 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);
    }
目录
相关文章
|
3天前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
21 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
6天前
|
前端开发 JavaScript 安全
集成WebSocket在Spring Boot中可以用于实现实时的双向通信
集成WebSocket在Spring Boot中可以用于实现实时的双向通信
25 4
|
19天前
|
JSON 安全 Java
Java一分钟之-Spring Security:身份验证与授权
【6月更文挑战第7天】本文介绍了Spring Security的常见问题及解决方案,包括配置启动、身份验证、授权、无状态JWT和异常处理。通过`@EnableWebSecurity`启动安全框架,自定义登录页面和登录逻辑,使用`http.authorizeRequests()`设置访问规则。对于JWT,需添加解析器并注册过滤器。此外,处理Spring Security异常,创建自定义的`AccessDeniedHandler`和`AuthenticationEntryPoint`。理解核心概念并按业务需求定制,是确保应用安全的关键。
25 1
|
5天前
|
Java 测试技术 数据安全/隐私保护
微信授权就是这个原理,Spring Cloud OAuth2 授权码模式
微信授权就是这个原理,Spring Cloud OAuth2 授权码模式
|
8天前
|
安全 Java 网络安全
除了认证和授权,Spring Security 还提供了哪些安全特性
【6月更文挑战第14天】在 Spring Security 中,提供了一个 `HttpFirewall` 接口,从它的名字可以看出是作为防火墙使用。
111 0
|
12天前
|
JSON 安全 Java
Spring Security 与 JWT、OAuth 2.0 整合详解:构建安全可靠的认证与授权机制
Spring Security 与 JWT、OAuth 2.0 整合详解:构建安全可靠的认证与授权机制
25 0
|
20天前
|
安全 Java 数据安全/隐私保护
给 Spring Security OAuth 增加新的授权模式
【6月更文挑战第1天】这篇我们来试着参考密码模式,给 Spring Security OAuth 增加一个手机验证码模式。以下内容会与之前的文章内容有关联,因此在此之前,可以先阅读之前的几篇文章,回顾一下这些内容。
40 0
|
消息中间件 网络协议 前端开发
SpringBoot轻松整合WebSocket,实现Web在线聊天室
前面为大家讲述了 Spring Boot的整合Redis、RabbitMQ、Elasticsearch等各种框架组件;随着移动互联网的发展,服务端消息数据推送已经是一个非常重要、非常普遍的基础功能。今天就和大家聊聊在SpringBoot轻松整合WebSocket,实现Web在线聊天室,希望能对大家有所帮助。
SpringBoot轻松整合WebSocket,实现Web在线聊天室
|
存储 JavaScript 开发者
Vue合理配置WebSocket并实现群聊
Vue合理配置WebSocket并实现群聊
Vue合理配置WebSocket并实现群聊
|
网络协议 前端开发 安全
websocket和http的瓜葛以及websocket协议实现
websocket和http的瓜葛以及websocket协议实现
websocket和http的瓜葛以及websocket协议实现