【stomp 实战】Spring websocket 用户校验和业务会话绑定

通过这节内容,我们将了解到以下内容

  • 怎么对websocket连接鉴权,不是任意一个连接我都要去建立连接的?
  • 用户鉴权以后,这个用户就算“登录”成功了。用户的会话怎么来管理。我想给该用户推送一条消息,怎么找到该用户?

一、什么是jwtToken

简单来说,token就是一种身份验证方法,和cookie有相似作用;它被很多人翻译过来后生动的称为“令牌”,它的扩展性,安全性更高,非常适合用在Web应用和移动开发应用上。

1.1token验证流程

​ 使用token身份验证,服务器端就不会存储用户的登录记录。

  • (1)客户端使用用户名跟密码请求登录;
  • (2)服务端收到请求,去验证用户名与密码;
  • (3)验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端;
  • (4)客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里;
  • (5)客户端每次向服务端请求资源的时候需要带着服务端签发的 Token;
  • (6)服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。

Token有很多种,下面重点介绍Jwt

1.2Jwt

​ json web token(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。它是以JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

http协议无状态的,所以需要sessionId或token的鉴权机制,jwt的token认证机制不需要在服务端再保留用户的认证信息或会话信息。这就意味着基于jwt认证机制的应用程序不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利,jwt更适用于分布式应用

1.3jwt的组成部分

​ 标准的jwt令牌分为三部分,分别是Header、payload、signature;

1.3.1Header

它的组成部分包括两点

参数类型-jwt
签名的算法-hs256
最后会经过Base64加密方式进行编码,看起来是这个样子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

1.3.2 Payload

它的组成就是登陆用户的一些信息,和token发行和失效时间等;这些内容里面有一些是标准字段,你也可以添加其它需要的内容。

iss:Issuer,发行者
sub:Subject,主题
aud:Audience,观众
exp:Expiration time,过期时间
nbf:Not before
iat:Issued at,发行时间
jti:JWT ID

最后它也会通过Base64加密方式进行编码,仍然长的像上面的样子

1.3.3 Signature

​ 它是由3个部分组成,先是用 Base64 编码的 header 和 payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。

​ secret就是在最后第二次加密时加的盐,算是一个秘钥(只保留在服务器),不向外部透露。

来看一下最后的jwt完整版

eyJhbGciOiJIUzI1NiIsInR9cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I
kpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
8HILod0ncfVDnbKIPJJqLH998duF9DSDGkx3gRPNVI

二、用jwtToken建立连接代码示例

2.1 token获取接口

我们可以定义一个接口,入参是userId。生成jwtToken,具体token的生成算法略。


@Data
public class TokenRequest {
    private String userId;
}

@RestController
@Slf4j
@RequestMapping("/api/ws/token")
@Api("token获取")
public class TokenController {
    @PostMapping("/get")
    public String getToken(@RequestBody @Validated TokenRequest tokenRequest) {
        return TokenUtil.generateToken(tokenRequest.getUserId());
    }
}

前端页面调用此接口,获取到token
在这里插入图片描述
此时前端建立连接,可以将此token带上

2.2 前端连接示例

stompClient.connect方法中,将header带入

 //建立连接
    function connect() {
        //连接请求头里面,设置好我们提前获取好的token
        var headers = {
            token:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMDAxIiwiZXhwIjoxNzE0MTQwNzI3LCJpYXQiOjE3MTQxMzM1Mjd9.QBTagYepIuZs8C7xmxsizu4jKgOxg_z0Q13Y8LvbtnA};
        var url = endpoint;
        var socket = new SockJS(url);
        stompClient = Stomp.over(socket);
        //建立连接
        stompClient.connect(headers, function (msg) {

        });
    }

2.3 后端校验逻辑

后端在接收客户端的消息时,会通过inboundChannel。Spring给我们提供了消息处理的拦截器。通过拦截器,我们就可以获取到收到的消息,进行前置处理,比如用户校验等
拦截器如何注册的?

@Configuration
@EnableWebSocketMessageBroker
@EnableConfigurationProperties({UserSessionProperties.class})
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer, ApplicationListener<BrokerAvailabilityEvent> {
    private final WebSocketInboundInterceptor webSocketInboundInterceptor;

    public WebSocketConfig(WebSocketInboundInterceptor webSocketInboundInterceptor) {
        this.webSocketInboundInterceptor = webSocketInboundInterceptor;
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(webSocketInboundInterceptor);
    }
 }

我们定义了WebSocketInboundInterceptor ,将其注册上了。其实自定义实现如下:

@Component
public class WebSocketInboundInterceptor implements ChannelInterceptor {
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (accessor == null) {
            return message;
        }

        //当消息是CONNECT连接报文时,进行会话校验,建立连接
        if (Objects.equals(accessor.getCommand(), StompCommand.CONNECT)) {
            connect(message, accessor);
        }
        return message;
    }

    /**
     * 建立会话
     *
     * @param message
     * @param accessor
     */
    private void connect(Message<?> message, StompHeaderAccessor accessor) {
        //1通过请求头获取到token
        String token = accessor.getFirstNativeHeader(WsConstants.TOKEN_HEADER);
        //2如果token为空或者用户id没有解析出来,抛出异常,spring会将此websocket连接关闭
        if (StringUtils.isEmpty(token)) {
            throw new MessageDeliveryException("token missing!");
        }
        String userId = TokenUtil.parseToken(token);
        if (StringUtils.isEmpty(userId)) {
            throw new MessageDeliveryException("userId missing!");
        }
        //这个是每个会话都会有的一个sessionId
        String simpleSessionId = (String) message.getHeaders().get(SimpMessageHeaderAccessor.SESSION_ID_HEADER);

        //3创建自己的业务会话session对象
        UserSession userSession = new UserSession();
        userSession.setSimpleSessionId(simpleSessionId);
        userSession.setUserId(userId);
        userSession.setCreateTime(LocalDateTime.now());
        //4关联用户的会话。通过msgOperations.convertAndSendToUser(username, "/topic/subNewMsg", msg); 此方法,可以发送给用户消息
        accessor.setUser(new UserSessionPrincipal(userSession));
    }
}

