SpringBoot整合WebSocket和JWTtoken步骤以及注意事项

一、重点导读

1、拦截器的配置:由于WebSocket不能像http那样很简单的将token设置到请求头中,而基于token的拦截器基本都是在请求头中获取token,因此不能拦截WebSocket的请求,否则会报错空指针异常。token除了放在请求头,还能放在请求地址,因此可以采取路径变量或者使用?拼接在地址栏。用户信息的获取放在ChatEndpoint 中并根据token获取

2、ChatEndpoint 中如何获取token,使用路径变量+WebSocket的@PathParam注解

3、ChatEndpoint 中如何根据token获取当前的用户id

4、为了安全,用户id不要拼接在地址栏,如果后端使用前端传来的id,这很不安全,因为可能用户登录的账号id与地址栏中的id不同,这样用户就使用了别人的账号发送消息

二、代码

1、导入依赖

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

        <!--token-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
        </dependency>

2、在SpringBoot容器中注册WebSocket

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;


@Configuration
public class WebSocketConfig {

    /**
     * 扫描注解了@ServerEndpoint注解的类
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

} 

3、端点类(核心逻辑)

关键代码:
@ServerEndpoint(value = "/chat/{token}")
User tokenUser = TokenUtils.getUser(token);

对应的请求:ws://localhost:9090/chat/{token}

如:ws://localhost:9090/chat/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxMzAiLCJleHAiOjE2NjE0MDYzMjl9.XEktvzwASqWvBRkbFPPZ3cCntOxB4bPjOR4hjGpCuas

因为使用的token,因此数据没有选择去session中取,如果有需要session做法的小伙伴可以去参考b站WebSocket打造在线聊天室【完结】_哔哩哔哩_bilibili

import cn.hutool.json.JSONUtil;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 各个webSocket端点
 * 不拦截该请求,因为接口测试工具设置请求体困难,因此选择使用路径变量来传递token
 */
@ServerEndpoint(value = "/chat/{token}")
@Component
public class ChatEndpoint {


    /**
     * 用来储存在线用户的容器
     */
    public static Map<Integer, ChatEndpoint> onlineUsers = new ConcurrentHashMap<>();

    /**
     * 用来给客户端发送消息
     */
    private Session session;


    /*建立时调用*/
    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {

        //将当前session赋值给属性
        this.session = session;
        //从token获取用户数据
        User tokenUser = TokenUtils.getUser(token);
        Integer userId = tokenUser.getId();
        //将当前端点存放到onlineUsers中保存
        onlineUsers.put(userId, this);
        //系统消息推送所有在线用户给客户端
        //封装系统推送消息,前端onmessage接收的数据
        String message = MessageUtils.formatMessage(null, null, MessageType.TEXT, tokenUser.getName() + "已上线", true, null);
        sendMessageToAllUser(message);
        //查询并给客户端发送未读消息个数

    }


    /**
     * 接收到客户端发送的数据时调用
     *
     * @param message 客户端发送的数据
     * @param session session对象
     * @return void
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        Message msg = JSONUtil.toBean(message, Message.class);
        msg.setTime(new Date());
        //获取接收信息的用户
        Integer recipientId = msg.getRecipientId();
        //封装发送的消息
        String result = MessageUtils.formatMessage(msg);
        //发送消息
        Session toSession = onlineUsers.get(recipientId).getSession();
        sendMessage(toSession, result);
        //将消息存储在数据库

    }


    /**
     * 关闭时调用
     */
    @OnClose
    public void onClose(Session session, @PathParam("token") String token) {

        try {
            //从token获取用户数据
            User tokenUser = TokenUtils.getUser(token);
            Integer userId = tokenUser.getId();
            //从在线用户列表中移除
            onlineUsers.remove(userId);
            //广播
            String message = MessageUtils.formatMessage(null, null, MessageType.TEXT, tokenUser.getName() + "已下线", true, null);
            sendMessageToAllUser(message);
        } catch (Exception e) {

            e.printStackTrace();
        }

    }

