关于websocket的请求必须登录,实现websocket需要登录后才可使用,不登录不能建立连接。

后台spring security配置添加websocket的请求可以匿名访问,关于websocket的请求不要认证就可以随意访问,去除匿名访问后,前端在与websocket建立链接无法在请求头里直接加入Authorization token信息,任何关于websocket的请求都无法通过token认证。

解决办法: 使用websocket的Sec-WebSocket-Protocol参数,将token传回后台,后台借助HttpServletRequestWrapper重新生成新的请求信息,实现使用一套登录认证拦截

原理: HttpServletRequestWrapper 采用装饰者模式对HttpServletRequest进行包装,我们可以通过继承HttpServletRequestWrapper 类去重写getParameterValues,getParameter等方法,实际还是调用 HttpServletRequest的相对应方法,但是可以对方法的结果进行改装。

1、前端webSock.js

与webSocke建立连接

var websock = null;

var global_callback = null;

var serverPort = "8080"; // webSocket连接端口

var wsurl = "ws://" + window.location.hostname + ":" + serverPort+"/websocket/message";

/**

* 创建socket连接

* @param callback

*/

function createWebSocket(callback) {

if (websock == null || typeof websock !== WebSocket) {

initWebSocket(callback);

}

}

/**

* 初始化socket

* @param callback

*/

function initWebSocket(callback) {

global_callback = callback;

// 初始化websocket

//token会放在请求的Sec-WebSocket-Protocol参数里面

websock = new WebSocket(wsurl,['你的token']);

websock.onmessage = function (e) {

websocketOnMessage(e);

};

websock.onclose = function (e) {

websocketClose(e);

};

websock.onopen = function () {

websocketOpen();

};

// 连接发生错误的回调方法

websock.onerror = function () {

console.log("WebSocket连接发生错误");

};

}

/**

* 实际调用的方法

*/

function sendSock(agentData ) {

if (websock.readyState === websock.OPEN) {

// 若是ws开启状态

websocketsend(agentData);

} else if (websock.readyState === websock.CONNECTING) {

// 若是 正在开启状态,则等待1s后重新调用

setTimeout(function () {

sendSock(agentData);

}, 1000);

} else {

// 若未开启 ,则等待1s后重新调用

setTimeout(function () {

sendSock(agentData);

}, 1000);

}

}

/**

* 关闭

*/

function closeSock() {

websock.close();

}

/**

* 数据接收

*/

function websocketOnMessage(msg) {

// 收到信息为Blob类型时

let result = null;

// debugger

if (msg.data instanceof Blob) {

const reader = new FileReader();

reader.readAsText(msg.data, "UTF-8");

reader.onload = (e) => {

result = reader.result;

// console.log("websocket收到", result);

global_callback(result);

};

} else {

result = msg.data;

// console.log("websocket收到", result);

global_callback(result);

}

}

/**

* 数据发送

*/

function websocketsend(agentData) {

// console.log("发送数据:" + agentData);

websock.send(agentData);

}

/**

* 异常关闭

*/

function websocketClose(e) {

// console.log("connection closed (" + e.code + ")");

}

function websocketOpen(e) {

// console.log("连接打开");

}

export { sendSock, createWebSocket, closeSock };

2、后端的JWT拦截 前提 spring security配置类不允许/websocket匿名访问,需要鉴权才可以使用

2.1 先实现 HeaderMapRequestWrapper继承HttpServletRequestWrapper 来实现修改HttpServletRequest的请求参数  

/**

* 修改header信息,key-value键值对儿加入到header中

*/

public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {

private static final Logger LOGGER = LoggerFactory.getLogger(HeaderMapRequestWrapper.class);

/**

* construct a wrapper for this request

*

* @param request

*/

public HeaderMapRequestWrapper(HttpServletRequest request) {

super(request);

}

private Map headerMap = new HashMap<>();

/**

* add a header with given name and value

*

* @param name

* @param value

*/

public void addHeader(String name, String value) {

headerMap.put(name, value);

}

@Override

public String getHeader(String name) {

String headerValue = super.getHeader(name);

if (headerMap.containsKey(name)) {

headerValue = headerMap.get(name);

}

return headerValue;

}

/**

* get the Header names

*/

@Override

public Enumeration getHeaderNames() {

List names = Collections.list(super.getHeaderNames());

for (String name : headerMap.keySet()) {

names.add(name);

}

return Collections.enumeration(names);

}

@Override

public Enumeration getHeaders(String name) {

List values = Collections.list(super.getHeaders(name));

if (headerMap.containsKey(name)) {

values = Arrays.asList(headerMap.get(name));

}

return Collections.enumeration(values);

}

}

token过滤器 JWT认证拦截器修改

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)

throws ServletException, IOException

