修复漏洞问题
This commit is contained in:
@@ -2,16 +2,23 @@ package com.ruoyi.web.controller.system;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.ruoyi.cms.util.StringUtil;
|
||||
import com.ruoyi.common.core.domain.entity.AppUser;
|
||||
import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwTokenResult;
|
||||
import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwUserLogin;
|
||||
import com.ruoyi.common.core.domain.model.RegisterBody;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.SiteSecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.ip.IpUtils;
|
||||
import com.ruoyi.framework.web.service.OauthLoginHlwService;
|
||||
import com.ruoyi.framework.web.service.OauthLoginService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
@@ -22,8 +29,11 @@ import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.framework.web.service.SysLoginService;
|
||||
import com.ruoyi.framework.web.service.SysPermissionService;
|
||||
import com.ruoyi.system.service.ISysMenuService;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
|
||||
/**
|
||||
@@ -46,6 +56,8 @@ public class SysLoginController
|
||||
private OauthLoginService oauthLoginService;
|
||||
@Autowired
|
||||
private OauthLoginHlwService oauthLoginHlwService;
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
@@ -229,12 +241,43 @@ public class SysLoginController
|
||||
*/
|
||||
@ApiOperation("保存注册信息")
|
||||
@PostMapping("/registerUser")
|
||||
public AjaxResult registerUser(@RequestBody RegisterBody registerBody)
|
||||
public AjaxResult registerUser(@RequestBody RegisterBody registerBody,HttpServletRequest request)
|
||||
{
|
||||
//限流
|
||||
if (!checkLimit(request)) {
|
||||
return AjaxResult.error("请求过于频繁,请稍后再试");
|
||||
}
|
||||
|
||||
String token=loginService.registerAppUser(registerBody);
|
||||
return AjaxResult.success().put("token",token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流
|
||||
* @return
|
||||
*/
|
||||
public boolean checkLimit(HttpServletRequest request) {
|
||||
String ip = IpUtils.getIpAddr(request);;
|
||||
Long userId = SiteSecurityUtils.getUserId();
|
||||
|
||||
//用户限流
|
||||
String userKey = "limit:registerUser:uid:" + userId;
|
||||
Long userCnt = redisCache.increment(userKey);
|
||||
if (userCnt == null || userCnt > 1) {
|
||||
return false;
|
||||
}
|
||||
redisCache.expire(userKey, 60, TimeUnit.SECONDS);
|
||||
// IP 限流
|
||||
String ipKey = "limit:registerUser:ip:" + ip;
|
||||
Long ipCnt = redisCache.increment(ipKey);
|
||||
if (ipCnt == null || ipCnt > 3) {
|
||||
return false;
|
||||
}
|
||||
redisCache.expire(ipKey, 60, TimeUnit.SECONDS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统一门户token
|
||||
*/
|
||||
@@ -285,4 +328,26 @@ public class SysLoginController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public AjaxResult logout() {
|
||||
try {
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
HttpSession session = attributes.getRequest().getSession(false);
|
||||
if (session != null) {
|
||||
session.invalidate(); // 服务器端直接销毁
|
||||
}
|
||||
}
|
||||
return AjaxResult.success("退出成功,登录已失效");
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("退出失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.ruoyi.cms.controller.app;
|
||||
|
||||
import com.ruoyi.cms.util.IdGenerator;
|
||||
import com.ruoyi.cms.util.ProxyServerUtil;
|
||||
import com.ruoyi.cms.util.file.FileValid;
|
||||
import com.ruoyi.common.core.domain.entity.File;
|
||||
import com.ruoyi.cms.service.IFileService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.ServletUtils;
|
||||
import com.ruoyi.common.utils.SiteSecurityUtils;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -15,6 +17,8 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@@ -25,19 +29,54 @@ public class AppFileController extends BaseController {
|
||||
@Autowired
|
||||
private IdGenerator idGenerator;
|
||||
|
||||
//文件大小限制 10MB
|
||||
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
@ApiOperation("上传文件")
|
||||
@PostMapping("/upload")
|
||||
public AjaxResult upload(@RequestParam("file") MultipartFile file, @RequestParam(value = "bussinessid",required = false) Long bussinessId) {
|
||||
if(!(SiteSecurityUtils.isLogin() || SecurityUtils.isLogin())){
|
||||
if (file.isEmpty()) return AjaxResult.error("文件不能为空!");
|
||||
if (file.getSize() > MAX_FILE_SIZE) {
|
||||
return AjaxResult.error("文件大小超出限制!");
|
||||
}
|
||||
if(!(SiteSecurityUtils.isLogin() || SecurityUtils.isLogin())){
|
||||
return AjaxResult.error("未登录,请登录后在上传文件!");
|
||||
}
|
||||
}
|
||||
boolean isValid = FileValid.validateFile(file);
|
||||
if (!isValid) {
|
||||
return AjaxResult.error("文件类型不合法或内容被篡改!");
|
||||
}
|
||||
return fileService.upload(file,bussinessId);
|
||||
}
|
||||
|
||||
@ApiOperation("获取附件列表")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(File file)
|
||||
public TableDataInfo list(File file,HttpServletRequest request)
|
||||
{
|
||||
String host = request.getHeader("Host");
|
||||
List<String> allowedHosts = Arrays.asList(
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
"10.98.80.146",
|
||||
"10.98.80.50",
|
||||
"www.xjksly.cn",
|
||||
"ks.zhaopinzao8dian.com"
|
||||
);
|
||||
|
||||
if (host != null) {
|
||||
if (host.contains(":")) {
|
||||
host = host.split(":")[0];
|
||||
}
|
||||
if (!allowedHosts.contains(host)) {
|
||||
return error(401,"非法请求来源");
|
||||
}
|
||||
}
|
||||
|
||||
//修复CORS 跨域漏洞
|
||||
HttpServletResponse response = ServletUtils.getResponse();
|
||||
response.setHeader("Access-Control-Allow-Origin", "");
|
||||
response.setHeader("Access-Control-Allow-Credentials", "");
|
||||
|
||||
if(file.getBussinessid()==null){
|
||||
return error(400,"无效的业务id!");
|
||||
}
|
||||
@@ -55,11 +94,19 @@ public class AppFileController extends BaseController {
|
||||
@ApiOperation("上传文件")
|
||||
@PostMapping("/uploadFile")
|
||||
public AjaxResult uploadFile(@RequestParam("file") MultipartFile file, @RequestParam(value = "bussinessid",required = false) Long bussinessId, HttpServletRequest request) {
|
||||
String proxyServer = ProxyServerUtil.getProxyServer(request);
|
||||
System.out.println("获取服务器地址======================"+proxyServer);
|
||||
if (file.isEmpty()) return AjaxResult.error("文件不能为空!");
|
||||
if (file.getSize() > MAX_FILE_SIZE) {
|
||||
return AjaxResult.error("文件大小超出限制!");
|
||||
}
|
||||
if(!(SiteSecurityUtils.isLogin() || SecurityUtils.isLogin())){
|
||||
return AjaxResult.error("未登录,请登录后在上传文件!");
|
||||
}
|
||||
boolean isValid = FileValid.validateFile(file);
|
||||
if (!isValid) {
|
||||
return AjaxResult.error("文件类型不合法或内容被篡改!");
|
||||
}
|
||||
String proxyServer = ProxyServerUtil.getProxyServer(request);
|
||||
System.out.println("获取服务器地址======================"+proxyServer);
|
||||
if(bussinessId==null){
|
||||
bussinessId=idGenerator.generateId();
|
||||
}
|
||||
|
||||
@@ -117,10 +117,14 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements IF
|
||||
throw new SecurityException("非法路径,禁止访问");
|
||||
}
|
||||
|
||||
|
||||
// 保存文件到服务器
|
||||
Files.copy(file.getInputStream(), filePath);
|
||||
|
||||
java.io.File destFile = filePath.toFile();
|
||||
destFile.setReadable(true, false); // 所有用户可读
|
||||
destFile.setWritable(false); // 禁止写入
|
||||
destFile.setExecutable(false);
|
||||
|
||||
// 保存文件信息到数据库
|
||||
saveFileInfo(fileName, bussinessid);
|
||||
|
||||
@@ -163,6 +167,11 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements IF
|
||||
// 保存文件到服务器
|
||||
Files.copy(file.getInputStream(), filePath);
|
||||
|
||||
java.io.File destFile = filePath.toFile();
|
||||
destFile.setReadable(true, false); // 所有用户可读
|
||||
destFile.setWritable(false); // 禁止写入
|
||||
destFile.setExecutable(false);
|
||||
|
||||
// 保存文件信息到数据库
|
||||
File svFile=saveFileInfo(fileName, bussinessid);
|
||||
AjaxResult ajaxResult=AjaxResult.success();
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.ruoyi.cms.util.file;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件校验
|
||||
*/
|
||||
public class FileValid {
|
||||
|
||||
/**
|
||||
* 白名单配置
|
||||
*/
|
||||
final static List<String> allowedExt = Arrays.asList(
|
||||
"jpg", "jpeg", "png", "gif", "pdf",
|
||||
"doc", "docx", "wps",
|
||||
"xls", "xlsx",
|
||||
"ppt", "pptx"
|
||||
);
|
||||
|
||||
/**
|
||||
* 支持的MIME类型
|
||||
*/
|
||||
final static List<String> allowedMime = Arrays.asList(
|
||||
"image/jpeg", "image/png", "image/gif", "application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-works", // wps
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||
);
|
||||
|
||||
/**
|
||||
* 魔数校验
|
||||
*/
|
||||
final static List<String> allowedMagic = Arrays.asList(
|
||||
"ffd8ffe0", "ffd8ffe1", // jpg
|
||||
"89504e47", // png
|
||||
"47494638", // gif
|
||||
"25504446" // pdf
|
||||
);
|
||||
|
||||
/**
|
||||
* 文件类型 + MIME + 魔数 三重验证
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public static boolean validateFile(MultipartFile file) {
|
||||
try {
|
||||
// 白名单配置
|
||||
|
||||
String originalFilename = file.getOriginalFilename().toLowerCase();
|
||||
String contentType = file.getContentType();
|
||||
String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
|
||||
|
||||
// 扩展名 + MIME 校验
|
||||
if (!allowedExt.contains(ext) || !allowedMime.contains(contentType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节转16进制(魔数校验用)
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,6 +52,19 @@ public class EncryptConstants {
|
||||
"/cms/appUser/getUserInfo",
|
||||
"/cms/job/getAppUserYhsc",
|
||||
"/app/idCardLogin",
|
||||
"/app/phoneLogin"
|
||||
"/app/phoneLogin",
|
||||
"/getInfo",
|
||||
"/registerUser"
|
||||
);
|
||||
|
||||
/**
|
||||
* 不需要加密的返回URL路径模式
|
||||
*/
|
||||
public static final List<String> NOT_URL_PATTERNS = Arrays.asList(
|
||||
"/app/user/list",
|
||||
"/app/appskill/list",
|
||||
"/app/userworkexperiences/list",
|
||||
"/app/company/list",
|
||||
"/app/companycontact/list"
|
||||
);
|
||||
}
|
||||
@@ -84,6 +84,13 @@ public class EncryptResponseFilter implements Filter {
|
||||
private boolean needProcess(HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
|
||||
//不加密返回
|
||||
for (String pattern : EncryptConstants.NOT_URL_PATTERNS) {
|
||||
if (pathMatcher.match(pattern, requestURI)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查URL是否匹配需要加解密的模式
|
||||
for (String pattern : EncryptConstants.URL_PATTERNS) {
|
||||
if (pathMatcher.match(pattern, requestURI)) {
|
||||
|
||||
@@ -173,8 +173,8 @@ public class SysLoginService
|
||||
recordLoginInfo(appUser);
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(appUser.getName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
recordLoginInfo(appUser);
|
||||
// 生成token //tokenSiteService.createToken(loginSiteUser) 有时间
|
||||
return tokenSiteService.noExpireCreateToken(loginSiteUser);
|
||||
// 生成token //tokenSiteService.noExpireCreateToken(loginSiteUser);
|
||||
return tokenSiteService.createToken(loginSiteUser);
|
||||
}
|
||||
|
||||
//单点登录
|
||||
|
||||
Reference in New Issue
Block a user