WebScoket和SpringBoot以及Security的一些记录

在构建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)

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值