From 2d342d53bd2fa992a54832ada8718fb6ccbd7634 Mon Sep 17 00:00:00 2001 From: Jeremy <7446482+rejuvenation50@user.noreply.gitee.com> Date: Thu, 29 Jan 2026 10:45:55 +0000 Subject: [PATCH] =?UTF-8?q?###=201.=20SQL=20=E6=B3=A8=E5=85=A5=E9=98=B2?= =?UTF-8?q?=E6=8A=A4=20-=20=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6=20?= =?UTF-8?q?=EF=BC=9A=20src/main/resources/com/ujcms/cms/core/mapper/Articl?= =?UTF-8?q?eMapper.xml=20-=20=E4=BF=AE=E5=A4=8D=E5=86=85=E5=AE=B9=20?= =?UTF-8?q?=EF=BC=9A=E5=B0=86=20${fromUserId}=20=E6=94=B9=E4=B8=BA=20#{fro?= =?UTF-8?q?mUserId}=20=EF=BC=8C=E4=BD=BF=E7=94=A8=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8C=96=E6=9F=A5=E8=AF=A2=E9=98=B2=E6=AD=A2=20SQL=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=20-=20=E4=BF=AE=E5=A4=8D=E5=8E=9F=E5=9B=A0=20?= =?UTF-8?q?=EF=BC=9A=E4=BD=BF=E7=94=A8=20${}=20=E4=BC=9A=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E5=B0=86=E5=80=BC=E6=8B=BC=E6=8E=A5=E5=88=B0=20SQL=20=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=E4=B8=AD=EF=BC=8C=E5=AD=98=E5=9C=A8=20SQL=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E9=A3=8E=E9=99=A9=EF=BC=9B=E4=BD=BF=E7=94=A8=20#{}=20?= =?UTF-8?q?=E4=BC=9A=E4=BD=BF=E7=94=A8=E5=8F=82=E6=95=B0=E5=8C=96=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=EF=BC=8C=E5=AE=89=E5=85=A8=E5=8F=AF=E9=9D=A0=20###=20?= =?UTF-8?q?2.=20CSRF=20=E9=98=B2=E6=8A=A4=E5=8A=A0=E5=BC=BA=20-=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6=20=EF=BC=9A=20src/main/jav?= =?UTF-8?q?a/com/ujcms/cms/core/SecurityConfig.java=20-=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=86=85=E5=AE=B9=20=EF=BC=9A=E5=9C=A8=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E8=BF=87=E6=BB=A4=E5=99=A8=E9=93=BE=E4=B8=AD=E6=98=8E?= =?UTF-8?q?=E7=A1=AE=E5=90=AF=E7=94=A8=20CSRF=20=E9=98=B2=E6=8A=A4?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8=20CookieCsrfTokenRepository.withHtt?= =?UTF-8?q?pOnlyFalse()=20=E5=AD=98=E5=82=A8=20CSRF=20=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=20-=20=E4=BF=AE=E5=A4=8D=E5=8E=9F=E5=9B=A0=20=EF=BC=9A?= =?UTF-8?q?=E4=BC=A0=E7=BB=9F=E4=BC=9A=E8=AF=9D=E6=A8=A1=E5=BC=8F=E4=B8=8B?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E5=8A=A0=E5=BC=BA=20CSRF=20=E9=98=B2?= =?UTF-8?q?=E6=8A=A4=EF=BC=8C=E9=98=B2=E6=AD=A2=E8=B7=A8=E7=AB=99=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=BC=AA=E9=80=A0=E6=94=BB=E5=87=BB=20###=203.=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=AE=89=E5=85=A8=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=20-=20=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6=20?= =?UTF-8?q?=EF=BC=9A=20src/main/java/com/ujcms/cms/core/web/backendapi/Abs?= =?UTF-8?q?tractUploadController.java=20-=20=E4=BF=AE=E5=A4=8D=E5=86=85?= =?UTF-8?q?=E5=AE=B9=20=EF=BC=9A=20=20=20-=20=E6=B7=BB=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=20MIME=20=E7=B1=BB=E5=9E=8B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=96=87=E4=BB=B6=E7=9A=84=20MIME?= =?UTF-8?q?=20=E7=B1=BB=E5=9E=8B=E4=B8=8E=E6=96=87=E4=BB=B6=E5=90=8E?= =?UTF-8?q?=E7=BC=80=E5=8C=B9=E9=85=8D=20=20=20-=20=E5=AE=9E=E7=8E=B0=20is?= =?UTF-8?q?ValidContentType=20=E6=96=B9=E6=B3=95=EF=BC=8C=E5=AF=B9?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=20MIME=20=E7=B1=BB=E5=9E=8B=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=20-=20=E4=BF=AE=E5=A4=8D=E5=8E=9F=E5=9B=A0=20?= =?UTF-8?q?=EF=BC=9A=E9=98=B2=E6=AD=A2=E6=94=BB=E5=87=BB=E8=80=85=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E6=81=B6=E6=84=8F=E6=96=87=E4=BB=B6=EF=BC=8C=E5=A6=82?= =?UTF-8?q?=E5=B0=86=E5=8F=AF=E6=89=A7=E8=A1=8C=E6=96=87=E4=BB=B6=E4=BC=AA?= =?UTF-8?q?=E8=A3=85=E6=88=90=E5=9B=BE=E7=89=87=E6=96=87=E4=BB=B6=20###=20?= =?UTF-8?q?4.=20=E5=AE=89=E5=85=A8=E5=A4=B4=E9=83=A8=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=AE=8C=E5=96=84=20-=20=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6?= =?UTF-8?q?=20=EF=BC=9A=20src/main/java/com/ujcms/cms/core/SecurityConfig.?= =?UTF-8?q?java=20-=20=E4=BF=AE=E5=A4=8D=E5=86=85=E5=AE=B9=20=EF=BC=9A?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=BB=A5=E4=B8=8B=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E5=A4=B4=E9=83=A8=E9=85=8D=E7=BD=AE=EF=BC=9A=20=20=20-=20HSTS?= =?UTF-8?q?=20=EF=BC=9A=E5=90=AF=E7=94=A8=20HTTP=20Strict=20Transport=20Se?= =?UTF-8?q?curity=EF=BC=8C=E5=BC=BA=E5=88=B6=E4=BD=BF=E7=94=A8=20HTTPS=20?= =?UTF-8?q?=20=20-=20Content-Security-Policy=20=EF=BC=9A=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=AE=89=E5=85=A8=E7=AD=96=E7=95=A5=EF=BC=8C?= =?UTF-8?q?=E9=99=90=E5=88=B6=E8=B5=84=E6=BA=90=E5=8A=A0=E8=BD=BD=E6=9D=A5?= =?UTF-8?q?=E6=BA=90=20=20=20-=20X-Content-Type-Options=20=EF=BC=9A?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=20MIME=20=E7=B1=BB=E5=9E=8B=E5=97=85?= =?UTF-8?q?=E6=8E=A2=20=20=20-=20X-Frame-Options=20=EF=BC=9A=E9=98=B2?= =?UTF-8?q?=E6=AD=A2=E7=82=B9=E5=87=BB=E5=8A=AB=E6=8C=81=E6=94=BB=E5=87=BB?= =?UTF-8?q?=20=20=20-=20X-XSS-Protection=20=EF=BC=9A=E5=90=AF=E7=94=A8?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E5=86=85=E7=BD=AE=E7=9A=84=20XSS=20?= =?UTF-8?q?=E9=98=B2=E6=8A=A4=20=20=20-=20Referrer-Policy=20=EF=BC=9A?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=20Referer=20=E5=A4=B4=E9=83=A8=E7=9A=84?= =?UTF-8?q?=E5=8F=91=E9=80=81=20-=20=E4=BF=AE=E5=A4=8D=E5=8E=9F=E5=9B=A0?= =?UTF-8?q?=20=EF=BC=9A=E5=AE=8C=E5=96=84=E5=AE=89=E5=85=A8=E5=A4=B4?= =?UTF-8?q?=E9=83=A8=E9=85=8D=E7=BD=AE=E5=8F=AF=E4=BB=A5=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=9A=84=E5=AE=89=E5=85=A8=E6=80=A7=EF=BC=8C?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E5=90=84=E7=A7=8D=E5=B8=B8=E8=A7=81=E7=9A=84?= =?UTF-8?q?=20Web=20=E6=94=BB=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ujcms/cms/core/SecurityConfig.java | 25 +++++++++++++ .../backendapi/AbstractUploadController.java | 36 +++++++++++++++++++ .../ujcms/cms/core/mapper/ArticleMapper.xml | 2 +- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ujcms/cms/core/SecurityConfig.java b/src/main/java/com/ujcms/cms/core/SecurityConfig.java index 9abbdff8..2048e355 100644 --- a/src/main/java/com/ujcms/cms/core/SecurityConfig.java +++ b/src/main/java/com/ujcms/cms/core/SecurityConfig.java @@ -42,6 +42,9 @@ import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthen import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -135,6 +138,28 @@ public class SecurityConfig { IpLoginAttemptService ipLoginAttemptService) throws Exception { // 无后缀的请求 http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + // 启用CSRF防护 + .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) + // 完善安全头部配置 + .headers(headers -> headers + // 启用HSTS + .httpStrictTransportSecurity(hsts -> hsts + .includeSubDomains(true) + .maxAgeInSeconds(31536000) // 1 year + ) + // 启用内容安全策略 + .contentSecurityPolicy(csp -> csp + .policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';") + ) + // 启用X-Content-Type-Options + .contentTypeOptions(Customizer.withDefaults()) + // 启用X-Frame-Options + .frameOptions(frame -> frame.sameOrigin()) + // 启用X-XSS-Protection + .xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED)) + // 启用Referrer-Policy + .referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)) + ) .rememberMe(rememberMe -> rememberMe.tokenRepository(persistentTokenRepository(dataSource))) .logout(logout -> logout.logoutSuccessUrl("/")) // 使用Exception中的message信息。AccessDeniedHandlerImpl不使用Exception中的message信息会丢失 diff --git a/src/main/java/com/ujcms/cms/core/web/backendapi/AbstractUploadController.java b/src/main/java/com/ujcms/cms/core/web/backendapi/AbstractUploadController.java index eb5ef980..4e9b63b9 100644 --- a/src/main/java/com/ujcms/cms/core/web/backendapi/AbstractUploadController.java +++ b/src/main/java/com/ujcms/cms/core/web/backendapi/AbstractUploadController.java @@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -69,6 +70,12 @@ public abstract class AbstractUploadController { extension, props.getUploadsExtensionBlacklist())); } validateType(types, extension); + // 检查文件内容的MIME类型 + String contentType = multipart.getContentType(); + if (contentType != null && !isValidContentType(contentType, extension)) { + throw new Http400Exception(String.format("file content type not allowed: '%s' for extension '%s'", + contentType, extension)); + } FileHandler fileHandler = Contexts.getCurrentSite().getConfig().getUploadStorage().getFileHandler(pathResolver); File tempFile = Files.createTempFile(null, "." + extension).toFile(); @@ -234,6 +241,35 @@ public abstract class AbstractUploadController { Site site, Long userId, Map result) throws IOException, EncoderException; } + /** + * 验证文件的MIME类型是否与文件后缀匹配 + * + * @param contentType 文件的MIME类型 + * @param extension 文件后缀 + * @return 是否有效 + */ + protected boolean isValidContentType(String contentType, String extension) { + extension = StringUtils.lowerCase(extension); + // 图片类型 + if (Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "webp", "svg").contains(extension)) { + return contentType.startsWith("image/"); + } + // 视频类型 + if (Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv").contains(extension)) { + return contentType.startsWith("video/"); + } + // 音频类型 + if (Arrays.asList("mp3", "wav", "ogg", "aac", "flac").contains(extension)) { + return contentType.startsWith("audio/"); + } + // 文档类型 + if (Arrays.asList("doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf", "txt").contains(extension)) { + return contentType.startsWith("application/") || contentType.equals("text/plain"); + } + // 其他类型 + return true; + } + public static class CropParams { private String url; private int x; diff --git a/src/main/resources/com/ujcms/cms/core/mapper/ArticleMapper.xml b/src/main/resources/com/ujcms/cms/core/mapper/ArticleMapper.xml index 7208362a..de44f7c1 100644 --- a/src/main/resources/com/ujcms/cms/core/mapper/ArticleMapper.xml +++ b/src/main/resources/com/ujcms/cms/core/mapper/ArticleMapper.xml @@ -183,7 +183,7 @@ update ujcms_article set modified_user_id_ = #{toUserId} - where modified_user_id_ = ${fromUserId} + where modified_user_id_ = #{fromUserId} update ujcms_article -- Gitee