{

/**

* 通过request的url判断是否为websocket请求

* 如果为websocket请求,先处理Authorization

*/

if(request.getRequestURI().contains("/websocket")){

HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper((HttpServletRequest) request);

//修改header信息 将websocket请求中的Sec-WebSocket-Protocol值取出处理后放入认证参数key

//此处需要根据自己的认证方式进行修改 Authorization 和 Bearer

requestWrapper.addHeader("Authorization","Bearer "+request.getHeader("Sec-WebSocket-Protocol"));

request = (HttpServletRequest) requestWrapper;

}

//处理完后就可以走系统正常的登录认证权限认证逻辑了

//下边就是自己的验证token 登陆逻辑

LoginUser loginUser = getLoginUser(request);

·······

chain.doFilter(request, response);

}

websocket如何携带header或参数

var ws = new WebSocket("ws://url?userid=1");

ebsocket如何携带header

最近项目组接到新需求,需要websocket连接时,在header里面传递token,由于token较长,不适合在url中直接拼接。

网上查阅了相关的资料,websocket没有像http那样可以只定义请求头的一些参数,只有一个Sec-WebSocket-Protocol属性用于自定义子协议。

意思就是说可以将token当做一个参数传递给后端,只不过参数的封装形式是以Sec-WebSocket-Protocol为key的一个header属性值。

后台接收到websocket连接后,可以通过下述代码获取到token值。

request.getHeader("Sec-WebSocket-Protocol")

需要注意的是,如果前端通过websocket连接时指定了Sec-WebSocket-Protocol,后端接收到连接后,必须原封不动的将Sec-WebSocket-Protocol头信息返回给前端,否则连接会抛出异常。

前端代码格式

 let WebSocket = new WebSocket(url,[protocols]);

url 要连接的URL;WebSocket服务器响应的URL。 protocols 可选 一个协议字符串或者一个包含协议字符串的数组。这些字符串用于指定子协议,这样单个服务器可以实现多个WebSocket子协议(例如,您可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互)。如果不指定协议字符串,则假定为空字符串。

const webSocket = new WebSocket(url,[token]);

后端依赖

org.springframework.boot

spring-boot-starter-websocket

java代码

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.http.HttpStatus;

import org.springframework.http.server.ServerHttpRequest;

import org.springframework.http.server.ServerHttpResponse;

import org.springframework.http.server.ServletServerHttpRequest;

import org.springframework.http.server.ServletServerHttpResponse;

import org.springframework.stereotype.Component;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.server.HandshakeInterceptor;

import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import javax.annotation.Resource;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.util.Map;

import java.util.Objects;

/**

* webSocket握手拦截器

* @since 2023年12月16日16:57:52

*/

@Component

public class PortalHandshakeInterceptor implements HandshakeInterceptor {

private final Logger logger = LoggerFactory.getLogger(PortalHandshakeInterceptor.class);

@Resource

private UserClient userClient;

/**

* 初次握手访问前

* @param request

* @param serverHttpResponse

* @param webSocketHandler

* @param map

* @return

*/

@Override

public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map map) {

logger.info("HandshakeInterceptor beforeHandshake start...");

if (request instanceof ServletServerHttpRequest) {

HttpServletRequest req = ((ServletServerHttpRequest) request).getServletRequest();

String authorization = req.getHeader("Sec-WebSocket-Protocol");

logger.info("authorization = {}", authorization);

if (Objects.isNull(userClient)){

userClient = (UserClient) SpringContextUtil.getBean("userClient");

}

TokenVO tokenVO = userClient.checkToken(authorization, authorization);

Long userId = tokenVO.getUserId();

logger.info("userId = {}, tokenVO = {}", userId, tokenVO);

if (Objects.isNull(userId)){

serverHttpResponse.setStatusCode(HttpStatus.FORBIDDEN);

logger.info("【beforeHandshake】 authorization Parse failure. authorization = {}", authorization);

return false;

}

//存入数据,方便在hander中获取,这里只是在方便在webSocket中存储了数据,并不是在正常的httpSession中存储,想要在平时使用的session中获得这里的数据,需要使用session 来存储一下

map.put(MagicCode.WEBSOCKET_USER_ID, userId);

map.put(MagicCode.WEBSOCKET_CREATED, System.currentTimeMillis());

map.put(MagicCode.TOKEN, authorization);

map.put(HttpSessionHandshakeInterceptor.HTTP_SESSION_ID_ATTR_NAME, req.getSession().getId());

logger.info("【beforeHandshake】 WEBSOCKET_INFO_MAP: {}", map);

}

logger.info("HandshakeInterceptor beforeHandshake end...");

return true;

}

/**

* 初次握手访问后,将前端自定义协议头Sec-WebSocket-Protocol原封不动返回回去,否则会报错

* @param request

* @param serverHttpResponse

* @param webSocketHandler

* @param e

*/

@Override

public void afterHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

logger.info("HandshakeInterceptor afterHandshake start...");

HttpServletRequest httpRequest = ((ServletServerHttpRequest) request).getServletRequest();

HttpServletResponse httpResponse = ((ServletServerHttpResponse) serverHttpResponse).getServletResponse();

if (StringUtils.isNotEmpty(httpRequest.getHeader("Sec-WebSocket-Protocol"))) {

httpResponse.addHeader("Sec-WebSocket-Protocol", httpRequest.getHeader("Sec-WebSocket-Protocol"));

}

logger.info("HandshakeInterceptor afterHandshake end...");

}

}

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: