Spring WebSocket 认证与授权:掌控安全通道,迈向巅峰之旅!

2023-09-22 10:00:00

一、需要了解的事项

  • 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);
    }
更多推荐

Centos 7 部署SVN服务器

一、安装SVN1、安装Subversionsudoyum-yinstallsubversion2、验证是否安装成功(查看svn版本号)svnserve--version二、创建版本库1、先建立目录,目录位置可修改mkdir-p/var/svncd/var/svn2、创建版本库,添加权限svnadmincreate/va

5-3 pytorch中的损失函数

一般来说,监督学习的目标函数由损失函数和正则化项组成。(Objective=Loss+Regularization)Pytorch中的损失函数一般在训练模型时候指定。注意Pytorch中内置的损失函数的参数和tensorflow不同,是y_pred在前,y_true在后,而Tensorflow是y_true在前,y_p

如何通过AI视频智能分析,构建着装规范检测/工装穿戴检测系统?

众所周知,规范着装在很多场景中起着重要的作用。违规着装极易增加安全隐患,并且引发安全事故和质量问题,例如,在化工工厂中,倘若员工没有穿戴符合要求的特殊防护服和安全鞋,将有极大可能受到有害物质的侵害,对身体健康和生命安全带来严重的威胁。TSINGSEE青犀视频AI算法平台的着装规范检测/工装穿戴检测算法,是基于AI深度学

【启发式搜索】

运用启发式策略的两种基本情况:(1)一个问题由于存在问题陈述和数据获取的模糊性,可能会使它没有一个确定的解。(2)虽然一个问题可能有确定解,但是其状态空间特别大,搜索中生成扩展的状态数会随着搜索的深度呈指数级增长。启发式信息用来简化搜索过程有关具体问题领域的特性的信息叫做启发信息。估价函数估价函数(evaluation

Linux命令教程:使用cat命令查看和处理文件

文章目录教程:使用cat命令在Linux中查看和处理文件1.引言2.cat命令的基本概述3.查看文件内容4.创建文件5.文件重定向和管道6.格式化和编辑文件7.实际应用示例7.1使用cat命令浏览日志文件7.2利用cat命令合并多个配置文件7.3使用cat命令将文件内容发送到其他命令进行处理8.注意事项和常见问题9.结

设计模式:备忘录模式

目录组件代码示例源码中使用优缺点总结备忘录模式(MementoPattern)是一种行为型设计模式,用于在不破坏封装性的前提下,捕获和恢复对象的内部状态。备忘录模式可以将对象的状态保存到备忘录对象中,并在需要时从备忘录对象中恢复状态,实现对象状态的保存和回滚。组件在备忘录模式中,通常包含以下角色:发起人(Origina

[X3m]ros交叉编译

ros需要安装以下包PYTHON_PACKAGE_LIST="larklark-parsernetifacespyyamlifcfgpyunicodedata"TogetheROS.Bot|TogetheROS.Bot用户手册编译tros.b​1使用docker文件​该部分操作均在开发机的docker内完成。##创建目

数据驱动 vs 关键字驱动:对搭建UI自动化测试框架的探索

UI自动化测试用例剖析让我们先从分析一端自动化测试案例的代码开始我们的旅程。以下是我之前写的一个自动化测试的小Demo。这个Demo基于Selenium与Java。由于现在Selenium在自动化测试的统治地位,并且随着Selenium4的即将发布,在未来很长的一段时间里这种统治地位应该还会持续,所以我的这篇文章还都是

我的创作纪念日

机缘第一次写博客我记得是写了个原生ajax的文章,因为突然用这个确实写不出来我写博客纯属为了记录项目经验有的bug可能这个项目解决了下个项目又噶了哈哈,我觉得跟博友们好好交流一下还是可以的,互相进步收获获得了88粉丝的关注有些文章的阅读量还是很高的,嘿嘿,收获最大的就是在工作中遇到的bug解决的更快了哈哈认识十几个志同

算法分享三个方面学习方法(做题经验,代码编写经验,比赛经验)

目录0.前言:(遇到OI不要慌)(只要道路对了,就不怕遥远)1.做题经验谈1.1做题的目的1.2我对于算法比赛的题目的看法1.2.1类似题1.2.2套模型:1.3在训练过程中如何做题1.4一些建议:提高算法能力1.5一些建议:提高代码能力1.6选一个好的OJ1.7分析问题的方法:我的一些经验2.代码编写经验谈2.1你5

UML活动图

在UML中,活动图本质上就是流程图,它描述系统的活动、判定点和分支等,因此它对开发人员来说是一种重要工具。活动图活动是某件事情正在进行的状态,既可以是现实生活中正在进行的某一项工作,也可以是软件系统中某个类对象的一个操作。活动图和流程图的区别1、流程图着重描述处理过程,他的主要控制结构是顺序、分支和循环,各个处理过程之

热文推荐