diff --git a/srs-admin/src/main/java/com/srs/web/controller/common/StampController.java b/srs-admin/src/main/java/com/srs/web/controller/common/StampController.java new file mode 100644 index 0000000..92ffcf2 --- /dev/null +++ b/srs-admin/src/main/java/com/srs/web/controller/common/StampController.java @@ -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("读取印章图片失败"); + } + } +} diff --git a/srs-admin/src/main/resources/application.yml b/srs-admin/src/main/resources/application.yml index 80de8b7..381a6ad 100644 --- a/srs-admin/src/main/resources/application.yml +++ b/srs-admin/src/main/resources/application.yml @@ -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 diff --git a/srs-common/src/main/java/com/srs/common/utils/sign/ImageSignUtils.java b/srs-common/src/main/java/com/srs/common/utils/sign/ImageSignUtils.java new file mode 100644 index 0000000..093088d --- /dev/null +++ b/srs-common/src/main/java/com/srs/common/utils/sign/ImageSignUtils.java @@ -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); + } + } +} diff --git a/srs-framework/src/main/java/com/srs/framework/config/SecurityConfig.java b/srs-framework/src/main/java/com/srs/framework/config/SecurityConfig.java index 8cbfd3d..fe0f060 100644 --- a/srs-framework/src/main/java/com/srs/framework/config/SecurityConfig.java +++ b/srs-framework/src/main/java/com/srs/framework/config/SecurityConfig.java @@ -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()