代码里面通过判断CONNECT连接报文,就进行会话的校验和会话的绑定。具体看下代码中的注释。

其中UserSessionPrincipal定义如下

public class UserSessionPrincipal implements Principal {
    private final UserSession userSession;

    public UserSessionPrincipal(UserSession userSession) {
        this.userSession = userSession;
    }

    @Override
    public String getName() {
        return userSession.getUserId();
    }
}

通过 accessor.setUser(new UserSessionPrincipal(userSession))进行会话绑定以后,我们就可以用我们的业务的userId给指定用户发送消息了!
上面示例,我们token解析出来的用户id是1001,那业务代码就可以通过该方法发送给指定用户消息了。

msgOperations.convertAndSendToUser(1001, "/topic/answer", msg);

本节的示例源码,都在开源项目中:文章链接【stomp实战】搭建一套websocket推送平台。文章最后有项目地址。

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Stomp WebSocket是一种基于WebSocket协议的通信方式,它使用Stomp协议在客户端和服务器之间进行消息传递。Stomp是一个简单的文本协议,支持基于消息的中间件通信。 Spring提供了对Stomp WebSocket的支持,可以轻松地创建基于StompWebSocket应用程序。在Spring中,可以使用Spring WebSocket模块和Spring Messaging模块来实现Stomp WebSocket。 首先需要在pom.xml文件中添加相关依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.messaging</groupId> <artifactId>spring-messaging</artifactId> </dependency> ``` 然后,需要创建一个WebSocket配置类,该类需要继承WebSocketMessageBrokerConfigurer接口,并实现其方法,如下所示: ``` @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); } } ``` 在这个配置类中,我们首先通过@EnableWebSocketMessageBroker注解启用了WebSocket消息代理,并实现了configureMessageBroker()方法来配置消息代理。这里我们使用了一个简单的消息代理来处理消息,使用/topic作为消息前缀,使用/app作为应用程序前缀。 接下来,我们使用registerStompEndpoints()方法注册了一个Stomp协议的WebSocket端点,客户端可以使用这个端点来连接WebSocket服务器。这里我们使用了withSockJS()方法来启用SockJS支持,以便在WebSocket不可用时提供备用传输。 最后,我们需要创建一个控制器类来处理WebSocket请求,如下所示: ``` @Controller public class WebSocketController { @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay return new Greeting("Hello, " + message.getName() + "!"); } } ``` 在这个控制器类中,我们使用@MessageMapping注解来处理客户端发送的消息,使用@SendTo注解将消息发送到指定的目的地。在这个例子中,我们使用了“/hello”作为目的地,所以客户端发送的消息需要以“/app/hello”格式发送。而我们使用了“/topic/greetings”作为发送目的地,所以服务端返回的消息将会被发送到“/topic/greetings”这个主题中,所有订阅这个主题的客户端都将收到这条消息。 以上就是Spring Stomp WebSocket的简单介绍和使用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值