在构建WebScoket时,考虑到要配合Security的鉴权认证问题,毕竟不能把所有要传输的数据都放到Path上是吧?
一开始根据网上的不少参考资料进行开发,基本上都是基于Session进行的,本以为HttpSession和WebSocketSession这两个会自动进行数据的交换,但是没想到,两者毫不相干,再加上因为Spring容器的自动注入是单例而WebSocket是多对象,无法直接进行自动注入,所有无法直接从SecurityContextHolder.getContext().getAuthentication().getPrincipal()获取到当前登陆的用户对象。
于是开始想着自己往HttpSession中加入principal对象,然后通过拦截器的方式,在与WebScoket进行握手之前,向WebSocket的Session里进行写入principal对象,但是出现了一系列的null异常问题,试了一下午也没有得到好的解决方案。
于是绝定走Redis进行当前登录用户数据的存取。。。。
但是想了一会感觉有点大动干戈了,因此决定使用JWTToken进行当前用户数据的传递(在HttpSession和WebSocketSession之间)。
方案如下:
原理:因为每次通过Http进行登陆请求后得到token返回,之后的每一次请求,尤其是WebSocket请求,只要携带token数据,之后在对应的处理方法里解析就能得到当前登录的用户数据,以该用户的某个属性,比如username为key,将当前的WebSocketSession作为value 进行绑定映射,装入ConcurrentHashMap中进行存储,方便之后的操作。
重点:如何将token写入到WebSocket的请求里?
回答:在握手之间进行修改即可,在WebSocket的配置里刚好有个modifyHandshake方法可以在握手之前就进行request的修改。
注意,结果并不是要从request中取到,而是应该从WebSocket中的Session中取到,因为WebSocket提供的HandshakeRequest 无法直接在@On...等方法中进行直接引用,所有要在modifyHandshake中将request中的目标字段属性写到WebSocket中提供的ServerEndpointConfig 中,之后调用sec.getUserProperties().put()写入字段属性和值。
接下来具体的内容直接看图把。。。。。。
这是WebSocket配置和握手前的request修改:
生成token的地方:
以下为ServerEndPoint的编码:
package com.example.communication.server;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.auth.entity.LoginUserDetailServiceImp;
//import com.example.communication.conf.WebSocketConf;
import com.example.communication.conf.WebSocketConf;
import com.example.communication.entity.Message;
import com.example.core.utils.JWTUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.PathParam;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@javax.websocket.server.ServerEndpoint(value = "/server",configurator = WebSocketConf.class)
@Component
public class ServerEndpoint {
private static ConcurrentHashMap<String, Session> concurrentHashMap=new ConcurrentHashMap<>();
private Session session;
private LoginUserDetailServiceImp loginUserDetailServiceImp;
@Autowired
public void setLoginUserDetailServiceImp(LoginUserDetailServiceImp loginUserDetailServiceImp){
this.loginUserDetailServiceImp=loginUserDetailServiceImp;
}
public static void sendMessage(Message message) throws IOException {
String toUsername = message.getToName();
String msg=message.getMessage();
concurrentHashMap.get(toUsername).getBasicRemote().sendText(JSON.toJSONString(msg));
}
public static void sendToAllMessage(String data) throws IOException {
for(Map.Entry<String, Session> session :concurrentHashMap.entrySet()){
session.getValue().getBasicRemote().sendText(JSON.toJSONString(data));
}
}
public static void sendToOneMessage(Message message){
Object msg = message.getMessage();
}
public static String getUsernameFromToken(Session session){
List<String> token= (List) session.getUserProperties().get("token");
Claims claims = JWTUtil.pareToken(token.get(0));
return (String) claims.get("username");
}
@OnOpen
public void onOpen(Session session) throws IOException {
String currentUsername = getUsernameFromToken(session);
concurrentHashMap.put(currentUsername,session);
System.out.println(session);
sendToAllMessage("欢迎"+currentUsername+"加入了天上人间聊天室,当前聊天室内有"+concurrentHashMap.size()+"人");
}
@OnMessage
public void onMessage(Session session, String msg) throws IOException {
JSONObject parseJson = (JSONObject) JSONObject.parse(msg);
String massage = (String) parseJson.get("message");
String toUsername = (String) parseJson.get("toUsername");
if(toUsername.equals("ALL")){
sendToAllMessage(massage);
}else {
Message oneMassage = new Message(toUsername, massage);
sendMessage(oneMassage);
}
}
@OnClose
public void onClose(Session session) throws IOException {
if (session!=null) {
concurrentHashMap.remove(getUsernameFromToken(session));
sendToAllMessage("用户:"+getUsernameFromToken(session)+"已退出聊天室");
}
}
@OnError
public void onError(Session session) throws IOException {
String username = getUsernameFromToken(session);
sendMessage(new Message("出错了,请重试",username));
}
}
更为具体的可以看我这个项目:SchoolBlog: 目标是建议一套完整的校园web后端服务框架,包含但不限于基本的用户登录鉴权系统,贴吧系统,私信系统和聊天室系统,用户信息系统。 (gitee.com)