Spring中的Websocket身份验证和授权

2023-11-21

我一直在努力正确实现 Stomp (websocket)验证 and 授权与Spring-Security。对于后代,我将回答我自己的问题以提供指导。


问题

Spring WebSocket 文档(用于身份验证)看起来不清楚 ATM(恕我直言)。我不明白如何正确处理验证 and 授权.


我想要的是

  • 使用登录名/密码对用户进行身份验证。
  • 防止匿名用户通过 WebSocket 连接。
  • 添加授权层(用户、管理员……)。
  • Having Principal在控制器中可用。

我不想要什么

  • 在 HTTP 协商端点上进行身份验证(因为大多数 JavaScript 库不会随 HTTP 协商调用一起发送身份验证标头)。

如上所述,文档看起来不清楚(恕我直言),在 Spring 提供一些清晰的文档之前,这里有一个样板,可以让您免于花两天时间尝试了解安全链正在做什么。

做了一次非常好的尝试罗布·莱格特但是,他是分叉一些 Springs 类这样做我感觉不舒服。

开始之前需要了解的事项:

  • 安全链 and 安全配置 for http and WebSocket是完全独立的。
  • Spring AuthenticationProvider根本不参与 Websocket 身份验证。
  • 在我们的例子中,身份验证不会发生在 HTTP 协商端点上,因为我知道的 JavaScript STOMP(websocket)库都不会随 HTTP 请求一起发送必要的身份验证标头。
  • 一旦设置了 CONNECT 请求,user (simpUser)将存储在 websocket 会话中,并且后续消息不再需要身份验证。

Maven 部门

<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 配置

下面的配置注册了一个简单的消息代理(我们稍后将保护的简单端点)。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(final MessageBrokerRegistry config) {
        // These are endpoints the client can subscribes to.
        config.enableSimpleBroker("/queue/topic");
        // Message received with one of those below destinationPrefixes will be automatically router to controllers @MessageMapping
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(final StompEndpointRegistry registry) {
        // Handshake endpoint
        registry.addEndpoint("stomp"); // If you want to you can chain setAllowedOrigins("*")
    }
}

Spring安全配置

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

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // This is not for websocket authorization, and this should most likely not be altered.
        http
                .httpBasic().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().antMatchers("/stomp").permitAll()
                .anyRequest().denyAll();
    }
}

Then we'll create a service responsible for authenticating users.
@Component
public class WebSocketAuthenticatorService {
    // This method MUST return a UsernamePasswordAuthenticationToken instance, the spring security chain is testing it with 'instanceof' later on. So don't use a subclass of it or any other class
    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.");
        }
        // Add your own logic for retrieving user in fetchUserFromDb()
        if (fetchUserFromDb(username, password) == null) {
            throw new BadCredentialsException("Bad credentials for user " + username);
        }

        // null credentials, we do not pass the password along
        return new UsernamePasswordAuthenticationToken(
                username,
                null,
                Collections.singleton((GrantedAuthority) () -> "USER") // MUST provide at least one role
        );
    }
}

注意:UsernamePasswordAuthenticationToken MUST至少有一个GrantedAuthority,如果你使用另一个构造函数,Spring会自动设置isAuthenticated = false.


Almost there, now we need to create an Interceptor that will set the `simpUser` header or throw `AuthenticationException` on CONNECT messages.
@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() MUST返回一个UsernamePasswordAuthenticationToken,Spring Security 链中的另一个元素对此进行了测试。 请注意:如果您的UsernamePasswordAuthenticationToken未经通过而建造GrantedAuthority,认证会失败,因为构造函数没有授予权限自动设置authenticated = false 这是一个重要的细节,在 spring-security 中没有记录.


Finally create two more class to handle respectively Authorization and Authentication.
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketAuthenticationSecurityConfig extends  WebSocketMessageBrokerConfigurer {
    @Inject
    private AuthChannelInterceptorAdapter authChannelInterceptorAdapter;
    
    @Override
    public void registerStompEndpoints(final StompEndpointRegistry registry) {
        // Endpoints are already registered on WebSocketConfig, no need to add more.
    }

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

}

