JWT实现websocket的token登录拦截认证

关于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;// debuggerif (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<String, String> 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);}@Overridepublic String getHeader(String name) {String headerValue = super.getHeader(name);if (headerMap.containsKey(name)) {headerValue = headerMap.get(name);}return headerValue;}/*** get the Header names*/@Overridepublic Enumeration<String> getHeaderNames() {List<String> names = Collections.list(super.getHeaderNames());for (String name : headerMap.keySet()) {names.add(name);}return Collections.enumeration(names);}@Overridepublic Enumeration<String> getHeaders(String name) {List<String> values = Collections.list(super.getHeaders(name));if (headerMap.containsKey(name)) {values = Arrays.asList(headerMap.get(name));}return Collections.enumeration(values);}
}

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

@Overrideprotected 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 和 BearerrequestWrapper.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]);

后端依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

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);@Resourceprivate UserClient userClient;/*** 初次握手访问前* @param request* @param serverHttpResponse* @param webSocketHandler* @param map* @return*/@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> 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*/@Overridepublic 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...");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/226463.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【教学类-05-02】20231216 (比大小> <=)X-Y之间的比大小88题(补全88格子,有空格分割提示)

作品展示&#xff1a; 背景需求&#xff1a; 1、以前做过一份比大小的题目 【教学类-05-01】20211018 Python VSC 大班 数字比大小&#xff08;&#xff1e; &#xff1c;&#xff09;_vsc比较3位数大小-CSDN博客文章浏览阅读674次。【教学类-05-01】20211018 Python VSC 大班…

MILP加速运算技巧——模型预处理presolve【草稿】

文章目录 预处理是许多MIP求解器在正是求解前会进行的一个步骤&#xff0c;能通过一些简单策略的较低开销&#xff0c;节省最终模型整体求解中不必要的计算&#xff0c;提高求解效率。 这些简单策略包括消除模型中冗余的约束、提升模型的紧凑性&#xff08;tighten formulatio…

如何使用MySQL Workbench将样本数据库导入到MySQL数据库服务器

如何使用MySQL Workbench将样本数据库导入到MySQL数据库服务器 摘要&#xff1a;在本教程中&#xff0c;您将学习如何使用MySQL Workbench将MySQL样本数据库加载到MySQL数据库服务器。之后&#xff0c;您将有classicmodels示例数据库以方便练习和学习MySQL。 步骤1. 下载class…

学习C语言——体会计算机中的0和1

