diff --git a/pom.xml b/pom.xml index b24166e7..df250e8f 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 4.1.2 2.3 0.9.1 + 2.16.0 @@ -150,6 +151,19 @@ ${fastjson.version} + + + org.apache.logging.log4j + log4j-api + ${log4j2.version} + + + + org.apache.logging.log4j + log4j-to-slf4j + ${log4j2.version} + + io.jsonwebtoken diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java index 08cf1514..4aa1d2b2 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -2,9 +2,7 @@ package com.ruoyi.common.core.domain.entity; import java.util.Date; import java.util.List; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; +import javax.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -14,6 +12,7 @@ import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; /** * 用户对象 sys_user @@ -135,6 +134,7 @@ public class SysUser extends BaseEntity this.deptId = deptId; } + @Xss(message = "用户昵称不能包含脚本字符") @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") public String getNickName() { @@ -146,6 +146,7 @@ public class SysUser extends BaseEntity this.nickName = nickName; } + @Xss(message = "用户账号不能包含脚本字符") @NotBlank(message = "用户账号不能为空") @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") public String getUserName() diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java new file mode 100644 index 00000000..d9821e0e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.utils.bean; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; + +/** + * bean对象属性验证 + * + * @author ruoyi + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java new file mode 100644 index 00000000..14e43dc5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.xss; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java new file mode 100644 index 00000000..43163721 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java @@ -0,0 +1,29 @@ +package com.ruoyi.common.xss; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author ruoyi + */ +public class XssValidator implements ConstraintValidator +{ + private final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + return !containsHtml(value); + } + + public boolean containsHtml(String value) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java index 4cde0ec4..663b4a1f 100644 --- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java @@ -65,7 +65,7 @@ public class JobInvokeUtil /** * 校验是否为为class包名 * - * @param str 名称 + * @param invokeTarget 名称 * @return true是 false否 */ public static boolean isValidClassName(String invokeTarget) @@ -110,30 +110,30 @@ public class JobInvokeUtil { return null; } - String[] methodParams = methodStr.split(",(?=(?:[^\']*\"[^\']*\')*[^\']*$)"); + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); List classs = new LinkedList<>(); for (int i = 0; i < methodParams.length; i++) { String str = StringUtils.trimToEmpty(methodParams[i]); - // String字符串类型,包含' - if (StringUtils.contains(str, "'")) + // String字符串类型,以'或"开头 + if (StringUtils.startsWithAny(str, "'", "\"")) { - classs.add(new Object[] { StringUtils.replace(str, "'", ""), String.class }); + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); } // boolean布尔类型,等于true或者false - else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) { classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); } - // long长整形,包含L - else if (StringUtils.containsIgnoreCase(str, "L")) + // long长整形,以L结尾 + else if (StringUtils.endsWith(str, "L")) { - classs.add(new Object[] { Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class }); + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); } - // double浮点类型,包含D - else if (StringUtils.containsIgnoreCase(str, "D")) + // double浮点类型,以D结尾 + else if (StringUtils.endsWith(str, "D")) { - classs.add(new Object[] { Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class }); + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); } // 其他类型归类为整形 else diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java index cb739dc4..b5c6187b 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java @@ -5,6 +5,7 @@ import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; /** * 通知公告表 sys_notice @@ -45,6 +46,7 @@ public class SysNotice extends BaseEntity this.noticeTitle = noticeTitle; } + @Xss(message = "公告标题不能包含脚本字符") @NotBlank(message = "公告标题不能为空") @Size(min = 0, max = 50, message = "公告标题不能超过50个字符") public String getNoticeTitle() diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java index e40c8c85..8d33286f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -3,6 +3,7 @@ package com.ruoyi.system.service.impl; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import javax.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -16,6 +17,7 @@ import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanValidators; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.SysPost; import com.ruoyi.system.domain.SysUserPost; @@ -56,6 +58,9 @@ public class SysUserServiceImpl implements ISysUserService @Autowired private ISysConfigService configService; + @Autowired + protected Validator validator; + /** * 根据条件分页查询用户列表 * @@ -513,6 +518,7 @@ public class SysUserServiceImpl implements ISysUserService SysUser u = userMapper.selectUserByUserName(user.getUserName()); if (StringUtils.isNull(u)) { + BeanValidators.validateWithException(validator, user); user.setPassword(SecurityUtils.encryptPassword(password)); user.setCreateBy(operName); this.insertUser(user); @@ -521,6 +527,7 @@ public class SysUserServiceImpl implements ISysUserService } else if (isUpdateSupport) { + BeanValidators.validateWithException(validator, user); user.setUpdateBy(operName); this.updateUser(user); successNum++; diff --git a/ruoyi-ui/src/plugins/download.js b/ruoyi-ui/src/plugins/download.js index 103e1062..86e20311 100644 --- a/ruoyi-ui/src/plugins/download.js +++ b/ruoyi-ui/src/plugins/download.js @@ -2,6 +2,7 @@ import axios from 'axios' import { Message } from 'element-ui' import { saveAs } from 'file-saver' import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' import { blobValidate } from "@/utils/ruoyi"; const baseURL = process.env.VUE_APP_BASE_API @@ -20,7 +21,7 @@ export default { const blob = new Blob([res.data]) this.saveAs(blob, decodeURI(res.headers['download-filename'])) } else { - Message.error('无效的会话,或者会话已过期,请重新登录。'); + this.printErrMsg(res.data); } }) }, @@ -37,7 +38,7 @@ export default { const blob = new Blob([res.data]) this.saveAs(blob, decodeURI(res.headers['download-filename'])) } else { - Message.error('无效的会话,或者会话已过期,请重新登录。'); + this.printErrMsg(res.data); } }) }, @@ -54,12 +55,18 @@ export default { const blob = new Blob([res.data], { type: 'application/zip' }) this.saveAs(blob, name) } else { - Message.error('无效的会话,或者会话已过期,请重新登录。'); + this.printErrMsg(res.data); } }) }, saveAs(text, name, opts) { saveAs(text, name, opts); + }, + async printErrMsg(data) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); } } diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 1acd9416..8ee43ccb 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -108,7 +108,10 @@ export function download(url, params, filename) { const blob = new Blob([data]) saveAs(blob, filename) } else { - Message.error('无效的会话,或者会话已过期,请重新登录。'); + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); } downloadLoadingInstance.close(); }).catch((r) => {