diff --git a/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/configurator/CrosXssFilter.java b/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/configurator/CrosXssFilter.java index e5da23d..fcb1788 100644 --- a/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/configurator/CrosXssFilter.java +++ b/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/configurator/CrosXssFilter.java @@ -8,7 +8,7 @@ import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; -import javax.servlet.annotation.WebFilter; +import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -17,16 +17,20 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import java.util.UUID; +import org.apache.commons.lang3.StringUtils; import org.jboss.logging.MDC; @WebFilter public class CrosXssFilter implements Filter { - + private static final Logger logger = LoggerFactory.getLogger(CrosXssFilter.class); - + @Value("${crosxss.filter.disable:false}") private boolean disable; + @Value("${response.access.control.allow.origin:*}") + private String accessControlAllowOrigin; + @Override public void init(FilterConfig filterConfig) throws ServletException { } @@ -37,34 +41,77 @@ public class CrosXssFilter implements Filter { try { MDC.put("processNo", UUID.randomUUID().toString().replace("-", "")); request.setCharacterEncoding("utf-8"); -// response.setContentType("text/html;charset=utf-8"); + response.setContentType("application/json;charset=UTF-8"); if (disable) { chain.doFilter(request, response); } else { - //跨域设置 if (response instanceof HttpServletResponse) { + HttpServletResponse httpServletResponse = (HttpServletResponse) response; - //禁用浏览器缓存 - httpServletResponse.setHeader("Cache-Control", "no-store"); - //禁止被IFrame嵌套 - httpServletResponse.setHeader("X-Frame-Options", "deny"); - //安全性配置 + HttpServletRequest httpRequest = (HttpServletRequest) request; + + String referer = httpRequest.getHeader("Referer"); + if (StringUtils.isNotBlank(referer) && !"*".equals(accessControlAllowOrigin) + && !referer.startsWith(accessControlAllowOrigin)) { + httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid Referer"); + return; + } + + + httpServletResponse.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); + httpServletResponse.setHeader("Pragma", "no-cache"); + httpServletResponse.setDateHeader("Expires", 0); + + httpServletResponse.setHeader("X-Frame-Options", "SAMEORIGIN"); + + String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 16); // 生成随机 nonce + httpServletResponse.setHeader("Content-Security-Policy", + "default-src 'self'; " + + "img-src 'self' data:; "+ + "font-src 'self' https://i.alicdn.com data:; "+ //阿里系的ui组件 +// "script-src 'self' 'nonce-" + nonce + "'; " + //nonce针对内联 JavaScript +// "style-src 'self' 'nonce-" + nonce + "'; " + //nonce针对内联 CSS + "script-src 'self'; " + + "style-src 'self'; " + + "object-src 'none'; " + + "base-uri 'none'; " + + "form-action 'self'; " + + "frame-ancestors 'none'" + ); httpServletResponse.setHeader("X-XSS-Protection", "1; mode=block"); httpServletResponse.setHeader("X-Content-Type-Options", "nosniff"); httpServletResponse.setHeader("Referrer-Policy", "origin"); + httpServletResponse.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"); + + //add + httpServletResponse.addHeader("Vary", "Origin"); + httpServletResponse.addHeader("Vary", "Access-Control-Request-Method"); + httpServletResponse.addHeader("Vary", "Access-Control-Request-Headers"); + + httpServletResponse.setHeader("Access-Control-Allow-Headers", "*"); + httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS"); + // 设置允许的域名 + httpServletResponse.setHeader("Access-Control-Allow-Origin", accessControlAllowOrigin); + httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); + + + + if ("OPTIONS".equals(((HttpServletRequest) request).getMethod())) { + httpServletResponse.setStatus(HttpServletResponse.SC_OK); // 200 + return; + } } ServletRequest requestWrapper = null; if(request instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) request); } if(requestWrapper == null) { - chain.doFilter(request, response); + chain.doFilter(request, response); } else { - chain.doFilter(requestWrapper, response); + chain.doFilter(requestWrapper, response); } } } finally { - // 避免线程泄漏 MDC.clear(); } diff --git a/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/controller/AccountController.java b/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/controller/AccountController.java index 2e2dce3..9dacbc4 100644 --- a/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/controller/AccountController.java +++ b/dongjian-center-admin-controller/src/main/java/com/dongjian/datacenter/admin/controller/AccountController.java @@ -1,17 +1,18 @@ package com.dongjian.datacenter.admin.controller; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.LineCaptcha; +import com.dongjian.datacenter.admin.service.captcha.HutoolCaptchaGenerator; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import org.apache.tomcat.util.codec.binary.Base64; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mobile.device.Device; import org.springframework.web.bind.annotation.*; -import com.google.code.kaptcha.impl.DefaultKaptcha; import com.dongjian.datacenter.admin.common.response.SimpleDataResponse; import com.dongjian.datacenter.admin.configurator.interceptor.AccessRequired; import com.dongjian.datacenter.admin.dto.account.CacheUserData; @@ -20,11 +21,9 @@ import com.dongjian.datacenter.admin.service.AccountService; import com.dongjian.datacenter.admin.service.captcha.CaptchaService; import com.dongjian.datacenter.admin.service.captcha.CaptchaVO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; +import java.awt.*; import java.io.IOException; -import javax.imageio.ImageIO; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -40,8 +39,6 @@ public class AccountController { @Autowired private AccountService accountService; - @Autowired - private DefaultKaptcha producer; @Autowired private CaptchaService captchaService; @@ -84,20 +81,22 @@ public class AccountController { @Operation(summary = "获取登录验证码") @RequestMapping(value = "/getCaptcha", method = RequestMethod.GET ) public SimpleDataResponse getCaptcha() throws IOException { - // 生成文字验证码 - String content = producer.createText(); - // 生成图片验证码 - ByteArrayOutputStream outputStream = null; - BufferedImage image = producer.createImage(content); - outputStream = new ByteArrayOutputStream(); - ImageIO.write(image, "jpg", outputStream); - // 对字节数组Base64编码 -// BASE64Encoder encoder = new BASE64Encoder(); + // 使用 Hutool 创建一个验证码 + LineCaptcha captcha = CaptchaUtil.createLineCaptcha(110, 40, 4, 5); + captcha.setFont(new Font("微软雅黑", Font.BOLD, 32)); + captcha.setGenerator(new HutoolCaptchaGenerator()); + // 重新生成验证码内容(因为 setGenerator() 之后要刷新) + captcha.createCode(); + String content = captcha.getCode(); // 获取验证码文本 + // Encode byte array to Base64 String str = "data:image/jpeg;base64,"; - String base64Img = str + Base64.encodeBase64String(outputStream.toByteArray()).replace("\n", "").replace("\r", ""); - CaptchaVO captchaVO = captchaService.cacheCaptcha(content); + String base64Img = str + captcha.getImageBase64() + .replace("\n", "") + .replace("\r", ""); + // Cache captcha and prepare response + CaptchaVO captchaVO = captchaService.cacheCaptcha(content); captchaVO.setBase64Img(base64Img); - return SimpleDataResponse.success(captchaVO); + return SimpleDataResponse.success(captchaVO); } } \ No newline at end of file diff --git a/dongjian-center-admin-controller/src/main/resources/config/application.properties b/dongjian-center-admin-controller/src/main/resources/config/application.properties index fdc1d3f..2921455 100644 --- a/dongjian-center-admin-controller/src/main/resources/config/application.properties +++ b/dongjian-center-admin-controller/src/main/resources/config/application.properties @@ -10,15 +10,15 @@ spring.datasource.name=data_center_aeon_admin spring.datasource.url=jdbc:mysql://${datasourceDNS}/data_center_aeon_admin?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=${datasourceTimeZone} spring.datasource.username=${datasourceUsername} spring.datasource.password=${datasourcePassword} -#使用druid数据源 +#\u4F7F\u7528druid\u6570\u636E\u6E90 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver -#配置log日志 +#\u914D\u7F6Elog\u65E5\u5FD7 logging.config=classpath:config/logback-boot.xml logging_level=${loggingLevel} logging_path=${loggingPath} -#部署时使用SYSLOG +#\u90E8\u7F72\u65F6\u4F7F\u7528SYSLOG logging_appender=${loggingAppender} logging_maxHistory=${loggingMaxHistory:7} logging_maxFileSize=100MB @@ -26,38 +26,38 @@ mybatis_log_level=${mybatisLogLevel} user.login.keytimeout=360000 -#集群模式cluster +#\u96C6\u7FA4\u6A21\u5F0Fcluster spring.redis.cluster.nodes=192.168.0.30:7000,192.168.0.30:7001 -#跨集群执行命令时要遵循的最大重定向数量 +#\u8DE8\u96C6\u7FA4\u6267\u884C\u547D\u4EE4\u65F6\u8981\u9075\u5FAA\u7684\u6700\u5927\u91CD\u5B9A\u5411\u6570\u91CF spring.redis.cluster.max-redirects=3 -#哨兵模式sentinel +#\u54E8\u5175\u6A21\u5F0Fsentinel spring.redis.sentinel.master=mymaster spring.redis.sentinel.nodes=192.168.0.30:16379,192.168.0.30:16379 -#单机模式standalone +#\u5355\u673A\u6A21\u5F0Fstandalone spring.redis.host=${redisHost} spring.redis.port=6379 spring.redis.password=${redisPassword} spring.redis.timeout=5000 -#Redis数据库索引(默认为0) +#Redis\u6570\u636E\u5E93\u7D22\u5F15\uFF08\u9ED8\u8BA4\u4E3A0\uFF09 spring.redis.database=15 -#配置启动模式cluster、sentinel、standalone +#\u914D\u7F6E\u542F\u52A8\u6A21\u5F0Fcluster\u3001sentinel\u3001standalone spring.redis.mode=standalone # Lettuce -# 连接池最大连接数(使用负值表示没有限制) +# \u8FDE\u63A5\u6C60\u6700\u5927\u8FDE\u63A5\u6570\uFF08\u4F7F\u7528\u8D1F\u503C\u8868\u793A\u6CA1\u6709\u9650\u5236\uFF09 spring.redis.lettuce.pool.max-active=8 -# 连接池最大阻塞等待时间(使用负值表示没有限制) +# \u8FDE\u63A5\u6C60\u6700\u5927\u963B\u585E\u7B49\u5F85\u65F6\u95F4\uFF08\u4F7F\u7528\u8D1F\u503C\u8868\u793A\u6CA1\u6709\u9650\u5236\uFF09 spring.redis.lettuce.pool.max-wait=100 -# 连接池中的最大空闲连接 +# \u8FDE\u63A5\u6C60\u4E2D\u7684\u6700\u5927\u7A7A\u95F2\u8FDE\u63A5 spring.redis.lettuce.pool.max-idle=8 -# 连接池中的最小空闲连接 +# \u8FDE\u63A5\u6C60\u4E2D\u7684\u6700\u5C0F\u7A7A\u95F2\u8FDE\u63A5 spring.redis.lettuce.pool.min-idle=0 -# 关闭超时时间 +# \u5173\u95ED\u8D85\u65F6\u65F6\u95F4 spring.redis.lettuce.shutdown-timeout=100 -#邮件发送信息 +#\u90AE\u4EF6\u53D1\u9001\u4FE1\u606F mail.smtp.host=email-smtp.ap-northeast-1.amazonaws.com mail.smtp.port=465 mail.smtp.auth=true @@ -67,13 +67,13 @@ mail.sender.password_encrypted=true mail.sender.password=a/52R0rao7ksRMvl1j17fVEmPCw7gC9OreHDqWOE+S7sgmoQT0YgoLRJqOlJqX7e mail.sender.sendername=datacenter-info mail.sender.from=alert@ttkdatatechbuild.com -#邮件通知服务开关 +#\u90AE\u4EF6\u901A\u77E5\u670D\u52A1\u5F00\u5173 mail.send.switch=true Spring.mvc.hiddenmethod.filter.enabled=true -#单个文件上传发大小 +#\u5355\u4E2A\u6587\u4EF6\u4E0A\u4F20\u53D1\u5927\u5C0F spring.servlet.multipart.max-file-size=20MB -#多个文件上传的共大小不得超过100M +#\u591A\u4E2A\u6587\u4EF6\u4E0A\u4F20\u7684\u5171\u5927\u5C0F\u4E0D\u5F97\u8D85\u8FC7100M spring.servlet.multipart.max-request-size=100MB server.servlet.context-path=/api @@ -82,11 +82,14 @@ mybatis.configuration.map-underscore-to-camel-case=true server.servlet.session.cookie.http-only=true server.servlet.session.cookie.secure=true +server.servlet.session.cookie.same-site=strict springdoc.swagger-ui.doc-expansion=none springdoc.swagger-ui.operations-sorter=alpha springdoc.swagger-ui.tags-sorter=alpha +response.access.control.allow.origin = ${accessControlAllowOrigin:*} + web.login.url=${webLoginUrl} web.login.2d3d.url=${webLoginUrl2d3d} web.admin.login.url=${webAdminLoginUrl} diff --git a/dongjian-center-admin-dao/pom.xml b/dongjian-center-admin-dao/pom.xml index 07cdc77..4db0051 100644 --- a/dongjian-center-admin-dao/pom.xml +++ b/dongjian-center-admin-dao/pom.xml @@ -43,7 +43,7 @@ com.mysql mysql-connector-j - 9.3.0 + 9.5.0 diff --git a/dongjian-center-admin-service/pom.xml b/dongjian-center-admin-service/pom.xml index 6d362d4..49745e0 100644 --- a/dongjian-center-admin-service/pom.xml +++ b/dongjian-center-admin-service/pom.xml @@ -45,11 +45,12 @@ test - - com.github.penggle - kaptcha - 2.3.2 - + + + cn.hutool + hutool-captcha + 5.8.41 + diff --git a/dongjian-center-admin-service/src/main/java/com/dongjian/datacenter/admin/service/captcha/HutoolCaptchaGenerator.java b/dongjian-center-admin-service/src/main/java/com/dongjian/datacenter/admin/service/captcha/HutoolCaptchaGenerator.java new file mode 100644 index 0000000..3b33950 --- /dev/null +++ b/dongjian-center-admin-service/src/main/java/com/dongjian/datacenter/admin/service/captcha/HutoolCaptchaGenerator.java @@ -0,0 +1,27 @@ +package com.dongjian.datacenter.admin.service.captcha; + +import cn.hutool.captcha.generator.CodeGenerator; + +public class HutoolCaptchaGenerator implements CodeGenerator { + + private int length = 4; + private static final String chars = "23456789abcdefghkmnpqrstuvwxyzABCDEFGHKMNPRSTUVWXYZ"; + + @Override + public String generate() { + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + int idx = (int) (Math.random() * chars.length()); + sb.append(chars.charAt(idx)); + } + return sb.toString(); + } + + @Override + public boolean verify(String code, String userInput) { + if (code == null || userInput == null) { + return false; + } + return code.equalsIgnoreCase(userInput); + } +} \ No newline at end of file diff --git a/dongjian-center-admin-service/src/main/java/com/dongjian/datacenter/admin/service/captcha/KaptchaConfig.java b/dongjian-center-admin-service/src/main/java/com/dongjian/datacenter/admin/service/captcha/KaptchaConfig.java deleted file mode 100644 index bba5bcf..0000000 --- a/dongjian-center-admin-service/src/main/java/com/dongjian/datacenter/admin/service/captcha/KaptchaConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.dongjian.datacenter.admin.service.captcha; - -import java.util.Properties; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.google.code.kaptcha.impl.DefaultKaptcha; -import com.google.code.kaptcha.util.Config; - -@Configuration -public class KaptchaConfig { - @Bean - public DefaultKaptcha producer(){ - - DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); - Properties properties = new Properties(); - properties.setProperty("kaptcha.border", "no"); - properties.setProperty("kaptcha.border.color", "105,179,90"); - properties.setProperty("kaptcha.textproducer.font.color", "black"); - properties.setProperty("kaptcha.image.width", "110"); - properties.setProperty("kaptcha.image.height", "40"); - properties.setProperty("kaptcha.textproducer.char.string","23456789abcdefghkmnpqrstuvwxyzABCDEFGHKMNPRSTUVWXYZ"); - properties.setProperty("kaptcha.textproducer.font.size", "30"); - properties.setProperty("kaptcha.textproducer.char.space","3"); - properties.setProperty("kaptcha.session.key", "code"); - properties.setProperty("kaptcha.textproducer.char.length", "4"); - properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑"); -// properties.setProperty("kaptcha.obscurificator.impl","com.xxx");可以重写实现类 - properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise"); - Config config = new Config(properties); - defaultKaptcha.setConfig(config); - - return defaultKaptcha; - } -} \ No newline at end of file diff --git a/dongjian-center-admin-util/pom.xml b/dongjian-center-admin-util/pom.xml index 28907ca..dd2f284 100644 --- a/dongjian-center-admin-util/pom.xml +++ b/dongjian-center-admin-util/pom.xml @@ -31,11 +31,7 @@ org.springframework.boot spring-boot-starter-data-redis - - - org.apache.commons - commons-lang3 - + org.apache.commons commons-pool2 diff --git a/pom.xml b/pom.xml index 57d2939..a13606a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.12 + 3.5.7 @@ -55,7 +55,7 @@ org.apache.tomcat.embed tomcat-embed-core - 10.1.42 + 10.1.49 org.springframework.boot @@ -80,7 +80,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.5.0 + 2.8.14 @@ -91,17 +91,17 @@ com.fasterxml.jackson.core jackson-core - 2.19.0 + 2.19.4 com.fasterxml.jackson.core jackson-databind - 2.19.0 + 2.19.4 com.fasterxml.jackson.core jackson-annotations - 2.19.0 + 2.19.4 @@ -113,7 +113,7 @@ com.mysql mysql-connector-j - 9.3.0 + 9.5.0 @@ -139,20 +139,27 @@ ch.qos.logback logback-classic - 1.5.18 + 1.5.21 compile ch.qos.logback logback-core - 1.5.18 + 1.5.21 compile + + + + org.apache.commons + commons-lang3 + 3.20.0 + org.apache.commons commons-compress - 1.27.1 + 1.28.0 @@ -177,7 +184,7 @@ io.lettuce lettuce-core - 6.5.5.RELEASE + 6.8.1.RELEASE