一、重点导读
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;
}
}
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
似乎无法为此找到有效的答案。我正在阅读Rails教程的第10章第10.1.2节,但似乎无法使邮件程序预览正常工作。我发现处理错误的所有答案都与教程的不同部分相关,我假设我犯的错误正盯着我的脸。我已经完成并将教程中的代码复制/粘贴到相关文件中,但到目前为止,我还看不出我输入的内容与教程中的内容有什么区别。到目前为止,建议是在函数定义中添加或删除参数user,但这并没有解决问题。触发错误的url是http://localhost:3000/rails/mailers/user_mailer/account_activation.http://localhost:3000/rails/mai
rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐
我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token
我是Cucumber测试的新手。我创建了两个特征文件:events.featurepartner.feature并将我的步骤定义放在step_definitions文件夹中:./step_definitions/events.rbpartner.rbCucumber似乎在所有.rb文件中查找步骤信息。有没有办法限制该功能查看特定的步骤定义文件?我之所以要这样做,是因为即使我使用了--guess标志,我也会遇到不明确的匹配错误。我之所以要这样做,有以下几个原因。我正在测试CMS,并希望在不同的功能中测试每种不同的内容类型(事件和合作伙伴)。事件.特征Feature:AddpartnerA
我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d
我正在尝试创建密码规则来设计可恢复的密码更改。我通过passwords_controller.rb做了一个父类(superclass),但我需要在应用规则之前检查用户角色,但我所拥有的只是reset_password_token。 最佳答案 假设您的模型是用户:User.with_reset_password_token(your_token_here)Source 关于ruby-on-rails-设计通过reset_password_token获取用户,我们在StackOverflow
简单代码require'net/http'url=URI.parse('getjson/otherdatahere[link]')req=Net::HTTP::Get.new(url.to_s)res=Net::HTTP.start(url.host,url.port){|http|http.request(req)}putsres.body只是想知道如何在phpcURL中放置身份验证token,我是这样做的 curl_setopt($ch,CURLOPT_HTTPHEADER,array('Authorization:Bearerxxx'));//Bearertokenfora