/* 把hello隐写入一个整型数组,这个小程序可以考察是否清楚数据在内存中存储的具体细节。 具体的说&#xff0c;int类型在小端机器上的存储方式是高位在高地址&#xff0c;低位在低地址&#xff0c;从视觉习惯上和我们的日常书写习惯相反&#xff1b; char类型占用…

centos8stream 升级 sqlite3 ,解决 SQLite 3.27 or later is required (found 3.26.0).

服务器环境是centos8stream, 默认的sqlite是 3.26 &#xff0c;因此&#xff0c;需要升级。 sqlite官网&#xff1a;SQLite Download Page 1.从官网下载最新源码包 cd /opt/ wget https://www.sqlite.org/2023/sqlite-autoconf-3440200.tar.gz tar xvf sqlite-autoconf-344020…

TC397 EB MCAL开发从0开始系列 之 [11.3] SPI配置实战 - 使用SPI Async异步EB配置W25Q32

一、SPI配置1、配置目标2、目标依赖2.1 硬件使用2.2 软件使用2.3 新增模块3、EB配置3.1 配置讲解3.2 模块配置3.2.1 MCU配置3.2.2 PORT配置3.2.3 SPI配置3.2.3.1 General配置3.2.3.2 SpiMaxChannel/Job/Sequence配置3.2.3.3 Spi

Linux的权限(二)

目录 前言 文件类型和访问权限&#xff08;事物属性&#xff09; 补充知识 文件类型 文件操作权限 修改文件权限 chmod指令 文件权限值的表示方法 字符表示方法 8进制数值表示方法 权限有无带来的影响 修改文件角色 chown与chgrp指令 目录的rwx权限 补充知识 …

基于net6的zmq调试工具

0.前言 最近在做CS架构的上位机控制软件&#xff0c;服务端和客户端是通过zmq进行通讯的&#xff0c;网上现有的工具都是tcp、串口的调试工具&#xff0c;一直没有找到一个合适的zmq调试工具。就使用C#语言开发了这个简易的zmq调试工具&#xff0c;项目地址ZmqDebuggerTool。 …

小程序禁止滚动穿透,page-meta

使用场景&#xff1a;页面中有弹窗&#xff0c;并且弹窗里数据超过弹窗的高&#xff0c;要在弹窗做滑动操作&#xff0c;当弹窗滑动到底部的时候&#xff0c;继续划动会导致底层页面的滚动&#xff0c;这就是滚动穿透。这种情况对于体验感很不友好。 解决办法&#xff1a;使用p…

机器学习scikit-learn实现

机器学习scikit-learn实现 scikit-learn简介scikit-learn API机器学习基础机器学习算法机器学习工作流程监督学习无监督学习数据集 (Dataset)特征 (Features)标签 (Labels)模型 (Model)训练 (Training)验证 (Validation)测试 (Testing)损失函数 (Loss Function)优化算法 (Opti…

day03-报表技术POIEasyPOI

1、了解百万数据的导入 1.1 需求分析 使用POI基于事件模式解析案例提供的Excel文件 1.2 思路分析 **用户模式&#xff1a;**加载并读取Excel时&#xff0c;是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时&#xff0c;由于不同的运行环境…

ArrayList与LinkLIst

ArrayList 在Java中&#xff0c;ArrayList是java.util包中的一个类&#xff0c;它实现了List接口&#xff0c;是一个动态数组&#xff0c;可以根据需要自动增长或缩小。下面是ArrayList的一些基本特性以及其底层原理的简要讲解&#xff1a; ArrayList基本特性&#xff1a; 动…

少儿编程:是智商税还是未来必备技能?

在当今这个科技日新月异的时代&#xff0c;编程已经成为了一项重要的技能。越来越多的家长开始关注少儿编程教育&#xff0c;希望孩子从小就能掌握这项技能。然而&#xff0c;也有一部分人认为少儿编程是一种“智商税”&#xff0c;认为这种教育方式并不适合所有孩子。那么&…

初识Pandas函数是Python的一个库(继续更新...)

学习网页&#xff1a; Welcome to Python.orghttps://www.python.org/https://www.python.org/https://www.python.org/ Pandas函数库 Pandas是一个Python库&#xff0c;提供了大量的数据结构和数据分析工具&#xff0c;包括DataFrame和Series等。Pandas的函数非常丰富&…

Java泛型(1)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

ubuntu22.04 怎么查看系统日志

在 Ubuntu 22.04 中&#xff0c;查看系统日志可以通过多种方法来实现&#xff0c;这取决于你想要查看的日志类型。以下是一些常见的方法&#xff1a; 使用日志文件 Ubuntu 使用 systemd 作为其初始化系统&#xff0c;它提供了 journalctl 命令来查看日志。要查看系统日志&…

基于FFmpeg,实现播放器功能

一、客户端选择音视频文件 MainActivity package com.anniljing.ffmpegnative;import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Ur…

【每日一题】统计区间中的整数数目

文章目录 Tag题目来源解题思路方法一&#xff1a;平衡二叉搜索树 写在最后 Tag 【平衡二叉搜索树】【设计类】【2023-12-16】 题目来源 2276. 统计区间中的整数数目 解题思路 方法一&#xff1a;平衡二叉搜索树 思路 用一棵平衡二叉搜索树维护插入的区间&#xff0c;树中的…

Redis常用内存淘汰策略?

从淘汰范围来说可以分为不淘汰任何数据、只从设置了到期时间的键中淘汰和从所有键中淘汰三类。而从淘汰算法来分&#xff0c;又主要分为 random&#xff08;随机&#xff09;&#xff0c;LRU&#xff08;最近最少使用&#xff09;&#xff0c;以及 LFU&#xff08;最近最不常使…

Linux--LAMP 平台部署及应用

5.1 LAMP平台概述 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供动态Web站点服务及其应用开发环境。LAMP是一个缩写词&#xff0c;具体包括Linux操作系统&#xff0c;Apache 网站服务器、MySQL数据库服务器&…