Ruoyi Vue分离版 WebSocket后端鉴权认证解决方案

WebSocket 若依鉴权

使用WebSocket实现实时通信,Ruoyi官方教程做法是在SecurityConfig中将ws的地址设为匿名访问: ("/websocket/**").permitAll()

这种方式虽然能够让WebSocket成功连接,但我想在大多数生产环境中,无需鉴权的情况极为少见。而网络上缺乏明确的Ruoyi解决方案,于是动拙笔浅析下我个人的解决方案。

WebSocket Header携带Token,修改Ruoyi鉴权拦截器

前端vue,将Token放置于创建WebSocket连接请求的protocols参数中:

ini 复制代码
    this.ws = new WebSocket(wsuri,[getToken()]);

则其请求头的Sec-Websocket-Protocol参数会替换为Token,如下图所示:

JavaScript WebSocket无法自定义Header ,只有Sec-Websocket-Protocol内容能通过这种方式改变

在此基础上,我们需要让若依识别协议头Sec-Websocket-Protocol中的Token

在若依的JWT鉴权请求过滤器JwtAuthenticationTokenFilter.java中,其拦截逻辑是:

scss 复制代码
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException
{
    //从request请求中解析出LoginUser
    LoginUser loginUser = tokenService.getLoginUser(request);
    //使用这个LoginUser 完成JWT鉴权流程
    if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
    {
        tokenService.verifyToken(loginUser);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }
    chain.doFilter(request, response);
}

进入这个getLoginUser函数,其内容如下:

ini 复制代码
/**
 * 获取用户身份信息
 * @return 用户信息
 */
public LoginUser getLoginUser(HttpServletRequest request)
{
    // **从request中获取携带的Token令牌**
    String token = getToken(request);
    if (StringUtils.isNotEmpty(token))
    {
        try
        {
            Claims claims = parseToken(token);
            // 解析对应的权限以及用户信息
            String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
            LoginUser user = CacheUtils.get(CacheConstants.LOGIN_TOKEN_KEY, uuid, LoginUser.class);
            return user;
        }
        catch (Exception e)
        {
        }
    }
    return null;
}

在第8行代码getToken中,进入即可看到获取Token的逻辑:

typescript 复制代码
/**
 * 获取请求token
 * @param request
 * @return token
 */
private String getToken(HttpServletRequest request)
{
    String token = request.getHeader(header);
    if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
    {
        token = token.replace(Constants.TOKEN_PREFIX, "");
    }
    return token;
}

其中的header变量是ruoyi的全局参数,内容会被装载为在application.yml中定义的Token协议头(默认是Authorization

TOKEN_PREFIX为全局常量,默认为"Bearer ",标识Token开始前的前缀,我们没有前缀因此可忽略

我们的修改逻辑只是在getHeader为空时再次getHeader我们自己的协议头Sec-Websocket-Protocol, 修改后内容为:

ini 复制代码
private String getToken(HttpServletRequest request)
{
    String token = request.getHeader(header);
    if (StringUtils.isEmpty(token)){
        //如果Authorization为空,那么尝试读取Sec-Websocket-Protocol的内容
        token = request.getHeader("Sec-Websocket-Protocol");
    }else if(token.startsWith(Constants.TOKEN_PREFIX)) {
        token = token.replace(Constants.TOKEN_PREFIX, "");
    }
    return token;
}

"Sec-Websocket-Protocol"硬编码即可,无需提取为全局参数,因为WebSocket协议头名称永远无法改变

于是ruoyi便能够从WebSocket连接请求中得到Token并成功鉴权,无需配置匿名访问了。

相关推荐
paopaokaka_luck21 小时前
基于Springboot3+vue3的旅游景区点评系统(AI审核、webSocket聊天、协同过滤算法、Echarts图形化分析)
websocket·网络协议·旅游
熬夜苦读学习1 天前
基于websocket的多用户五子棋网页游戏
linux·服务器·网络·websocket·网络协议·游戏·五子棋
米尔的可达鸭1 天前
深入操作系统 Socket 底层:EPOLLOUT 可写事件管理 + 非阻塞异步
开发语言·网络·数据结构·经验分享·websocket·网络协议
米尔的可达鸭2 天前
深入操作系统 Socket 底层:套接字控制块、FD映射、阻塞IO核心完整实现
arm开发·数据结构·websocket·网络协议·算法·架构·安全架构
永久鳕5 天前
WebSocket 连接成功但行情不更新,应该先查哪几个状态?
网络·websocket·网络协议
JerrySir6 天前
Node.js WebSocket 优雅关闭:一次滚动发布如何避免 1 万次重连风暴
websocket
CryptoPP6 天前
BSE股票K线数据接入实战:从接口调用到前端图表展示
大数据·前端·网络·人工智能·websocket·网络协议
宠友信息6 天前
多端即时通讯源码技术实践,Redis路由、MySQL持久化与Socket接入
spring boot·websocket·mysql·uni-app
脉动数据行情6 天前
Python WebSocket 实时订阅 NQ(小型纳斯达克指数)完整监听程序
websocket·小纳指·纳斯达克·国际股指·python 量化盯盘
console.log('npc')6 天前
AI Agent 前端流式渲染核心技术文档(SSE \+ WebSocket \+ 打字机渲染)
前端·人工智能·websocket