若依vue -【 44】
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
44 服务监控讲解
1 需求
显示CPU、内存、服务器信息、Java虚拟机信息、磁盘状态的信息
2 前端
- RuoYi-Vue\ruoyi-ui\src\views\monitor\server\index.vue
<script> import { getServer } from "@/api/monitor/server"; export default { name: "Server", data() { return { // 服务器信息 server: [] }; }, created() { this.getList(); this.openLoading(); }, methods: { /** 查询服务器信息 */ getList() { getServer().then(response => { this.server = response.data; this.$modal.closeLoading(); }); }, // 打开加载层 openLoading() { this.$modal.loading("正在加载服务监控数据请稍候"); } } }; </script>
- RuoYi-Vue\ruoyi-ui\src\api\monitor\server.js
import request from '@/utils/request' // 获取服务信息 export function getServer() { return request({ url: '/monitor/server', method: 'get' }) }
3 后端
- RuoYi-Vue\pom.xml引入开源框架获取cpu、内存、磁盘等信息。
<!-- 获取系统信息 --> <dependency> <groupId>com.github.oshi</groupId> <artifactId>oshi-core</artifactId> <version>${oshi.version}</version> </dependency>
- RuoYi-Vue\ruoyi-framework\pom.xml引入开源框架获取cpu、内存、磁盘等信息。
<!-- 获取系统信息 --> <dependency> <groupId>com.github.oshi</groupId> <artifactId>oshi-core</artifactId> </dependency>
- ServerController#getInfo
/** * 服务器监控 * * @author ruoyi */ @RestController @RequestMapping("/monitor/server") public class ServerController { @PreAuthorize("@ss.hasPermi('monitor:server:list')") @GetMapping() public AjaxResult getInfo() throws Exception { // 实例化 Server server = new Server(); // 设置相关的值 server.copyTo(); // 返回给前端 return AjaxResult.success(server); } }
- Server返回数据
/** * 服务器相关信息 * * @author ruoyi */ public class Server { private static final int OSHI_WAIT_SECOND = 1000; /** * CPU相关信息 */ private Cpu cpu = new Cpu(); /** * 內存相关信息 */ private Mem mem = new Mem(); /** * JVM相关信息 */ private Jvm jvm = new Jvm(); /** * 服务器相关信息 */ private Sys sys = new Sys(); /** * 磁盘相关信息 */ private List<SysFile> sysFiles = new LinkedList<SysFile>(); }
- Server#copyTo数据填充
/** * 数据填充 */ public void copyTo() throws Exception { // SystemInfo框架api SystemInfo si = new SystemInfo(); // SystemInfo框架api HardwareAbstractionLayer hal = si.getHardware(); // cpu信息大多从开源框架的API中获取 setCpuInfo(hal.getProcessor()); // 内存信息 setMemInfo(hal.getMemory()); // 服务器信息 setSysInfo(); // 虚拟机信息jdk的API setJvmInfo(); // 磁盘信息 setSysFiles(si.getOperatingSystem()); }
- Cpu.java
/** * CPU相关信息 * * @author ruoyi */ public class Cpu { /** * 核心数 */ private int cpuNum; /** * CPU总的使用率 */ private double total; /** * CPU系统使用率 */ private double sys; /** * CPU用户使用率 */ private double used; /** * CPU当前等待率 */ private double wait; /** * CPU当前空闲率 */ private double free; }
- Jvm.java
/** * JVM相关信息 * * @author ruoyi */ public class Jvm { /** * 当前JVM占用的内存总数(M) */ private double total; /** * JVM最大可用内存总数(M) */ private double max; /** * JVM空闲内存(M) */ private double free; /** * JDK版本 */ private String version; /** * JDK路径 */ private String home; }
- Mem.java
/** * 內存相关信息 * * @author ruoyi */ public class Mem { /** * 内存总量 */ private double total; /** * 已用内存 */ private double used; /** * 剩余内存 */ private double free; }
- Sys.java
/** * 系统相关信息 * * @author ruoyi */ public class Sys { /** * 服务器名称 */ private String computerName; /** * 服务器Ip */ private String computerIp; /** * 项目路径 */ private String userDir; /** * 操作系统 */ private String osName; /** * 系统架构 */ private String osArch; }
- SysFile.java
/** * 系统文件相关信息 * * @author ruoyi */ public class SysFile { /** * 盘符路径 */ private String dirName; /** * 盘符类型 */ private String sysTypeName; /** * 文件类型 */ private String typeName; /** * 总大小 */ private String total; /** * 剩余大小 */ private String free; /** * 已经使用量 */ private String used; /** * 资源的使用率 */ private double usage; }
4 拓展
4 拓展集群
如果要做成集群就需要再扩展一下
- 需要一个表去控制把服务器的的名称、IP、地址等相关信息入库。
- 然后需要做实时的监控比如使用websocket。
45 系统接口使用详解
1 需求
api文档
2 效果
3 使用
- 第一步获取token
- 第二步点击" Authorize "配置token
- 第三步传参数调接口
4 拓展
-
可以使用很多第三方插件把界面优化的更漂亮。
46 系统接口实现详解
1 RuoYi-Vue\pom.xml
<properties>
<swagger.version>3.0.0</swagger.version>
</properties>
<!-- Swagger3依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
<exclusions>
<!--
排除它和前端的UI有冲突
-->
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
2 RuoYi-Vue\ruoyi-admin\pom.xml
<!-- swagger3
界面是swagger ui渲染出来的。
而sawagger ui的所有页面都在它的jar包中。
-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
<!--
因为与前端ui冲突所以做了排除。
并把版本降了一下。
-->
<!-- 防止进入swagger页面报类型转换错误排除3.0.0中的引用手动增加1.6.2版本 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.2</version>
</dependency>
3 前端
- ruoyi-ui\src\views\tool\swagger\index.vue
<template> <!--url指定了后台的接口地址 --> <i-frame :src="url" /> </template> <script> import iFrame from "@/components/iFrame/index"; export default { name: "Swagger", components: { iFrame }, data() { return { url: process.env.VUE_APP_BASE_API + "/swagger-ui/index.html" }; }, }; </script>
4 后端
- ResourcesConfig#addResourceHandlersswagger ui映射。swagger ui怎么去加载的呢
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { /** 本地文件上传路径 */ registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); /** swagger配置 */ /** * 映射配置 * "/swagger-ui/**"访问的地址。 * "classpath:/META-INF/resources/webjars/springfox-swagger-ui/"找到对应的路径在jar包中。 * 因为默认情况下swagger ui首页是英文版的但是可以对它做一些国际化的操作。 */ registry.addResourceHandler("/swagger-ui/**") .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());; }
- SwaggerConfigswagger配置类
package com.ruoyi.web.core.config; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.ruoyi.common.config.RuoYiConfig; import io.swagger.annotations.ApiOperation; import io.swagger.models.auth.In; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiKey; import springfox.documentation.service.AuthorizationScope; import springfox.documentation.service.Contact; import springfox.documentation.service.SecurityReference; import springfox.documentation.service.SecurityScheme; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; /** * Swagger2的接口配置 */ @Configuration public class SwaggerConfig { /** * 系统基础配置。 * 读取项目相关配置文件常用的几个属性。 * */ @Autowired private RuoYiConfig ruoyiConfig; /** 是否开启swagger */ @Value("${swagger.enabled}") private boolean enabled; /** * 设置请求的统一前缀。 * 即使用swagger ui页面调用接口进行测试时请求url都会拼接这样的一个前缀。不然就映射不到后台来了因为前端有对应的路由控制。 * 为什么需要请求前缀呢因为路由有映射。 * 当然这个是可以改的可根据实际情况去调整。 * */ @Value("${swagger.pathMapping}") private String pathMapping; /** * 创建API */ @Bean public Docket createRestApi() { // 版本DocumentationType.OAS_30 return new Docket(DocumentationType.OAS_30) // 是否启用Swagger .enable(enabled) // 用来创建该API的基本信息展示在文档的页面中自定义展示的信息 .apiInfo(apiInfo()) // 设置哪些接口暴露给Swagger展示 .select() // 暴露方式1扫描所有有注解的api用这种方式更灵活 .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 暴露方式2扫描指定包中的swagger注解 // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) // 暴露方式3扫描所有 .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() /* 设置安全模式swagger可以设置访问token */ /** * 默认情况下直接访问会提示没有权限,所以需要设置一下安全模式即页面中的” Authorize按钮 ”。 * 点击“ Authorize按钮 ”可以看到接口需要哪些属性。 * 可以按实际情况传更多的参数。 */ .securitySchemes(securitySchemes()) .securityContexts(securityContexts()) .pathMapping(pathMapping); } /** * 安全模式这里指定token通过Authorization头请求头传递 */ private List<SecurityScheme> securitySchemes() { List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>(); apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); return apiKeyList; } /** * 安全上下文 */ private List<SecurityContext> securityContexts() { List<SecurityContext> securityContexts = new ArrayList<>(); securityContexts.add( SecurityContext.builder() .securityReferences(defaultAuth()) .operationSelector(o -> o.requestMappingPattern().matches("/.*")) .build()); return securityContexts; } /** * 默认的安全上引用 */ private List<SecurityReference> defaultAuth() { AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; List<SecurityReference> securityReferences = new ArrayList<>(); securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); return securityReferences; } /** * 添加摘要信息。 * 自定义展示的api基本信息。 */ private ApiInfo apiInfo() { // 用ApiInfoBuilder进行定制 return new ApiInfoBuilder() // 设置标题 .title("标题若依管理系统_接口文档") // 描述 .description("描述用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") // 作者信息 .contact(new Contact(ruoyiConfig.getName(), null, null)) // 版本 .version("版本号:" + ruoyiConfig.getVersion()) .build(); } }
- RuoYiConfig系统基础配置
/** * 读取项目相关配置。 * 基础配置对应配置文件中的常用的几个属性。 */ @Component @ConfigurationProperties(prefix = "ruoyi") public class RuoYiConfig { /** 项目名称 */ private String name; /** 版本 */ private String version; /** 版权年份 */ private String copyrightYear; /** 实例演示开关 */ private boolean demoEnabled; /** 上传路径 */ private static String profile; /** 获取地址开关 */ private static boolean addressEnabled; /** 验证码类型 */ private static String captchaType; /** * 获取导入上传路径 */ public static String getImportPath() { return getProfile() + "/import"; } /** * 获取头像上传路径 */ public static String getAvatarPath() { return getProfile() + "/avatar"; } /** * 获取下载路径 */ public static String getDownloadPath() { return getProfile() + "/download/"; } /** * 获取上传路径 */ public static String getUploadPath() { return getProfile() + "/upload"; } }
- application.yml
# 项目相关配置 ruoyi: # 名称 name: RuoYi # 版本 version: 3.8.6 # 版权年份 copyrightYear: 2023 # 实例演示开关 demoEnabled: true # 文件路径 示例 Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath profile: D:/ruoyi/uploadPath # 获取ip地址开关 addressEnabled: false # 验证码类型 math 数字计算 char 字符验证 captchaType: math
- SwaggerConfig#securitySchemes安全模式这里指定token通过Authorization头请求头传递
/** * 安全模式这里指定token通过Authorization头请求头传递 */ private List<SecurityScheme> securitySchemes() { List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>(); apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); return apiKeyList; }
5 如何汉化系统接口Swagger
想必很多小伙伴都曾经使用过Swagger
但是打开UI界面是纯英文的界面并不太友好作为国人还是习惯中文界面。
- 找到m2/repository/io/springfox/springfox-swagger-ui/x.x.x/springfox-swagger-ui-x.x.x.jar
- 修改对应springfox-swagger-ui-x.x.x.jar包内
resources
目录下swagger-ui.html
添加如下JS代码<!-- 选择中文版 --> <script src='webjars/springfox-swagger-ui/lang/translator.js' type='text/javascript'></script> <script src='webjars/springfox-swagger-ui/lang/zh-cn.js' type='text/javascript'></script>
- 本地修改结束后在覆盖压缩包文件重启就实现汉化了
6 编码案例
@Api("用户信息管理")
public class TestController extends BaseController{
// 暴露注解
@ApiOperation("获取用户列表")
// 参数注解
/**
* name参数
* value显示的文本
* required是否必填
* dataType数据类型
* paramType参数类型。如path对应的是getUser(@PathVariable Integer userId)中的@PathVariable传参方式。
* dataTypeClass数据类型
*/
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
public R<UserEntity> getUser(@PathVariable Integer userId)
{
List<UserEntity> userList = new ArrayList<UserEntity>(users.values());
return R.ok(userList);
}
}
完整代码
/**
* swagger 用户测试方法
*
* @author ruoyi
*/
@Api("用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
{
private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
{
users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
}
@ApiOperation("获取用户列表")
@GetMapping("/list")
public R<List<UserEntity>> userList()
{
List<UserEntity> userList = new ArrayList<UserEntity>(users.values());
return R.ok(userList);
}
@ApiOperation("获取用户详细")
/**
* name参数
* value显示的文本
* required是否必填
* dataType数据类型
* paramType参数类型。如path对应的是getUser(@PathVariable Integer userId)中的@PathVariable传参方式。
* dataTypeClass数据类型
*/
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@GetMapping("/{userId}")
public R<UserEntity> getUser(@PathVariable Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
return R.ok(users.get(userId));
}
else
{
return R.fail("用户不存在");
}
}
@ApiOperation("新增用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
@ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
})
@PostMapping("/save")
public R<String> save(UserEntity user)
{
if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
{
return R.fail("用户ID不能为空");
}
users.put(user.getUserId(), user);
return R.ok();
}
@ApiOperation("更新用户")
@PutMapping("/update")
public R<String> update(@RequestBody UserEntity user)
{
if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
{
return R.fail("用户ID不能为空");
}
if (users.isEmpty() || !users.containsKey(user.getUserId()))
{
return R.fail("用户不存在");
}
users.remove(user.getUserId());
users.put(user.getUserId(), user);
return R.ok();
}
@ApiOperation("删除用户信息")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@DeleteMapping("/{userId}")
public R<String> delete(@PathVariable Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
users.remove(userId);
return R.ok();
}
else
{
return R.fail("用户不存在");
}
}
}
/**
* 参数类型为实体类
*/
@ApiModel(value = "UserEntity", description = "用户实体")
class UserEntity
{
// 注解显示的文本。如果没有这个注解默认使用属性名称。
@ApiModelProperty("用户ID")
private Integer userId;
@ApiModelProperty("用户名称")
private String username;
@ApiModelProperty("用户密码")
private String password;
@ApiModelProperty("用户手机")
private String mobile;
public UserEntity()
{
}
public UserEntity(Integer userId, String username, String password, String mobile)
{
this.userId = userId;
this.username = username;
this.password = password;
this.mobile = mobile;
}
// get/set方法
}
47 XSS脚本过滤详解
1 XSS攻击的定义
跨站脚本攻击XSS是最普遍的Web应用安全漏洞。
2 模拟xss攻击示例1页面效果
ruoyi-ui\src\views\system\config\index.vuev-text效果正确
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参数名称" prop="configName">
<!--
<el-input v-model="form.configName" placeholder="请输入参数名称" />
-->
<!-- v-text其实是没有问题的因为它默认会给我们做一些处理。 -->
<div v-text="form.configName"></div>
</el-form-item>
<el-form-item label="参数键名" prop="configKey">
<el-input v-model="form.configKey" placeholder="请输入参数键名" />
</el-form-item>
<el-form-item label="参数键值" prop="configValue">
<el-input v-model="form.configValue" placeholder="请输入参数键值" />
</el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-radio-group v-model="form.configType">
<el-radio
v-for="dict in dict.type.sys_yes_no"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
ruoyi-ui\src\views\system\config\index.vuev-html效果不正确。点击出现脚本。这种情况下可能会造成恶意代码没有经过特殊处理的脚本注入到html里面去进行攻击
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参数名称" prop="configName">
<!--
<el-input v-model="form.configName" placeholder="请输入参数名称" />
-->
<!-- v-text其实是没有问题的因为它默认会给我们做一些处理。 -->
<!--<div v-text="form.configName"></div>-->
<!--
v-html有问题。
v-html即支持标签为一个HTML类型。
-->
<div v-html="form.configName"></div>
</el-form-item>
<el-form-item label="参数键名" prop="configKey">
<el-input v-model="form.configKey" placeholder="请输入参数键名" />
</el-form-item>
<el-form-item label="参数键值" prop="configValue">
<el-input v-model="form.configValue" placeholder="请输入参数键值" />
</el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-radio-group v-model="form.configType">
<el-radio
v-for="dict in dict.type.sys_yes_no"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
3 模拟xss攻击示例2数据库效果
数据库表 sys_conifg。如下图所示如果没有做XSS处理在数据库里面就会完整的把标签和脚本进行入库是非常危险的
4 如何在项目处理xss攻击?
- application.ymlxss处理开关
# 防止XSS攻击 xss: # 过滤开关 enabled: true # 排除链接多个用逗号分隔 # 即哪些不需要去过滤。 # 因为通知公告的内容是富文本内容可能会包含一些HTML内容。 # 所以一般排除就会排除一些富文本的url。 # " * "表示所有 excludes: /system/notice # 匹配链接 # 即需要过滤的链接/system/*系统管理/monitor/*监控/tool/*工具 urlPatterns: /system/*,/monitor/*,/tool/*
- 重启项目刷新页面
- 面对" 模拟xss攻击示例2 "数据库效果
- 面对" 模拟xss攻击示例2 "页面效果点击不再有弹出框
5 项目处理xss攻击实现原理
- FilterConfig通用的过滤器的配置
/** * Filter配置 * 通用过滤器的配置。 */ @Configuration public class FilterConfig { /** * 获取application.yaml中的xss相关参数配置 */ @Value("${xss.excludes}") private String excludes; /** * 获取application.yaml中的xss相关参数配置 */ @Value("${xss.urlPatterns}") private String urlPatterns; /** * 这里可以添加加很多过滤器现在只有一个xss过滤器 * 后续有其他的过滤器直接往里面加就行了 * 这样方便统一去管理所有的过滤器。 */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); /** * 配置请求类型 */ registration.setDispatcherTypes(DispatcherType.REQUEST); /** * 配置xss核心过滤器 */ registration.setFilter(new XssFilter()); /** * 配置需要过滤的链接 */ registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); /** * 配置xss核心过滤器的名称随意 */ registration.setName("xssFilter"); /** * 配置xss核心过滤器的优先级 */ registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); /** * 配置xss核心过滤器的初始化参数 */ Map<String, String> initParameters = new HashMap<String, String>(); /** * 配置xss核心过滤器的初始化参数 —— 排除的链接不需要过滤的链接 */ initParameters.put("excludes", excludes); registration.setInitParameters(initParameters); return registration; } }
- com.ruoyi.common.filter.XssFilter防止XSS攻击的过滤器
/** * 防止XSS攻击的过滤器 * * @author ruoyi */ public class XssFilter implements Filter { /** * 排除链接 */ public List<String> excludes = new ArrayList<>(); /** * 初化方法 */ @Override public void init(FilterConfig filterConfig) throws ServletException { /** * 排除链接List */ String tempExcludes = filterConfig.getInitParameter("excludes"); if (StringUtils.isNotEmpty(tempExcludes)) { String[] url = tempExcludes.split(","); for (int i = 0; url != null && i < url.length; i++) { excludes.add(url[i]); } } } /** * 核心处理 * @param request * @param response * @param chain * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (handleExcludeURL(req, resp)) { /** * 排除链接正常执行 */ chain.doFilter(request, response); return; } /** * xss攻击的处理 */ XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { /** * 获取请求url */ String url = request.getServletPath(); String method = request.getMethod(); // GET DELETE 不过滤 if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) { return true; } return StringUtils.matches(url, excludes); } @Override public void destroy() { } }
- XssHttpServletRequestWrapperXSS过滤处理
/** * XSS过滤处理 */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { /** * @param request */ public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } /** * 处理非json类型 * @param name * @return */ @Override public String[] getParameterValues(String name) { /** * 获取所有请求参数值 */ String[] values = super.getParameterValues(name); if (values != null) { int length = values.length; String[] escapesValues = new String[length]; for (int i = 0; i < length; i++) { // 防xss攻击和过滤前后空格 escapesValues[i] = EscapeUtil.clean(values[i]).trim(); } return escapesValues; } return super.getParameterValues(name); } /** * 处理json类型 */ @Override public ServletInputStream getInputStream() throws IOException { // 非json类型直接返回 if (!isJsonRequest()) { return super.getInputStream(); } // 为空直接返回 String json = IOUtils.toString(super.getInputStream(), "utf-8"); if (StringUtils.isEmpty(json)) { return super.getInputStream(); } // xss过滤 json = EscapeUtil.clean(json).trim(); byte[] jsonBytes = json.getBytes("utf-8"); final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); return new ServletInputStream() { @Override public boolean isFinished() { return true; } @Override public boolean isReady() { return true; } @Override public int available() throws IOException { return jsonBytes.length; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return bis.read(); } }; } /** * 是否是Json请求 * * @param request */ public boolean isJsonRequest() { String header = super.getHeader(HttpHeaders.CONTENT_TYPE); return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); } }
- EscapeUtil#clean清除所有HTML标签但是不删除标签内的内容
/** * 清除所有HTML标签但是不删除标签内的内容 * * @param content 文本 * @return 清除标签后的文本 */ public static String clean(String content) { return new HTMLFilter().filter(content); }