Compare commits

...

2 Commits

Author SHA1 Message Date
2c591aa557 Merge remote-tracking branch 'origin/main' 2026-03-20 11:49:15 +08:00
f99b4621d2 日常事务-处分管理 添加印章图片签名验证功能
- 实现了图片签名工具类ImageSignUtils,支持生成和验证带时间戳的签名URL
- 配置SecurityConfig允许匿名访问签名的印章图片URL,但Base64接口需认证
- 创建StampController提供受保护的印章图片访问接口
- 将应用默认文件路径配置改为Windows环境路径
- 支持通过签名URL安全访问印章图片资源
2026-03-20 11:48:37 +08:00
4 changed files with 183 additions and 1 deletions

View File

@@ -0,0 +1,74 @@
package com.srs.web.controller.common;
import com.srs.common.config.SrsConfig;
import com.srs.common.core.controller.BaseController;
import com.srs.common.core.domain.AjaxResult;
import com.srs.common.utils.sign.ImageSignUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Base64;
/**
* 受保护图片接口
* 提供签名URL的图片访问
*/
@RestController
@RequestMapping("/common/stamp")
@Api(value = "受保护图片管理", tags = "受保护图片管理")
public class StampController extends BaseController {
/**
* 获取印章图片的Base64编码需要登录验证
*/
@GetMapping("/base64")
@ApiOperation("获取印章图片Base64编码")
public AjaxResult getStampBase64() {
String stampPath = SrsConfig.getProfile() + "/stamp/stamp.jpg";
File file = new File(stampPath);
if (!file.exists()) {
return error("印章图片不存在");
}
try (FileInputStream fis = new FileInputStream(file)) {
byte[] bytes = new byte[(int) file.length()];
fis.read(bytes);
String base64 = Base64.getEncoder().encodeToString(bytes);
String dataUrl = "data:image/jpeg;base64," + base64;
return success(dataUrl);
} catch (IOException e) {
return error("读取印章图片失败");
}
}
/**
* 获取印章图片的Base64编码指定文件名
*/
@GetMapping("/base64/{fileName}")
@ApiOperation("获取印章图片Base64编码")
public AjaxResult getStampBase64ByName(@PathVariable String fileName) {
String stampPath = SrsConfig.getProfile() + "/stamp/" + fileName;
File file = new File(stampPath);
if (!file.exists()) {
return error("印章图片不存在");
}
try (FileInputStream fis = new FileInputStream(file)) {
byte[] bytes = new byte[(int) file.length()];
fis.read(bytes);
String base64 = Base64.getEncoder().encodeToString(bytes);
String mimeType = fileName.toLowerCase().endsWith(".png") ? "image/png" : "image/jpeg";
String dataUrl = "data:" + mimeType + ";base64," + base64;
return success(dataUrl);
} catch (IOException e) {
return error("读取印章图片失败");
}
}
}

View File

@@ -9,7 +9,7 @@ srs:
# 实例演示开关
demoEnabled: true
# 文件路径 示例( Windows配置D:/srs/uploadPath,Linux配置 /home/srs/uploadPath)#D:/srs/uploadPath
profile: /usr/local/java/srs/uploadPath #/usr/local/java/srs/uploadPath
profile: D:/srs/uploadPath #/usr/local/java/srs/uploadPath
#profile: D:/srs/uploadPath #/srs/uploadPath
# 获取ip地址开关
addressEnabled: false

View File

@@ -0,0 +1,100 @@
package com.srs.common.utils.sign;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
/**
* 图片签名工具类
* 用于生成和验证图片访问签名URL
*/
public class ImageSignUtils {
private static final String SECRET_KEY = "srs-stamp-secret-key-2024";
private static final long DEFAULT_EXPIRE_TIME = 3 * 60_000; // 3分钟
/**
* 生成签名
*/
public static String generateSign(String fileName, long expireTime) {
String raw = fileName + "-" + expireTime + "-" + SECRET_KEY;
return md5(raw);
}
/**
* 生成带签名的URL
*/
public static String generateSignedUrl(String fileName) {
long expireTime = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;
String sign = generateSign(fileName, expireTime);
return String.format("/common/stamp/%s?expire=%d&sign=%s",
encodeFileName(fileName), expireTime, sign);
}
/**
* 验证签名是否有效
*/
public static boolean validateSign(String fileName, long expireTime, String sign) {
if (System.currentTimeMillis() > expireTime) {
return false; // 已过期
}
String expectedSign = generateSign(fileName, expireTime);
return expectedSign.equals(sign);
}
/**
* 从请求中提取文件名
*/
public static String extractFileName(HttpServletRequest request) {
String uri = request.getRequestURI();
String fileName = uri.substring(uri.lastIndexOf("/common/stamp/") + 14);
int paramIndex = fileName.indexOf("?");
if (paramIndex > 0) {
fileName = fileName.substring(0, paramIndex);
}
return decodeFileName(fileName);
}
/**
* 验证请求签名
*/
public static boolean validateRequest(HttpServletRequest request) {
String fileName = extractFileName(request);
String expireStr = request.getParameter("expire");
String sign = request.getParameter("sign");
if (fileName == null || expireStr == null || sign == null) {
return false;
}
try {
long expireTime = Long.parseLong(expireStr);
return validateSign(fileName, expireTime, sign);
} catch (NumberFormatException e) {
return false;
}
}
private static String encodeFileName(String fileName) {
return Base64.getUrlEncoder().encodeToString(fileName.getBytes(StandardCharsets.UTF_8));
}
private static String decodeFileName(String encoded) {
return new String(Base64.getUrlDecoder().decode(encoded), StandardCharsets.UTF_8);
}
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5 calculation failed", e);
}
}
}

View File

@@ -140,6 +140,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 签名的印章图片URL可匿名访问签名验证
.antMatchers(HttpMethod.GET, "/common/stamp/**").permitAll()
// Base64接口需要认证
.antMatchers(HttpMethod.GET, "/common/stamp/base64/**").authenticated()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
@@ -172,6 +176,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 签名的印章图片URL可匿名访问签名验证
.antMatchers(HttpMethod.GET, "/common/stamp/**").permitAll()
// Base64接口需要认证
.antMatchers(HttpMethod.GET, "/common/stamp/base64/**").authenticated()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()