一、重点导读
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 举报,一经查实,本站将立刻删除。