请注意:@Order is CRUCIAL不要忘记它,它允许我们的拦截器首先在安全链中注册。

@Configuration
public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
    @Override
    protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) {
        // You can customize your authorization mapping here.
        messages.anyMessage().authenticated();
    }

    // TODO: For test purpose (and simplicity) i disabled CSRF, but you should re-enable this and provide a CRSF endpoint.
    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring中的Websocket身份验证和授权 的相关文章

  • 如何在 JPQL 或 HQL 中进行限制查询?

    在 Hibernate 3 中 有没有办法在 HQL 中执行相当于以下 MySQL 限制的操作 select from a table order by a table column desc limit 0 20 如果可能的话 我不想使用
  • 如何在 Java 中向时间戳添加/减去时区偏移量?

    我正在使用 JDK 8 并且玩过ZonedDateTime and Timestamp很多 但我仍然无法解决我面临的问题 假设我得到了格式化的Timestamp在格林威治标准时间 UTC 我的服务器位于某处 假设它设置为Asia Calcu
  • Android中如何使用JNI获取设备ID?

    我想从 c 获取 IMEIJNI 我使用下面的代码 但是遇到了未能获取的错误cls 它总是返回NULL 我检查了环境和上下文 它们都没有问题 为什么我不能得到Context班级 我在网上搜索了一下 有人说我们应该使用java lang Ob
  • Spring Data JPA,对多对多实体的一个属性的更改错误地显示在共享它的所有其他实体上

    当我更改实体的一个属性时 使用该实体的每个其他实体也会以某种方式更改它 我有三个实体 如下所示 学生和课程之间需要有多对多的关系 课程需要和课程讲座有一对多的关系 当我通过 Transactional 更改属于特定学生的课程或课程讲座时st
  • 如何检查某个元素是否存在于一组项目中?

    In an ifJava中的语句如何检查一个对象是否存在于一组项目中 例如 在这种情况下 我需要验证水果是苹果 橙子还是香蕉 if fruitname in APPLE ORANGES GRAPES Do something 这是一件非常微
  • Java 中如何将 char 转换为 int? [复制]

    这个问题在这里已经有答案了 我是Java编程新手 我有例如 char x 9 我需要得到撇号中的数字 即数字 9 本身 我尝试执行以下操作 char x 9 int y int x 但没有成功 那么我应该怎么做才能得到撇号中的数字呢 ASC
  • 如何在 Spring 中使 @PropertyResource 优先于任何其他 application.properties ?

    我正在尝试在类路径之外添加外部配置属性资源 它应该覆盖任何现有的属性 但以下方法不起作用 SpringBootApplication PropertySource d app properties public class MyClass
  • 将人类日期(当地时间 GMT)转​​换为日期

    我正在服务器上工作 服务器正在向我发送 GMT 本地日期的日期 例如Fri Jun 22 09 29 29 NPT 2018在字符串格式上 我将其转换为日期 如下所示 SimpleDateFormat simpleDateFormat ne
  • 如何在.NET中使用java.util.zip.Deflater解压缩放气流?

    之后我有一个转储java util zip Deflater 可以确认它是有效的 因为 Java 的Inflater打开它很好 并且需要在 NET中打开它 byte content ReadSample sampleName var inp
  • 在Java中运行bat文件并等待

    您可能会认为从 Java 启动 bat 文件是一项简单的任务 但事实并非如此 我有一个 bat 文件 它对从文本文件读取的值循环执行一些 sql 命令 它或多或少是这样的 FOR F x in CD listOfThings txt do
  • Java继承,扩展类如何影响实际类

    我正在查看 Sun 认证学习指南 其中有一段描述了最终修饰符 它说 如果程序员可以自由地扩展我们所知的 String 类文明 它可能会崩溃 他什么意思 如果可以扩展 String 类 我是否不会有一个名为 MyString 的类继承所有 S
  • 如何在JPanel中设置背景图片

    你好 我使用 JPanel 作为我的框架的容器 然后我真的想在我的面板中使用背景图片 我真的需要帮助 这是我到目前为止的代码 这是更新 请检查这里是我的代码 import java awt import javax swing import
  • 不可变的最终变量应该始终是静态的吗? [复制]

    这个问题在这里已经有答案了 在java中 如果一个变量是不可变的并且是final的 那么它应该是一个静态类变量吗 我问这个问题是因为每次类的实例使用它时创建一个新对象似乎很浪费 因为无论如何它总是相同的 Example 每次调用方法时都会创
  • 在 Rails 3 中选择性地关闭 Devise 的 Flash 通知

    Devise 身份验证框架在各处都使用 Flash 通知 这使得与应用程序集成变得很容易 但有时会导致用户体验不佳 我想知道有什么简单的方法可以有选择地关闭 Rails 3 应用程序中的一些 Devise flash 通知 特别是 我想摆脱
  • 手动设置Android Studio的JDK路径

    如何为 Android Studio 使用自定义 JDK 路径 我不想弄乱 PATH 因为我没有管理员权限 是否有某个配置设置文件允许我进行设置 如果您查看项目设置 您可以从那里访问 jdk 在标准 Windows 键盘映射上 您可以在项目
  • Android S8+ 警告消息“不支持当前的显示尺寸设置,可能会出现意外行为”

    我在 Samsung S8 Android 7 中收到此警告消息 APP NAME 不支持当前的显示尺寸设置 可能会 行为出乎意料 它意味着什么以及如何删除它 谢谢 通过添加解决supports screens 机器人 xlargeScre
  • 列表过滤器内的 Java 8 lambda 列表

    示例 JSON id 1 products id 333 status Active id 222 status Inactive id 111 status Active id 2 products id 6 status Active
  • Java 正则表达式中的逻辑 AND

    是否可以在 Java Regex 中实现逻辑 AND 如果答案是肯定的 那么如何实现呢 正则表达式中的逻辑 AND 由一系列堆叠的先行断言组成 例如 foo bar glarch 将匹配包含所有三个 foo bar 和 glarch 的任何
  • 子类构造函数(JAVA)中的重写函数[重复]

    这个问题在这里已经有答案了 为什么在派生类构造函数中调用超类构造函数时 id 0 当创建子对象时 什么时候在堆中为该对象分配内存 在基类构造函数运行之后还是之前 class Parent int id 10 Parent meth void
  • Java RMI - 客户端超时

    我正在使用 Java RMI 构建分布式系统 它必须支持服务器丢失 如果我的客户端使用 RMI 连接到服务器 如果该服务器出现故障 例如电缆问题 我的客户端应该会收到异常 以便它可以连接到其他服务器 但是当服务器出现故障时 我的客户端什么也

