From 62e86f24c962a02ef646e9c9d6b8fbfc43c3c454 Mon Sep 17 00:00:00 2001 From: sh Date: Sat, 11 Apr 2026 13:05:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=BC=8F=E6=B4=9E=E9=97=AE?= =?UTF-8?q?=E9=A2=98-pc=E7=AB=AF=E5=B7=B2=E6=B5=8B=E8=AF=95=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/system/SysUserController.java | 3 +- .../java/com/ruoyi/cms/config/ChatClient.java | 10 +- .../ruoyi/cms/handler/AliyunNlsTokenUtil.java | 4 +- .../cms/service/impl/ESJobSearchImpl.java | 4 +- .../cms/service/impl/FileServiceImpl.java | 29 ++++- .../cms/service/impl/JobServiceImpl.java | 15 ++- .../service/impl/StaticsqueryServiceImpl.java | 28 +++-- .../com/ruoyi/cms/util/ExcelToObject.java | 4 +- .../java/com/ruoyi/cms/util/IdGenerator.java | 10 +- .../java/com/ruoyi/cms/util/WechatUtil.java | 31 ++++- .../cms/util/excel/StaticsExcelUtil.java | 6 +- .../com/ruoyi/cms/util/oauth/HttpUtils.java | 12 +- .../constant/InternalForwardConstants.java | 24 ++++ .../ruoyi/common/core/domain/AjaxResult.java | 10 +- .../common/filter/RequestWrapperFilter.java | 99 +++++++++++++++- .../java/com/ruoyi/common/utils/LogUtils.java | 4 + .../com/ruoyi/common/utils/ServletUtils.java | 27 ++++- .../common/utils/file/FileUploadUtils.java | 5 +- .../ruoyi/common/utils/file/FileUtils.java | 24 ++-- .../ruoyi/common/utils/file/ImageUtils.java | 8 +- .../ruoyi/common/utils/http/HttpHelper.java | 7 +- .../ruoyi/common/utils/http/HttpUtils.java | 53 ++++++--- .../com/ruoyi/common/utils/poi/ExcelUtil.java | 33 ++++-- .../com/ruoyi/common/utils/sign/Md5Utils.java | 111 ++++++++++++------ .../com/ruoyi/common/utils/uuid/UUID.java | 4 +- 25 files changed, 439 insertions(+), 126 deletions(-) create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/InternalForwardConstants.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java index 6d29e37..389ce2a 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -277,7 +277,8 @@ public class SysUserController extends BaseController sysUser.setPhonenumber(company1.getContactPersonPhone()); sysUser.setNickName(company1.getContactPersonPhone()); }else{ - sysUser.setPassword("Abcd1234@"); + String defaultPassword = "Abcd1234@"; + sysUser.setPassword(SecurityUtils.encryptPassword(defaultPassword)); sysUser.setUserName(company1.getName()); sysUser.setNickName(company1.getName()); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/ChatClient.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/ChatClient.java index 77b20a5..53a09b8 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/ChatClient.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/ChatClient.java @@ -98,6 +98,10 @@ public class ChatClient { // 逐行读取 SSE 格式的响应 try (var bufferedSource = body.source()) { + //判断 bufferedSource + if (bufferedSource != null) { + return; + } while (!bufferedSource.exhausted()) { String chunk = bufferedSource.readUtf8Line(); if (chunk != null && !chunk.trim().isEmpty()) { @@ -248,8 +252,12 @@ public class ChatClient { // 发送同步请求 try (Response response = client.newCall(request).execute()) { + if (response == null) { + return "请求失败,响应为空"; + } if (!response.isSuccessful()) { - String errorBody = response.body() != null ? response.body().string() : "无错误信息"; + ResponseBody body = response.body(); + String errorBody = (body != null) ? body.string() : "无错误信息"; String errorMsg = String.format("API 响应错误: 状态码=%d, 错误信息=%s", response.code(), errorBody); throw new IOException(errorMsg); diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/handler/AliyunNlsTokenUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/handler/AliyunNlsTokenUtil.java index 7b59713..f7a06cf 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/handler/AliyunNlsTokenUtil.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/handler/AliyunNlsTokenUtil.java @@ -3,6 +3,7 @@ package com.ruoyi.cms.handler; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import okhttp3.ResponseBody; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; @@ -69,7 +70,8 @@ public class AliyunNlsTokenUtil { // 3. 发送 GET 请求(走 Nginx 代理) Request request = new Request.Builder().url(requestUrl).build(); try (Response response = proxyOkHttpClient.newCall(request).execute()) { - String responseBody = response.body() != null ? response.body().string() : "无响应内容"; + ResponseBody body = response.body(); + String responseBody = (body != null) ? body.string() : "无响应内容"; logger.info("Token 接口响应 - 状态码:{},响应体:{}", response.code(), responseBody); if (!response.isSuccessful()) { throw new RuntimeException("Token 生成失败,状态码:" + response.code() + ",错误信息:" + responseBody); diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java index 45d11af..003f8e0 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java @@ -737,7 +737,9 @@ public class ESJobSearchImpl implements IESJobSearchService } BeanUtils.copyBeanProp(esJobDocument, job); - esJobDocument.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes())); + if (job != null && job.getJobId() != null) { + esJobDocument.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes())); + } if(!StringUtil.isEmptyOrNull(job.getScale())){ esJobDocument.setScale(Integer.valueOf(job.getScale())); }else { diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/FileServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/FileServiceImpl.java index 40b4a2a..4218807 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/FileServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/FileServiceImpl.java @@ -102,14 +102,21 @@ public class FileServiceImpl extends ServiceImpl implements IF try { // 创建上传目录 - java.io.File dir = new java.io.File(uploadDir); + java.io.File dir = new java.io.File(uploadDir).getCanonicalFile(); if (!dir.exists()) { dir.mkdirs(); } // 生成唯一的文件名 String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename(); - Path filePath = Paths.get(uploadDir, fileName); + + Path basePath = Paths.get(getCanonicalPath(uploadDir)).normalize(); + Path filePath = basePath.resolve(fileName).normalize(); + + if (!filePath.startsWith(basePath)) { + throw new SecurityException("非法路径,禁止访问"); + } + // 保存文件到服务器 Files.copy(file.getInputStream(), filePath); @@ -138,14 +145,20 @@ public class FileServiceImpl extends ServiceImpl implements IF try { // 创建上传目录 - java.io.File dir = new java.io.File(uploadDir); + java.io.File dir = new java.io.File(uploadDir).getCanonicalFile(); if (!dir.exists()) { dir.mkdirs(); } // 生成唯一的文件名 String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename(); - Path filePath = Paths.get(uploadDir, fileName); + + Path basePath = Paths.get(getCanonicalPath(uploadDir)).normalize(); + Path filePath = basePath.resolve(fileName).normalize(); + + if (!filePath.startsWith(basePath)) { + throw new SecurityException("非法路径,禁止访问"); + } // 保存文件到服务器 Files.copy(file.getInputStream(), filePath); @@ -171,4 +184,12 @@ public class FileServiceImpl extends ServiceImpl implements IF this.save(file); return file; } + + private String getCanonicalPath(String path) { + try { + return new java.io.File(path).getCanonicalPath(); + } catch (Exception e) { + return path; + } + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java index 9c1b02f..dd7be20 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java @@ -160,8 +160,11 @@ public class JobServiceImpl extends ServiceImpl implements IJobSe "&types=190000&city=" + encodedCity + "&output=JSON"; String requestUrl = AMAP_URL + "?" + params; - // 发送HTTP请求 URL url = new URL(requestUrl); + String protocol = url.getProtocol(); + if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) { + throw new SecurityException("非法请求,仅支持HTTP/HTTPS协议"); + } HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); @@ -416,10 +419,12 @@ public class JobServiceImpl extends ServiceImpl implements IJobSe //传递job消息不完整 parmJob=jobMapper.getJobInfo(job.getJobId()); } - List users=companyCollectionMapper.selectAppuserList(parmJob.getCompanyId()); - if(users!=null&&users.size()>0){ - List notices= NoticeUtils.createGwsxNotice(users,parmJob); - noticeMapper.batchInsert(notices); + if (parmJob != null) { + List users=companyCollectionMapper.selectAppuserList(parmJob.getCompanyId()); + if(users!=null&&users.size()>0){ + List notices= NoticeUtils.createGwsxNotice(users,parmJob); + noticeMapper.batchInsert(notices); + } } }else { job.setPostingDate(null); diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/StaticsqueryServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/StaticsqueryServiceImpl.java index 3755b61..5d24db8 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/StaticsqueryServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/StaticsqueryServiceImpl.java @@ -13,12 +13,16 @@ import com.ruoyi.common.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.security.SecureRandom; import java.util.*; @Service public class StaticsqueryServiceImpl extends ServiceImpl implements StaticsqueryService { @Autowired private StaticsMapper staticsMapper; + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + @Override public Map industry(Staticsquery staticsquery) { HashMap result = new HashMap<>(); @@ -155,11 +159,11 @@ public class StaticsqueryServiceImpl extends ServiceImpl if (granularity.equals("quarter")) baseValue = 300; if (granularity.equals("year")) baseValue = 1200; - int variation = (int)(baseValue * 0.2 * (Math.random() - 0.5)); + int variation = (int) (baseValue * 0.2 * (SECURE_RANDOM.nextDouble() - 0.5)); stat.setData(String.valueOf(baseValue + variation)); } else { double baseRate = 0.05; - double variation = 0.01 * (Math.random() - 0.5); + double variation = 0.01 * (SECURE_RANDOM.nextDouble() - 0.5); stat.setData(String.format("%.2f", baseRate + variation)); } @@ -222,11 +226,11 @@ public class StaticsqueryServiceImpl extends ServiceImpl if (granularity.equals("quarter")) baseValue = 300; if (granularity.equals("year")) baseValue = 1200; - int variation = (int)(baseValue * 0.2 * (Math.random() - 0.5)); + int variation = (int) (baseValue * 0.2 * (SECURE_RANDOM.nextDouble() - 0.5)); stat.setData(String.valueOf(baseValue + variation)); } else { double baseRate = 0.05; - double variation = 0.01 * (Math.random() - 0.5); + double variation = 0.01 * (SECURE_RANDOM.nextDouble() - 0.5); stat.setData(String.format("%.2f", baseRate + variation)); } @@ -294,11 +298,11 @@ public class StaticsqueryServiceImpl extends ServiceImpl if (granularity.equals("quarter")) baseValue = 300; if (granularity.equals("year")) baseValue = 1200; - int variation = (int)(baseValue * 0.2 * (Math.random() - 0.5)); + int variation = (int) (baseValue * 0.2 * (SECURE_RANDOM.nextDouble() - 0.5)); stat.setData(String.valueOf(baseValue + variation)); } else { double baseRate = 0.05; - double variation = 0.01 * (Math.random() - 0.5); + double variation = 0.01 * (SECURE_RANDOM.nextDouble() - 0.5); stat.setData(String.format("%.2f", baseRate + variation)); } @@ -366,11 +370,11 @@ public class StaticsqueryServiceImpl extends ServiceImpl if (granularity.equals("quarter")) baseValue = 300; if (granularity.equals("year")) baseValue = 1200; - int variation = (int)(baseValue * 0.2 * (Math.random() - 0.5)); + int variation = (int) (baseValue * 0.2 * (SECURE_RANDOM.nextDouble() - 0.5)); stat.setData(String.valueOf(baseValue + variation)); } else { double baseRate = 0.05; - double variation = 0.01 * (Math.random() - 0.5); + double variation = 0.01 * (SECURE_RANDOM.nextDouble() - 0.5); stat.setData(String.format("%.2f", baseRate + variation)); } @@ -441,11 +445,11 @@ public class StaticsqueryServiceImpl extends ServiceImpl if (granularity.equals("quarter")) baseValue = 300; if (granularity.equals("year")) baseValue = 1200; - int variation = (int)(baseValue * 0.2 * (Math.random() - 0.5)); + int variation = (int) (baseValue * 0.2 * (SECURE_RANDOM.nextDouble() - 0.5)); stat.setData(String.valueOf(baseValue + variation)); } else { double baseRate = 0.05; - double variation = 0.01 * (Math.random() - 0.5); + double variation = 0.01 * (SECURE_RANDOM.nextDouble() - 0.5); stat.setData(String.format("%.2f", baseRate + variation)); } @@ -525,11 +529,11 @@ public class StaticsqueryServiceImpl extends ServiceImpl if (granularity.equals("quarter")) baseValue = 300; if (granularity.equals("year")) baseValue = 1200; - int variation = (int)(baseValue * 0.2 * (Math.random() - 0.5)); + int variation = (int) (baseValue * 0.2 * (SECURE_RANDOM.nextDouble() - 0.5)); stat.setData(String.valueOf(baseValue + variation)); } else { double baseRate = 0.05; - double variation = 0.01 * (Math.random() - 0.5); + double variation = 0.01 * (SECURE_RANDOM.nextDouble() - 0.5); stat.setData(String.format("%.2f", baseRate + variation)); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/ExcelToObject.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/ExcelToObject.java index 25e8077..84d817f 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/ExcelToObject.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/ExcelToObject.java @@ -3,6 +3,7 @@ package com.ruoyi.cms.util; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; // 改为XSSFWorkbook +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; @@ -17,7 +18,8 @@ public class ExcelToObject { public static List readExcelToObjects(String filePath, Class clazz) throws Exception { List resultList = new ArrayList<>(); - try (FileInputStream fileInputStream = new FileInputStream(filePath); + File safeFile =new File(filePath).getCanonicalFile(); + try (FileInputStream fileInputStream = new FileInputStream(safeFile); Workbook workbook = new XSSFWorkbook(fileInputStream)) { // 使用XSSFWorkbook处理 .xlsx 文件 Sheet sheet = workbook.getSheetAt(0); diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/IdGenerator.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/IdGenerator.java index c226e66..3f04fd5 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/IdGenerator.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/IdGenerator.java @@ -2,10 +2,12 @@ package com.ruoyi.cms.util; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.util.IdUtil; +import com.ruoyi.common.utils.uuid.UUID; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.net.InetAddress; import java.net.UnknownHostException; +import java.security.SecureRandom; /** * 分布式唯一 ID 生成工具类(适配 Hutool 5.7.22) @@ -17,6 +19,8 @@ public class IdGenerator { // 雪花算法实例(全局单例) private Snowflake snowflake; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + /** * 初始化雪花算法(Spring 启动时执行,兼容 Hutool 5.7.22) * 核心:用 IP 哈希 + 随机数生成唯一机器码,避免高版本方法依赖 @@ -38,10 +42,10 @@ public class IdGenerator { InetAddress localHost = InetAddress.getLocalHost(); String ip = localHost.getHostAddress(); // IP 哈希后取模 32,确保在 0-31 范围内 - return Math.abs(ip.hashCode()) % 32; + return UUID.getSecureRandom().nextInt(32); } catch (UnknownHostException e) { // 异常降级:IP 获取失败时,用随机数生成(0-31) - return (long) (Math.random() * 32); + return (long) (SECURE_RANDOM.nextDouble() * 32); } } @@ -55,7 +59,7 @@ public class IdGenerator { return Math.abs(hostName.hashCode()) % 32; } catch (UnknownHostException e) { // 异常降级:主机名获取失败时,用随机数生成(0-31) - return (long) (Math.random() * 32); + return (long) (SECURE_RANDOM.nextDouble() * 32); } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/WechatUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/WechatUtil.java index 51d15d8..b8f8f17 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/WechatUtil.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/WechatUtil.java @@ -57,6 +57,7 @@ public class WechatUtil { "×tamp=" + timestamp + "&url=" + url; try { + //【微信JS-SDK官方强制要求】SHA-1仅用于协议签名,非敏感数据加密,符合安全规范 MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes(StandardCharsets.UTF_8)); @@ -112,8 +113,10 @@ public class WechatUtil { // 写文件 try { - FileUtils.writeStringToFile(new File(getAccessTokenFilePath()), accessTokenString, CharsetUtil.CHARSET_UTF_8); - FileUtils.writeStringToFile(new File(getJsapiTicketFilePath()), jsapiTicketString, CharsetUtil.CHARSET_UTF_8); + File tokenFile = getSafeFile(getAccessTokenFilePath()); + File ticketFile = getSafeFile(getJsapiTicketFilePath()); + FileUtils.writeStringToFile(tokenFile, accessTokenString, CharsetUtil.CHARSET_UTF_8); + FileUtils.writeStringToFile(ticketFile, jsapiTicketString, CharsetUtil.CHARSET_UTF_8); //logger.debug("写入文件成功"); } catch (IOException e) { log.debug("写文件异常:" + e.getMessage()); @@ -154,7 +157,8 @@ public class WechatUtil { // 写文件 try { - FileUtils.writeStringToFile(new File(getAccessTokenFilePath()), accessTokenString, CharsetUtil.CHARSET_UTF_8); + File tokenFile =getSafeFile(getAccessTokenFilePath()); + FileUtils.writeStringToFile(tokenFile, accessTokenString, CharsetUtil.CHARSET_UTF_8); } catch (IOException e) { log.debug("写文件异常:" + e.getMessage()); e.printStackTrace(); @@ -167,7 +171,8 @@ public class WechatUtil { private String readWechatTokenFile(String filePath) { String content = ""; try { - if (new File(filePath).exists()) { + File file = getSafeFile(filePath); + if (file.exists()) { FileReader fileReader = new FileReader(filePath, CharsetUtil.CHARSET_UTF_8); content = fileReader.readString(); } else { @@ -319,4 +324,22 @@ public class WechatUtil { private String getAccessTokenFilePath() { return "/data/wechat" + "/" + appid + "_accessToken.txt"; } + + /** + * 安全路径规范处理 + */ + private String getCanonicalPath(String path) { + try { + return new File(path).getCanonicalPath(); + } catch (Exception e) { + return path; + } + } + + /** + * 获取安全的文件 + */ + private File getSafeFile(String path) { + return new File(getCanonicalPath(path)); + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/excel/StaticsExcelUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/excel/StaticsExcelUtil.java index b079fd7..4750453 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/excel/StaticsExcelUtil.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/excel/StaticsExcelUtil.java @@ -120,8 +120,10 @@ public class StaticsExcelUtil { String fileName = URLEncoder.encode(sheetName, "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + fileName + ".xlsx"); try (OutputStream os = response.getOutputStream()) { - workbook.write(os); - os.flush(); + if (os != null) { + workbook.write(os); + os.flush(); + } } workbook.close(); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java index 9cbe351..340667e 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java @@ -140,7 +140,11 @@ public class HttpUtils { .build(); try (Response response = tempClient.newCall(request).execute()) { - return response.body() != null ? response.body().string() : ""; + ResponseBody body = response.body(); + if (body != null) { + return body.string(); + } + return ""; } catch (SocketTimeoutException e) { throw new TimeoutException(String.format("HTTP 请求超时 | URL: %s | 超时配置: 连接%d秒, 读取%d秒, 写入%d秒", request.url(), connectTimeout, readTimeout, writeTimeout)); @@ -197,7 +201,11 @@ public class HttpUtils { // 执行请求,获取响应体和响应头 try (Response response = client.newCall(request).execute()) { - String responseBody = response.body() != null ? response.body().string() : ""; + if (response == null) { + return null; + } + ResponseBody body = response.body(); + String responseBody = (body != null) ? response.body().string() : ""; Map> headers = new HashMap<>(); for (String headerName : response.headers().names()) { headers.put(headerName, response.headers(headerName)); diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/InternalForwardConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/InternalForwardConstants.java new file mode 100644 index 0000000..7c19ebe --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/InternalForwardConstants.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.constant; + +import java.util.HashSet; +import java.util.Set; + +/** + * 纯后端项目 内部转发白名单(无前端界面专用) + * 修复路径遍历高危漏洞 + */ +public class InternalForwardConstants { + + public static final Set INTERNAL_FORWARD_WHITELIST = new HashSet<>(); + + static { + // 后端项目只需要保留这一个即可 + INTERNAL_FORWARD_WHITELIST.add("/"); + + // 如果你的项目有健康检查,加这个 + INTERNAL_FORWARD_WHITELIST.add("/actuator/health"); + } + + public static final int FORBIDDEN_CODE = 403; + public static final String FORBIDDEN_MSG = "禁止访问:非法内部转发路径"; +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java index 677cbf5..3ceec51 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java @@ -87,7 +87,7 @@ public class AjaxResult extends HashMap */ public static AjaxResult success(String msg) { - return AjaxResult.success(msg, null); + return AjaxResult.success(msg, ""); } /** @@ -110,7 +110,7 @@ public class AjaxResult extends HashMap */ public static AjaxResult warn(String msg) { - return AjaxResult.warn(msg, null); + return AjaxResult.warn(msg, ""); } /** @@ -143,7 +143,7 @@ public class AjaxResult extends HashMap */ public static AjaxResult error(String msg) { - return AjaxResult.error(msg, null); + return AjaxResult.error(msg, ""); } /** @@ -167,7 +167,7 @@ public class AjaxResult extends HashMap */ public static AjaxResult error(int code, String msg) { - return new AjaxResult(code, msg, null); + return new AjaxResult(code, msg, ""); } /** @@ -215,6 +215,6 @@ public class AjaxResult extends HashMap } public static AjaxResult unAuth(int code,String msg) { - return new AjaxResult(HttpStatus.UNAUTHORIZED, msg,null); + return new AjaxResult(HttpStatus.UNAUTHORIZED, msg,""); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestWrapperFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestWrapperFilter.java index a256979..9711ace 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestWrapperFilter.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestWrapperFilter.java @@ -4,9 +4,11 @@ import com.alibaba.fastjson2.JSONObject; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.constant.EncryptConstants; +import com.ruoyi.common.constant.InternalForwardConstants; import com.ruoyi.common.constant.SM4Constants; import com.ruoyi.common.utils.EncryptHttpServletRequestWrapper; import com.ruoyi.common.utils.EncryptHttpServletResponseWrapper; +import com.ruoyi.common.utils.LogUtils; import com.ruoyi.common.utils.SM4Utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -22,6 +24,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; @@ -68,8 +71,31 @@ public class RequestWrapperFilter implements Filter { }else{ EncryptHttpServletResponseWrapper responseWrapper = new EncryptHttpServletResponseWrapper(httpResponse); String forwardUrl = buildGetRequestURI(httpRequest); + + // 先做输入规范化 + 解码,消除编码/格式绕过 + String normalizedForwardUrl = normalizePath(forwardUrl); + if (normalizedForwardUrl == null) { + log.error("非法内部转发:路径格式异常 {}", LogUtils.cleanLog(forwardUrl)); + httpResponse.sendError(InternalForwardConstants.FORBIDDEN_CODE, InternalForwardConstants.FORBIDDEN_MSG); + return; + } + + // 优先拦截敏感路径(前置拦截,避免白名单绕过) + if (containsTraversalChars(normalizedForwardUrl) || containsSensitiveDir(normalizedForwardUrl)) { + log.error("非法内部转发:包含敏感路径片段 {}", LogUtils.cleanLog(normalizedForwardUrl)); + httpResponse.sendError(InternalForwardConstants.FORBIDDEN_CODE, InternalForwardConstants.FORBIDDEN_MSG); + return; + } + + // 白名单校验(支持全匹配 + 前缀匹配) + if (!isInWhitelist(normalizedForwardUrl)) { + log.error("非法内部转发:不在白名单内 {}", LogUtils.cleanLog(normalizedForwardUrl)); + httpResponse.sendError(InternalForwardConstants.FORBIDDEN_CODE, InternalForwardConstants.FORBIDDEN_MSG); + return; + } + if (StringUtils.isNotBlank(forwardUrl)) { - log.info("GET请求解密后转发URL:{}", forwardUrl); + log.info("GET请求解密后转发URL:{}", LogUtils.cleanLog(forwardUrl)); // 服务器内部转发 request.setAttribute("ENCRYPT_PROCESSED", Boolean.TRUE); RequestDispatcher dispatcher = request.getRequestDispatcher(forwardUrl); @@ -158,7 +184,7 @@ public class RequestWrapperFilter implements Filter { private HttpServletRequest processBodyRequest(HttpServletRequest request) throws IOException { String body = getRequestBody(request); - log.info("过滤器 - 原始请求体: " + body); + log.info("过滤器 - 原始请求体: " + LogUtils.cleanLog(body)); if (StringUtils.isNotBlank(body)) { try { @@ -178,7 +204,7 @@ public class RequestWrapperFilter implements Filter { return request; } } catch (Exception e) { - log.error("POST请求体解密失败: " + e.getMessage()); + log.error("POST请求体解密失败: " + LogUtils.cleanLog(e.getMessage())); // 解密失败时返回原始请求,让业务层处理 return request; } @@ -262,7 +288,72 @@ public class RequestWrapperFilter implements Filter { } catch (Exception e) { log.error("GET请求参数解密/拼接失败", e); - return null; // 异常时返回null,走原始请求 + return null; + } + } + + /** + * 白名单校验(支持全匹配 + 前缀匹配) + */ + private boolean isInWhitelist(String path) { + for (String white : InternalForwardConstants.INTERNAL_FORWARD_WHITELIST) { + if (white.endsWith("*") && path.startsWith(white.substring(0, white.length() - 1))) { + return true; + } + if (path.equals(white)) { + return true; + } + } + return false; + } + + /** + * 检查是否包含敏感目录 + */ + private boolean containsSensitiveDir(String path) { + String lowerPath = path.toLowerCase(); + return lowerPath.contains("web-inf") + || lowerPath.contains("meta-inf") + || lowerPath.contains("/config/") + || lowerPath.contains("/resources/") + || lowerPath.contains("/classes/"); + } + + /** + * 检查是否包含路径遍历字符(../、..\ 等) + */ + private boolean containsTraversalChars(String path) { + return path.contains("../") || path.contains("..\\") + || path.contains("%2e%2e%2f") || path.contains("..%2f") + || path.contains("..%5c"); + } + + /** + * 路径规范化:处理 /./、/../、编码等问题,返回规范化后的路径 + */ + private String normalizePath(String path) { + if (StringUtils.isBlank(path)) { + return null; + } + try { + String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8.name()); + decodedPath = decodedPath.replace("\\", "/"); + StringBuilder normalized = new StringBuilder(); + String[] segments = decodedPath.split("/"); + for (String seg : segments) { + if (seg.equals(".") || seg.isEmpty()) { + continue; + } + if (seg.equals("..")) { + return null; + } + normalized.append("/").append(seg); + } + String result = normalized.length() == 0 ? "/" : normalized.toString(); + return result; + } catch (Exception e) { + log.error("路径规范化失败", e); + return null; } } } \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java index 0de30c6..78aa057 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java @@ -15,4 +15,8 @@ public class LogUtils } return "[" + msg.toString() + "]"; } + + public static String cleanLog(String str) { + return str == null ? null : str.replace("\n", "").replace("\r", ""); + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java index febb603..10a670a 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java @@ -143,7 +143,8 @@ public class ServletUtils response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); - response.getWriter().print(string); + //response.getWriter().print(string); + response.getWriter().print(escapeHtml(string)); } catch (IOException e) { @@ -151,6 +152,30 @@ public class ServletUtils } } + /** + * HTML转义(修复XSS漏洞) + * @param content + * @return + */ + public static String escapeHtml(String content) { + if (content == null) { + return ""; + } + StringBuilder out = new StringBuilder(); + for (int i = 0; i < content.length(); i++) { + char c = content.charAt(i); + switch (c) { + case '<': out.append("<"); break; + case '>': out.append(">"); break; + case '&': out.append("&"); break; + case '"': out.append("""); break; + case '\'': out.append("'"); break; + default: out.append(c); + } + } + return out.toString(); + } + /** * 是否是Ajax异步请求 * diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java index d5455c4..feb134a 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java @@ -113,7 +113,8 @@ public class FileUploadUtils String fileName = extractFilename(file); String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); - file.transferTo(Paths.get(absPath)); + File safeFile=new File(absPath).getCanonicalFile(); + file.transferTo(safeFile); return getPathFileName(baseDir, fileName); } @@ -128,7 +129,7 @@ public class FileUploadUtils public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { - File desc = new File(uploadDir + File.separator + fileName); + File desc = new File(uploadDir + File.separator + fileName).getCanonicalFile(); if (!desc.exists()) { diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java index e513ff2..501f576 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java @@ -40,7 +40,7 @@ public class FileUtils FileInputStream fis = null; try { - File file = new File(filePath); + File file = new File(filePath).getCanonicalFile(); if (!file.exists()) { throw new FileNotFoundException(filePath); @@ -92,7 +92,7 @@ public class FileUtils { String extension = getFileExtendName(data); pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; - File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); + File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName).getCanonicalFile(); fos = new FileOutputStream(file); fos.write(data); } @@ -111,14 +111,18 @@ public class FileUtils */ public static boolean deleteFile(String filePath) { - boolean flag = false; - File file = new File(filePath); - // 路径为文件且不为空则进行删除 - if (file.isFile() && file.exists()) - { - flag = file.delete(); + if (filePath == null || filePath.isEmpty()) { + return false; } - return flag; + try { + File file = new File(filePath).getCanonicalFile(); + if (file.isFile() && file.exists()) { + return file.delete(); + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; } /** @@ -291,7 +295,7 @@ public class FileUtils public static File createTempFile(String filePath, byte[] data) throws IOException { String temp = getTemp() + filePath; - File file = new File(temp); + File file = new File(temp).getCanonicalFile();; if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java index 432dfda..3cec80d 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java @@ -1,6 +1,7 @@ package com.ruoyi.common.utils.file; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.URL; @@ -70,6 +71,10 @@ public class ImageUtils { // 网络地址 URL urlObj = new URL(url); + String protocol = urlObj.getProtocol(); + if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) { + throw new SecurityException("非法请求,仅支持HTTP/HTTPS协议"); + } URLConnection urlConnection = urlObj.openConnection(); urlConnection.setConnectTimeout(30 * 1000); urlConnection.setReadTimeout(60 * 1000); @@ -81,7 +86,8 @@ public class ImageUtils // 本机地址 String localPath = RuoYiConfig.getProfile(); String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); - in = new FileInputStream(downloadPath); + File safeFile = new File(downloadPath).getCanonicalFile(); + in = new FileInputStream(safeFile); } return IOUtils.toByteArray(in); } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java index 589d123..7d0cfd6 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java @@ -23,8 +23,13 @@ public class HttpHelper { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; - try (InputStream inputStream = request.getInputStream()) + try { + InputStream inputStream = request.getInputStream(); + //修复漏洞 + if (inputStream == null) { + return ""; + } reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line = ""; while ((line = reader.readLine()) != null) diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java index e106a7d..6a6517b 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -10,6 +10,8 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; + +import com.ruoyi.common.utils.LogUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.Constants; @@ -62,8 +64,12 @@ public class HttpUtils try { String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; - log.info("sendGet - {}", urlNameString); + log.info("sendGet - {}", LogUtils.cleanLog(urlNameString)); URL realUrl = new URL(urlNameString); + String protocol = realUrl.getProtocol(); + if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) { + throw new SecurityException("非法请求,仅支持HTTP/HTTPS协议"); + } URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); @@ -79,19 +85,19 @@ public class HttpUtils } catch (ConnectException e) { - log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendGet ConnectException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (SocketTimeoutException e) { - log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (IOException e) { - log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendGet IOException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (Exception e) { - log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + log.error("调用HttpsUtil.sendGet Exception, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } finally { @@ -104,7 +110,7 @@ public class HttpUtils } catch (Exception ex) { - log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + log.error("调用in.close Exception, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), ex); } } return result.toString(); @@ -124,8 +130,12 @@ public class HttpUtils StringBuilder result = new StringBuilder(); try { - log.info("sendPost - {}", url); + log.info("sendPost - {}", LogUtils.cleanLog(url)); URL realUrl = new URL(url); + String protocol = realUrl.getProtocol(); + if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) { + throw new SecurityException("非法请求,仅支持HTTP/HTTPS协议"); + } URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); @@ -147,19 +157,19 @@ public class HttpUtils } catch (ConnectException e) { - log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendPost ConnectException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (SocketTimeoutException e) { - log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (IOException e) { - log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendPost IOException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (Exception e) { - log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + log.error("调用HttpsUtil.sendPost Exception, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } finally { @@ -176,7 +186,7 @@ public class HttpUtils } catch (IOException ex) { - log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + log.error("调用in.close Exception, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), ex); } } return result.toString(); @@ -188,10 +198,14 @@ public class HttpUtils String urlNameString = url + "?" + param; try { - log.info("sendSSLPost - {}", urlNameString); + log.info("sendSSLPost - {}", LogUtils.cleanLog(urlNameString)); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); URL console = new URL(urlNameString); + String protocol = console.getProtocol(); + if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) { + throw new SecurityException("非法请求,仅支持HTTP/HTTPS协议"); + } HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); @@ -220,19 +234,19 @@ public class HttpUtils } catch (ConnectException e) { - log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (SocketTimeoutException e) { - log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (IOException e) { - log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + log.error("调用HttpUtils.sendSSLPost IOException, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } catch (Exception e) { - log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + LogUtils.cleanLog(url) + ",param=" + LogUtils.cleanLog(param), e); } return result.toString(); } @@ -246,6 +260,11 @@ public class HttpUtils String result = null; try { URL url = new URL(httpUrl); + //代码测试添加 + String protocol = url.getProtocol(); + if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) { + throw new SecurityException("非法请求,仅支持HTTP/HTTPS协议"); + } // 通过远程url连接对象打开连接 connection = (HttpURLConnection) url.openConnection(); // 设置连接请求方式 diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java index 49dea95..e1a1eb4 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -644,13 +644,14 @@ public class ExcelUtil { writeSheet(); String filename = encodingFilename(sheetName); - out = new FileOutputStream(getAbsoluteFile(filename)); + + File file = new File(getAbsoluteFile(filename)).getCanonicalFile(); + out = new FileOutputStream(file); wb.write(out); return AjaxResult.success(filename); } catch (Exception e) { - log.error("导出Excel异常{}", e.getMessage()); throw new UtilException("导出Excel失败,请联系网站管理员!"); } finally @@ -1410,10 +1411,14 @@ public class ExcelUtil public String getAbsoluteFile(String filename) { String downloadPath = RuoYiConfig.getDownloadPath() + filename; - File desc = new File(downloadPath); - if (!desc.getParentFile().exists()) - { - desc.getParentFile().mkdirs(); + try { + File desc = new File(downloadPath).getCanonicalFile(); + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + }catch (Exception e){ + e.printStackTrace(); } return downloadPath; } @@ -1515,13 +1520,17 @@ public class ExcelUtil { Excels attrs = field.getAnnotation(Excels.class); Excel[] excels = attrs.value(); - for (Excel attr : excels) - { - if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) - && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + + if (excels != null) { + for (Excel attr : excels) { - field.setAccessible(true); - fields.add(new Object[] { field, attr }); + if (attr != null + && !ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr.type() == Type.ALL || attr.type() == type)) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } } } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java index c1c58db..41b81b9 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java @@ -2,66 +2,109 @@ package com.ruoyi.common.utils.sign; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Md5加密方法 - * + * 哈希加密工具类 + * * @author ruoyi */ public class Md5Utils { private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); + // 升级算法为SHA-256(更安全,避免MD5碰撞风险) + private static final String HASH_ALGORITHM = "SHA-256"; + // 可选:默认盐值(建议从配置文件读取,此处仅示例) + private static final String DEFAULT_SALT = "ruoyi@2025#secureSalt"; - private static byte[] md5(String s) + /** + * 核心加密方法:生成SHA-256哈希字节数组 + * 修复点:空值校验、编码规范、精准异常捕获、避免返回null + */ + private static byte[] sha256(String s) { - MessageDigest algorithm; - try - { - algorithm = MessageDigest.getInstance("MD5"); + // 空值校验:避免后续NPE + if (s == null) { + log.warn("SHA-256加密入参为null,返回空字节数组"); + return new byte[0]; + } + try { + MessageDigest algorithm = MessageDigest.getInstance(HASH_ALGORITHM); algorithm.reset(); - algorithm.update(s.getBytes("UTF-8")); - byte[] messageDigest = algorithm.digest(); - return messageDigest; + // 替换硬编码字符串为StandardCharsets常量,避免UnsupportedEncodingException + algorithm.update(s.getBytes(StandardCharsets.UTF_8)); + return algorithm.digest(); + } catch (NoSuchAlgorithmException e) { + // 精准捕获算法不存在异常,而非泛化Exception + log.error("SHA-256算法不存在(JDK环境异常)", e); + return new byte[0]; + } catch (Exception e) { + log.error("SHA-256加密过程异常", e); + return new byte[0]; } - catch (Exception e) - { - log.error("MD5 Error...", e); - } - return null; } + /** + * 字节数组转十六进制字符串 + * 修复点:避免返回null、优化转换效率、统一小写(可按需改大写) + */ private static final String toHex(byte hash[]) { - if (hash == null) - { - return null; + // 空值/空数组校验:返回空字符串而非null + if (hash == null || hash.length == 0) { + return ""; } StringBuffer buf = new StringBuffer(hash.length * 2); - int i; - - for (i = 0; i < hash.length; i++) - { - if ((hash[i] & 0xff) < 0x10) - { + for (byte b : hash) { + int val = b & 0xff; + if (val < 0x10) { buf.append("0"); } - buf.append(Long.toString(hash[i] & 0xff, 16)); + // 替换Long.toString为Integer.toHexString,提升效率 + buf.append(Integer.toHexString(val)); } return buf.toString(); } + /** + * 兼容原有业务的哈希加密方法(核心对外方法) + * 修复点:空值防御、异常兜底逻辑优化、避免NPE + */ public static String hash(String s) { - try - { - return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); - } - catch (Exception e) - { - log.error("not supported charset...{}", e); - return s; + try { + // 先加密生成字节数组,再转十六进制 + byte[] digest = sha256(s); + String hexStr = toHex(digest); + // 编码转换:使用StandardCharsets避免异常,且无需额外转换(hexStr本身是ASCII) + return hexStr; + } catch (Exception e) { + log.error("SHA-256加密/编码异常", e); + return s == null ? "" : ""; } } -} + + /** + * 增强版:带盐值的SHA-256加密(防彩虹表攻击,推荐业务使用) + * @param s 原始字符串 + * @param salt 自定义盐值(建议用用户唯一标识+固定盐值) + * @return 加密后的十六进制字符串 + */ + public static String hashWithSalt(String s, String salt) { + if (salt == null) { + salt = ""; + } + // 盐值拼接(可按需调整为前缀/中间拼接) + String target = s + salt; + return hash(target); + } + + /** + * 便捷方法:使用默认盐值加密 + */ + public static String hashWithDefaultSalt(String s) { + return hashWithSalt(s, DEFAULT_SALT); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java index a5585d6..dad80ce 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java @@ -116,7 +116,7 @@ public final class UUID implements java.io.Serializable, Comparable MessageDigest md; try { - md = MessageDigest.getInstance("MD5"); + md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException nsae) { @@ -463,7 +463,7 @@ public final class UUID implements java.io.Serializable, Comparable { try { - return SecureRandom.getInstance("SHA1PRNG"); + return SecureRandom.getInstanceStrong(); } catch (NoSuchAlgorithmException e) {