日常事务-处分管理 添加印章图片签名验证功能
- 实现了图片签名工具类ImageSignUtils,支持生成和验证带时间戳的签名URL - 配置SecurityConfig允许匿名访问签名的印章图片URL,但Base64接口需认证 - 创建StampController提供受保护的印章图片访问接口 - 将应用默认文件路径配置改为Windows环境路径 - 支持通过签名URL安全访问印章图片资源
This commit is contained in:
@@ -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("读取印章图片失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ srs:
|
|||||||
# 实例演示开关
|
# 实例演示开关
|
||||||
demoEnabled: true
|
demoEnabled: true
|
||||||
# 文件路径 示例( Windows配置D:/srs/uploadPath,Linux配置 /home/srs/uploadPath)#D:/srs/uploadPath
|
# 文件路径 示例( 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
|
#profile: D:/srs/uploadPath #/srs/uploadPath
|
||||||
# 获取ip地址开关
|
# 获取ip地址开关
|
||||||
addressEnabled: false
|
addressEnabled: false
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,6 +140,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
// 静态资源,可匿名访问
|
// 静态资源,可匿名访问
|
||||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||||
.antMatchers("/swagger-ui.html", "doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").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()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
@@ -172,6 +176,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
// 静态资源,可匿名访问
|
// 静态资源,可匿名访问
|
||||||
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||||
.antMatchers("/swagger-ui.html", "doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").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()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
|
|||||||
Reference in New Issue
Block a user