    /**
     * 给所有的客户端发送消息
     *
     * @param message 给客户端发送消息
     * @return void
     */
    private void sendMessageToAllUser(String message) {
        //所有登录用户id
        Set<Integer> ids = onlineUsers.keySet();
        for (Integer id : ids) {
            //发送消息
            Session toSession = onlineUsers.get(id).getSession();
            sendMessage(toSession, message);
        }
    }

    /**
     * 发送消息给单个用户
     *
     * @param message
     */
    private void sendMessage(Session toSession, String message) {
        try {
            toSession.getBasicRemote().sendText(message);
        } catch (IOException e) {

            e.printStackTrace();
        }
    }

    public Session getSession() {
        return session;
    }
}

4、消息实体类以及消息工具类

package com.huayu.campuspostbar.eneity;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * 聊天消息实体类
 *
 * @TableName message
 */
@TableName(value = "message")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message {
    /**
     *
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 发送者id
     */
    private Integer senderId;

    /**
     * 接收者
     */
    private Integer recipientId;

    /**
     * 消息类型,文本、图片、视频
     */
    private String type;

    /**
     * 内容
     */
    private String content;

    /**
     * 时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date time;
    /**
     * 是不是系统消息
     */
    private Boolean isSystemMessage;
    /**
     * 是否被读过
     */
    private Boolean haveRead;

    public Message(Integer senderId, Integer recipientId, String type, String content, Date time, Boolean isSystemMessage, Boolean haveRead) {
        this.senderId = senderId;
        this.recipientId = recipientId;
        this.type = type;
        this.content = content;
        this.time = time;
        this.isSystemMessage = isSystemMessage;
        this.haveRead = haveRead;
    }



}
package com.huayu.campuspostbar.utils;

import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Slf4j
@Component
public class TokenUtils {

    @Resource
    private UserMapper userMapper;

    private static UserMapper staticUserMapper;

    @PostConstruct
    public void init() {
        staticUserMapper = userMapper;
    }

    /**
     * 生成token
     *
     * @param user
     * @return
     */
    public static String getToken(User user) {
        return JWT.create().withExpiresAt(DateUtil.offsetDay(new Date(), 1))
                .withAudience(user.getId().toString())
                .sign(Algorithm.HMAC256(user.getPassword()));
    }

    /**
     * 获取token中的用户信息
     *
     * @return
     */
    public static User getUser() {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        try {
            String token = request.getHeader("token");
            String aud = JWT.decode(token).getAudience().get(0);
            Integer userId = Integer.valueOf(aud);
            User user = staticUserMapper.selectById(userId);//获取user信息
            user.setToken(token);//设置token
            return user;
        } catch (Exception e) {
            log.error("解析token失败", e);
            return null;
        }

    }

    /**
     * 获取token中的用户信息
     *
     * @return
     */
    public static User getUser(String token) {
        try {
            String aud = JWT.decode(token).getAudience().get(0);
            Integer userId = Integer.valueOf(aud);
            User user = staticUserMapper.selectById(userId);//获取user信息
            user.setToken(token);//设置token
            return user;
        } catch (Exception e) {
            log.error("解析token失败", e);
            return null;
        }

    }
}

5、token(JWT)的整合过程就不说了,大家可以去学习青戈大佬的视频,或者其他的文章。另外非常感谢青戈大佬的免费开源教程,真的教会了我很多的东西。

从0开始带你手撸一套SpringBoot+Vue后台管理系统(2022年最新版)_哔哩哔哩_bilibili

6、过滤器配置

不拦截WebSocket请求地址


@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/",
                        "/chat/**",
                        "/user/register",
                        "/user/login",
                );   



    @Bean
    public JwtInterceptor jwtInterceptor() {
        return new JwtInterceptor();
    }


}

7、token工具类中使用的核心方法:通过token获取user

 public static User getUser(String token) {
        try {
            String aud = JWT.decode(token).getAudience().get(0);
            Integer userId = Integer.valueOf(aud);
            User user = staticUserMapper.selectById(userId);//获取user信息
            user.setToken(token);//设置token
            return user;
        } catch (Exception e) {
            log.error("解析token失败", e);
            return null;
        }
    }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340