Ruoyi-Vue 静态资源权限鉴权:非登录不可访问

一. 背景

移除/profile下静态资源访问权限后,富文本等组件中的图片加载失败!!!
使用ruoyi-vue3.8.9过程中发现上传的在ruoyi.profile下的文件未登录直接使用链接就可以访问下载,感觉这样不太安全,所以想对其进行鉴权限制,修改为只有用户登陆的时候才可以访问

二. 步骤

1. 创建配置类

package com.ruoyi.framework.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;import java.util.List;/*** @author ning* @create 2025-08-25-10:17*/
@Configuration
public class ReferConfig {/*** 是否开启防盗链拦截*/@Value("${refer.enabled:false}")private boolean enabled;/*** 允许的Referer请求*/@Value("#{'${refer.allowed-origins}'.split(',')}")private List<String> allowedOrigins;/*** 策略*/// TODO-ning at 2025/8/25 for 策略方式暂时不使用,后续考虑增加策略@Value("${refer.policy:same-origin}")private String policy;/*** 是否开启token验证*/@Value("${refer.token-enabled:false}")private boolean tokenEnabled;public boolean isEnabled() {return enabled;}public void setEnabled(boolean enabled) {this.enabled = enabled;}public List<String> getAllowedOrigins() {return allowedOrigins;}public void setAllowedOrigins(List<String> allowedOrigins) {this.allowedOrigins = allowedOrigins;}public String getPolicy() {return policy;}public void setPolicy(String policy) {this.policy = policy;}public boolean isTokenEnabled() {return tokenEnabled;}public void setTokenEnabled(boolean tokenEnabled) {this.tokenEnabled = tokenEnabled;}
}

2. 新增配置

ruoyi-admin/src/main/resources/application.yml

# 防盗链配置
refer:# 防盗链开关enabled: true# 白名单(多个用逗号分隔),新增前台路由即可allowed-origins: http://localhost:8080,http://127.0.0.1:8080# 校验token,开启后使用上面的token配置进行校验token-enabled: true

3. 新增拦截器

1)代码

路径:ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor

package com.ruoyi.framework.interceptor;import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.framework.config.ReferConfig;
import com.ruoyi.framework.web.service.TokenService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;/*** @author ning* @create 2025-08-25-10:13*/
@Component
public class StaticResourceInterceptor implements HandlerInterceptor {// Cookie中保存的Token前缀private static final String COOKIE_TOKEN_PREFIX = "Admin-Token";// Redis中保存的登录Token前缀private static final String LOGIN_TOKEN_KEY = "login_tokens:";@Autowiredprivate ReferConfig referConfig;@Autowiredprivate TokenService tokenService;@Autowiredprivate RedisCache redisCache;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果未开启防盗链功能,直接放行if (!referConfig.isEnabled()) {return true;}// 2. 检查Referer是否合法boolean isRefererValid = checkReferer(request);// 3. 检查Token是否有效(若开启Token校验)if (isRefererValid && referConfig.isTokenEnabled()) {if (checkTokenValid(request)) {return true;}} else {if (isRefererValid) {return true;}}// 4. 验证失败,返回403错误response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.getWriter().write("Access denied: Invalid referer or token");return false;}/*** 检查Referer是否在允许的列表中*/private boolean checkReferer(HttpServletRequest request) {String referer = request.getHeader("Referer");List<String> allowedOrigins = referConfig.getAllowedOrigins();// 如果允许的来源列表为空,放行if (allowedOrigins == null || allowedOrigins.isEmpty()) {return true;}// 处理空Referer情况if (referer == null || referer.isEmpty()) {return allowedOrigins.contains("*") || allowedOrigins.contains("");}// 检查Referer是否匹配允许的来源return allowedOrigins.stream().anyMatch(origin ->"*".equals(origin) || referer.startsWith(origin) ||getDomainFromUrl(referer).equals(getDomainFromUrl(origin)));}/*** 检查Token是否有效(从Cookie获取并验证)*/private boolean checkTokenValid(HttpServletRequest request) {try {// 从Cookie获取Token(优化:找到后立即退出循环)String token = getTokenFromCookie(request.getCookies());if (token == null) {return false;}// 解密Token(捕获JWT解析异常,避免500错误)Claims claims = tokenService.parseToken(token);if (claims == null) {return false;}// 验证Redis中的用户登录状态String userTokenId = claims.get(Constants.LOGIN_USER_KEY, String.class);return userTokenId != null && redisCache.hasKey(LOGIN_TOKEN_KEY + userTokenId);} catch (JwtException e) {// Token解析失败(过期、篡改等)return false;} catch (Exception e) {// 其他异常(如Redis连接失败)return false;}}/*** 从Cookie中提取Admin-Token(优化遍历逻辑)*/private String getTokenFromCookie(Cookie[] cookies) {if (cookies == null) {return null;}for (Cookie cookie : cookies) {if (COOKIE_TOKEN_PREFIX.equals(cookie.getName())) {return cookie.getValue(); // 找到后直接返回,减少循环}}return null;}/*** 从URL中提取域名*/private String getDomainFromUrl(String url) {try {if (!url.startsWith("http")) {url = "http://" + url;}java.net.URL urlObj = new java.net.URL(url);String host = urlObj.getHost();// 去除www.前缀,统一比较if (host.startsWith("www.")) {host = host.substring(4);}return host;} catch (Exception e) {return url;}}
}