随机推荐

  • Java 中的两个数组声明有什么区别? [复制]

    这个问题在这里已经有答案了 在我的书中 他们一直在以下两种方法之间切换声明数组的方式 int array1 1 2 3 int array2 1 2 3 我想知道两个括号的位置有什么区别 为什么当我将括号放在名称后面 例如在数组 1 中 时
  • Android - 从代码中引用当前应用的主题中的属性值

    Android 开发指南explains如何使用问号 而不是 at 来引用当前应用的主题中的属性值 有谁知道如何从代码中做到这一点 例如在定制组件中 在 XML 中 它看起来像这样 style header background 以编程方式
  • Django 自定义左外连接

    我用这个查询了 Django 模型 news News objects filter Q likes user isnull True Q likes user user extra select is liked NewsLikes me
  • 使用 PHP 将 CSV 转换为 JSON?

    我需要转换一个CSV文件到JSON在服务器上使用 PHP 我正在使用这个有效的脚本 function csvToJSON csv rows explode n csv i 0 len count rows json n data forea
  • 有办法防止 cookie 被盗吗?

    在 Web 2 0 应用程序中 许多用户通常希望保持登录状态 记住我 标志 而另一方面 他们的 cookie 可以访问非常私密的数据 有没有办法防止有人直接从计算机或通过嗅探窃取 cookie 从而使用 cookie 来访问用户的数据 始终
  • “in”运算符或 obj.hasOwnProperty(prop) 的 Big O 表示法的效率是多少

    Mozilla的网站上清楚地描述了hasOwnProperty 和in操作员 但是 它没有提供有关其效率的任何实施细节 我怀疑他们会是O 1 恒定时间 但希望看到任何可能存在的参考或测试 将我的评论变成答案 hasOwnProperty s
  • 使用自己的应用程序打开自定义文件[重复]

    这个问题在这里已经有答案了 可能的重复 如何将文件扩展名与 C 中的当前可执行文件关联 所以 我正在申请学校 最终项目 在这个应用程序中 我有一个Project 班级 这可以保存为自定义文件 例如测试 gpr gpr 是扩展名 如何让 Wi
  • 将静态参数传递给类

    据我所知 你不能将参数传递给 C 中的静态构造函数 但是 在创建类的实例之前 我确实需要传递两个参数并将它们分配给静态字段 我该怎么办 这可能是对 工厂方法的调用 class Foo private int bar private stat
  • Python场景变化检测

    我想知道是否有人有Python和视频处理的经验 本质上 我想知道是否有任何库可以让我在视频中进行场景检测 如果没有 是否有任何可以让我将视频分成一系列帧并让我处理像素 Thanks OpenCV有 Python 绑定 我不认为它有任何内置的
  • 如果我在 PHP 中执行 print_r ,它会以垃圾形式打印数组

    如何以树状格式打印数组 使其更易于阅读 Try pre pre 它将提供 HTML 的空白策略修剪掉的正确树结构
  • Rails 当前页面?当方法为 POST 时“失败”

    我有一个非常简单的问题 我有一页报告 每个报告都有自己的选项卡 我在用着current page 以确定应突出显示哪个选项卡 当我提交任何报告时 current page 似乎不再起作用 显然是因为请求方法是POST 这是预期的行为吗cur
  • 在 OSX Catalina 上安装用于 gem 安装的 Ruby 开发工具

    我知道这个问题有很多例子 我已经完成了这些答案中的所有内容 但 4 小时后却一无所获 我正在尝试在 Catalina 10 15 7 上安装 gem 并获得非常流行的 System Library Frameworks Ruby frame
  • 如何在 WPF 中删除鼠标悬停时按钮的发光

    我在 WPF 中使用一个简单的按钮 我已经在背景上放置了按钮的图像 我的问题是 当我将鼠标指针移动到按钮时 它会获得默认发光并覆盖作为背景给出的图像
  • Hive 中的增量/增量负载

    我有以下用例 我的应用程序有一个表多年数据 in RDBMSD B 我们已经用过sqoop将数据获取到 HDFS 并加载到按以下分区的 hive 表中年 月 现在 应用程序每天都会更新并将新记录插入 RDBMS 表中 这些更新的记录可以跨越
  • powershell 2.0重定向文件处理异常

    我正在寻找解决方案The OS handle s position is not what FileStream expected Do not use a handle simultaneously in one FileStream a
  • 为什么 LINQ 中的 Union 函数不删除重复条目?

    我正在使用 VB NET 我知道 Union 通常按 ByRef 工作 但在 VB 中 字符串通常被视为原始数据类型 因此 问题就在这里 Sub Main Dim firstFile secondFile As String resulti
  • 为什么这个 Perl 6 feed 操作符是一个“虚假语句”?

    我把这个例子取自第 10 天 Perl 6 2010 降临节日历的 Feed 操作员随着轻微的变化 uc为了 ucfirst不再存在了 my rakudo people
  • Angularjs 忽略时区

    有没有更好的方法来忽略 Angularjs 中的时区 2014 01 18 14 30 00 而不是 2014 01 18 15 30 00 function Scoper scope scope datum 2014 01 18T14 3
  • C++ 中的惰性求值

    C 没有对惰性求值的本机支持 如 Haskell 那样 我想知道是否可以以合理的方式在 C 中实现惰性求值 如果是的话 你会怎么做 编辑 我喜欢康拉德鲁道夫的回答 我想知道是否可以以更通用的方式实现它 例如通过使用参数化的惰性类 它本质上适
  • Spring中的Websocket身份验证和授权

    我一直在努力正确实现 Stomp websocket 验证 and 授权与Spring Security 对于后代 我将回答我自己的问题以提供指导 问题 Spring WebSocket 文档 用于身份验证 看起来不清楚 ATM 恕我直言