2)解释

为啥会从Cookie里获取Token?
分析代码发现,在用户登录后,会将加密后的token存到Cookie中
代码路径:ruoyi-ui/src/store/modules/user.js
在这里插入图片描述
ruoyi-ui/src/utils/auth.js
在这里插入图片描述
所以后续所有链接都会携带该加密token
在这里插入图片描述
token生成代码:
com.ruoyi.framework.web.service.TokenService#createToken(com.ruoyi.common.core.domain.model.LoginUser)
在这里插入图片描述
同类里已经有解密方法:
在这里插入图片描述
所以我拦截器里面我直接使用的

4. 配置拦截器

方法路径:com.ruoyi.framework.config.ResourcesConfig#addInterceptors

在这里插入图片描述

5. 修改/profile/**鉴权

代码路径:org.springframework.security.web.SecurityFilterChain
移除原有的,新增鉴权
在这里插入图片描述

三、修改完毕!!!!撒花

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

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

相关文章

关于窗口关闭释放内存,主窗口下的子窗口关闭释放不用等到主窗口关闭>setAttribute(Qt::WA_DeleteOnClose);而且无需手动释放

‌QWidget重写closeEvent后&#xff0c;点击关闭时释放内存会调用析构函数‌&#xff0c;但需注意内存释放的时机和方式。 关闭事件与析构函数的关系 重写closeEvent时&#xff0c;若在事件处理中调用deleteLater()或手动删除对象&#xff0c;析构函数会被触发。但需注意&#…

C# 简单工厂模式(构建简单工厂)

构建简单工厂 现在很容易给出简单工厂类。只检测逗号是否存在&#xff0c;然后返回其中的一个类的实例。 public class NameFactory {public NameFactory(){}public Namer getName(string name){int iname.IndexOf(",");if(i>0)return new LastFirst(name);else{r…

uniappx与uniapp的区别

uniappx与uniapp的定位差异uniappx是DCloud推出的扩展版框架&#xff0c;基于uniapp进行功能增强&#xff0c;主要面向需要更复杂原生交互或跨平台深度定制的场景。uniapp则是标准版&#xff0c;适用于常规的跨平台应用开发&#xff0c;强调开发效率和代码复用。功能扩展性unia…

vue实现拖拉拽效果,类似于禅道首页可拖拽排布展示内容(插件-Grid Layout)

vue实现拖拉拽效果&#xff08;插件-Grid Layout&#xff09; 这个是类似与禅道那种首页有多个指标模块&#xff0c;允许用户自己拼装内容的那种感觉。 实现效果 插件资料 vue3版本 如果项目是vue3 的话使用的是 Grid Layout Plus。 官网&#xff1a;https://grid-layout-pl…

在Excel和WPS表格中打印时加上行号和列标

在电脑中查看excel和WPS表格的工作表时&#xff0c;能看到行号&#xff08;12345.....&#xff09;和列标&#xff08;ABCDE...&#xff09;&#xff0c;但是打印出来以后默认是没有行号和列标的&#xff0c;如果要让打印&#xff08;或者转为PDF&#xff09;出来以后仍能看到行…

设计模式:原型模式(Prototype Pattern)

文章目录一、原型模式的概念二、原型模式的结构三、原型注册机制四、完整示例代码一、原型模式的概念 原型模式是一种创建型设计模式&#xff0c; 使你能够复制已有对象&#xff0c; 而又无需使代码依赖它们所属的类。通过复制&#xff08;克隆&#xff09;已有的实例来创建新的…

Linux系统网络管理

一、网络参数配置1、图形化配置#开启 [rootlocalhost ~]# systemctl start NetworkManager #关闭 [rootlocalhost ~]# systemctl stop NetworkManager #关闭并开机不自启 [rootlocalhost ~]# systemctl disable --now NetworkManager #开启并开机自启 [rootlocalhost ~]# syste…

服务器初始化

服务器初始化文章目录服务器初始化1. 配置国内 Yum 源&#xff08;加速软件安装&#xff09;2. 更新系统与安装必备工具3. 网络连接验证4. 配置主机名5. 同步时间6. 配置防火墙 (两种方式)6.1 iptables整体思路详细步骤第 1 步&#xff1a;停止并禁用 Firewalld第 2 步&#xf…

基于YOLOv11训练无人机视角Visdrone2019数据集

【闲鱼服务】 基于YOLOv11训练无人机视角Visdrone2019数据集Visdrone2019数据集介绍数据集格式数据预处理yolov11模型训练数据分布情况可视化训练结果Visdrone2019数据集介绍 VisDrone 数据集 是由中国天津大学机器学习和数据挖掘实验室 AISKYEYE 团队创建的大规模基准。它包含…

基于Springboot 的智能化社区物业管理平台的设计与实现(代码+数据库+LW)

摘 要随着智慧社区的普及&#xff0c;传统的物业管理方式已经无法满足现代社区的需求。目前&#xff0c;很多社区管理中存在信息不畅通、工作效率低以及居民服务体验不佳等问题。为了解决这些问题&#xff0c;我们基于SpringBoot框架开发了一套智能化社区物业管理平台&#xf…

【深度学习新浪潮】SAM 2实战:Meta新一代视频分割模型的实时应用与Python实现

引言:从图像到视频的分割革命 上周AI领域最引人注目的计算机视觉进展,当属Meta在SAM(Segment Anything Model)基础上推出的SAM 2模型持续引发的技术热潮。尽管SAM 2最初发布于2024年,但最新更新的2.1版本(2024年9月发布)凭借其突破性的实时视频分割能力,在自动驾驶、影…

sqli-labs靶场安装与使用指导教程(3种方法:通用版、php7版、Docker版)

目录 一、SQLI-LABS靶场 1、核心特点 2、关卡难度 二、源码安装法 1、开启Web服务和数据库服务 2、靶场源码下载 &#xff08;1&#xff09;通用版本 &#xff08;2&#xff09;PHP7版本 3、部署sqli-labs靶场 &#xff08;1&#xff09;确认网站根目录位置 &#x…

从零开始配置前端环境及必要软件安装

从零开始配置前端环境及必要软件安装一、安装编辑器二、安装浏览器三、安装Git版本控制工具四、Node.js 和 npm 环境变量配置1. 安装 Node.js 和 npm2. 配置全局模块和缓存目录3. 设置环境变量4. 更换 npm 镜像源5. 测试配置五、hosts文件六、辅助应用markdown&#xff08;笔记…

神经网络模型搭建及手写数字识别案例

代码实现&#xff1a;import torch print(torch.__version__) from torch import nn from torch.utils.data import DataLoader from torchvision import datasets from torchvision.transforms import ToTensor training_data datasets.MNIST(rootdata,trainTrue,downloadTru…

CRMEB标准版PC扫码登录配置教程(PHP版)

需要在开放平台创建网站应用 微信开放平台地址&#xff1a;https://open.weixin.qq.com/ 1、注册网站应用 2、填写信息&#xff0c;网站地址填写前台访问的域名就行 3、复制开放平台AppId和开放平台AppSecret 4、粘贴到后台应用配置的PC站点配置里

AmazeVault 核心功能分析,认证、安全和关键的功能

系列文章目录 Amazevault 是一款专注于本地安全的桌面密码管理器 AmazeVault 核心功能分析&#xff0c;认证、安全和关键的功能 AmazeVault 快速开始&#xff0c;打造个人专属桌面密码管理器 文章目录系列文章目录前言一、认证系统核心组件图形解锁实现图形锁控件 (PatternLoc…

Coze用户账号设置修改用户昵称-后端源码

前言 本文将深入分析Coze Studio项目的用户昵称修改功能后端实现&#xff0c;通过源码解读来理解整个昵称更新流程的架构设计和技术实现。用户昵称修改作为用户个人信息管理系统的重要组成部分&#xff0c;主要负责处理用户显示名称的更新和管理。 昵称修改功能相对简单但不失重…

基于WebTransport(底层QUIC)实现视频传输(HTML+JavaScript)

工作目录和基本操作见博客《基于HTTP3的WebTransport实践》&#xff0c;在此仅展示服务器端和客户端代码。 服务器端 import { readFile } from "node:fs/promises"; import { createServer } from "node:https"; import {Server} from "socket.io&qu…

Git 怎么仓库迁移?error: remote origin already exists.怎么解决

1. 确认本地已经有完整的 旧 仓库你本地应该有旧的项目&#xff0c;并且能看到 .git 文件夹。如果没有&#xff0c;可以先从旧仓库克隆下来&#xff1a;git clone 旧仓库git地址 cd 旧代码目录比如&#xff1a;git clone https://gitee.com/star-information/esflow-pro-api.gi…

【语法】【C+V】本身常用图表类型用法快查【CSDN不支持,VSCODE可用】

文章目录Mermaid 简介Mermaid 由三部分组成Mermaid 的使用方法复杂图表结构的技巧饼图简介饼图语法饼图示例雷达图简介雷达图语法雷达图语法细节标题轴曲线选项雷达图示例时间线简介时间线语法时间线示例树形图简介树形图语法树形图示例数据包图简介数据包图语法1&#xff1a;数…