合肥seo网站推广外包广东网站设计的公司
2026/1/27 17:52:29 网站建设 项目流程
合肥seo网站推广外包,广东网站设计的公司,深圳知名网站建设平台,自己做网站排名#xff08;本文加解密使用规则即为博主之前写的加解密文章#xff09; 地址: SM4加解密 本文介绍了基于SM4加解密的零侵入式API安全方案#xff0c;主要包含两部分实现#xff1a; 1、响应加密通过EncryptResponse注解标记需要加密的接口#xff0c;由EncryptResponseI…本文加解密使用规则即为博主之前写的加解密文章 地址: SM4加解密本文介绍了基于SM4加解密的零侵入式API安全方案主要包含两部分实现1、响应加密通过EncryptResponse注解标记需要加密的接口由EncryptResponseInterceptor拦截器对返回结果自动进行SM4加密处理2、请求解密通过DecryptRequest注解标记需要解密的接口由DecryptRequestInterceptor拦截器配合RequestWrapperFilter过滤器实现POST/PUT/GET请求的参数自动解密。3、方案采用ThreadLocal存储解密数据支持明密文混合传输提供完整的参数解析器实现并内置内存泄漏防护机制。4、所有加解密操作通过Feign调用远程服务完成开发者只需添加注解即可实现接口级安全防护无需修改业务代码。一、自定义加密注解相关代码及效果展示1、自定义注解DecryptRequest博主这里是若依微服务项目为了方便使用故而放在com.ruoyi.common.security 目录下package com.ruoyi.common.security.annotation; import java.lang.annotation.*; /** * 解密标记注解仅需加在需要解密的接口上原代码无需任何修改零侵入。进去参数绑定前会自动解密并成参数绑定 */ Target({ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Documented public interface DecryptRequest { }2、零侵入响应结果加密拦截器结果返回给前端前自动完成加密处理仅对标记上该注解接口生效这里使用的加密依赖为本博主前面文章SM4加解密所写接口这里是微服务feign api形式调用各位可直接连同那边代码一起搬运使用也可自己编写一套加解密规则进行替换使用。不影响整个注解效果package com.ruoyi.common.security.interceptor; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.security.annotation.EncryptResponse; import com.ruoyi.system.api.RemoteSMService; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.lang.reflect.Method; /** * 响应结果加密拦截器 * 拦截标记EncryptResponse注解的方法对返回结果进行加密处理 */ Component ControllerAdvice public class EncryptResponseInterceptor implements ResponseBodyAdviceObject { // 从 Spring 容器中注入依赖 加解密专用服务 feign api调用 Autowired private RemoteSMService remoteSMService; /** * 判断是否需要执行加密处理 */ Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { // 检查方法是否标记了EncryptResponse注解 Method method returnType.getMethod(); boolean annotationPresent method.isAnnotationPresent(EncryptResponse.class); // 2. 获取目标类绕开代理 Class? targetClass returnType.getDeclaringClass(); // 3. 获取目标类的真实方法AopUtils确保拿到原方法 Method targetMethod AopUtils.getMostSpecificMethod(method, targetClass); // 4. 用AnnotationUtils获取注解即使被代理也能拿到 EncryptResponse annotation AnnotationUtils.findAnnotation(targetMethod, EncryptResponse.class); return method ! null annotationPresent; } /** * 对响应结果进行加密处理 */ Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { JSONObject object JSONObject.from(body); // 非200状态码不加密 if(object.getIntValue(code) ! 200){ return body; } // 获取注解信息 EncryptResponse annotation returnType.getMethodAnnotation(EncryptResponse.class); if (annotation null) { return body; } try { // 转换为JSON字符串作为加密入参 String jsonData JSONObject.toJSONString(body); // 调用SM4加密接口 AjaxResult encryptResult remoteSMService.sm4EncryptStr(jsonData, null); if (!encryptResult.isSuccess()) { return AjaxResult.error(加密失败 encryptResult.get(msg)); } String encryptedData encryptResult.get(data).toString(); // 构建最终响应结果 AjaxResult finalResult new AjaxResult(); finalResult.put(code, object.get(code)); finalResult.put(msg, object.get(msg)); finalResult.put(encrypted, encryptedData); return finalResult; } catch (Exception e) { // 加密过程异常不影响原始响应仅打日志 e.printStackTrace(); return body; } } }3、实际作用示例1、未加密处理返回结果原文代码及返回结果展示1、请求信息2、接口接参3、未加密返回2、加密处理返回结果原文代码及结果展示1、请求信息这里包含了下面的要讲解的前端加密传参请求数据2、未返回前明文信息3、加密代码生效过程截图4、实际返回结果信息已加密3、作用及说明1、自定义注解EncryptResponse 仅实现对方法进行标记 不影响其他注解效果2、拦截器EncryptResponseInterceptor 实现在接口处理完成返回给前端前进行拦截处理并进行条件过滤仅针对被标记后的方法及 code 200请求响应进行处理其他接口正常明文放行3、使用示例二、解密相关代码及效果展示1、解密注解DecryptRequest博主这里是若依微服务项目为了方便使用故而放在com.ruoyi.common.security 目录下package com.ruoyi.common.security.annotation; import java.lang.annotation.*; /** * 接口返回结果加密注解 * 标记此注解的方法会对返回结果进行SM4加密处理 */ Target({ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Documented public interface EncryptResponse { /** * 是否加密整个响应体 * 默认为true加密整个返回对象false时可指定加密字段 */ boolean encryptAll() default true; /** * 需要加密的字段名当encryptAll为false时生效 */ String[] encryptFields() default {}; }2、POST/PUT请求体包装类缓存流支持重复读取package com.ruoyi.common.security.config; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.StandardCharsets; /** * POST/PUT请求体包装类缓存流支持重复读取 */ public class BufferedServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); // 读取原始流并缓存 try (InputStream is request.getInputStream()) { body is.readAllBytes(); } } Override public ServletInputStream getInputStream() throws IOException { ByteArrayInputStream bis new ByteArrayInputStream(body); return new ServletInputStream() { Override public boolean isFinished() { return bis.available() 0; } Override public boolean isReady() { return true; } Override public void setReadListener(ReadListener listener) {} Override public int read() throws IOException { return bis.read(); } }; } Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8)); } // 获取缓存的请求体字符串 public String getBody() { return new String(body, StandardCharsets.UTF_8); } }3、RequestWrapperFilter过滤器package com.ruoyi.common.security.filter; import com.ruoyi.common.security.config.BufferedServletRequestWrapper; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; import java.io.IOException; // 新增过滤器需注册到Spring Component public class RequestWrapperFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 仅包装POST/PUT请求 if (request instanceof HttpServletRequest) { HttpServletRequest httpRequest (HttpServletRequest) request; String method httpRequest.getMethod().toUpperCase(); if (POST.equals(method) || PUT.equals(method)) { request new BufferedServletRequestWrapper(httpRequest); } } chain.doFilter(request, response); } }4、零侵入拦截器package com.ruoyi.common.security.interceptor; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONValidator; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.security.annotation.DecryptRequest; import com.ruoyi.common.security.config.BufferedServletRequestWrapper; import com.ruoyi.common.security.utils.DecryptDataHolder; import com.ruoyi.system.api.RemoteSMService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; import java.util.*; /** * 零侵入拦截器 * 1. POST/PUT解密encrypted字段合并到原有JSON存入ThreadLocal * 2. GET解密encrypted字段合并到原有参数存入ThreadLocal * 3. 不修改原Request不影响原有逻辑 */ Component // 添加这个注解 Slf4j public class DecryptRequestInterceptor implements HandlerInterceptor { Autowired private RemoteSMService remoteSMService; // 构造方法日志验证Spring是否创建实例 public DecryptRequestInterceptor() { log.info( DecryptRequestInterceptor 实例被创建 ); } Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 仅处理Controller方法 if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod (HandlerMethod) handler; Method method handlerMethod.getMethod(); // 2. 仅处理标记DecryptRequest的接口 DecryptRequest decryptAnnotation AnnotationUtils.findAnnotation(method, DecryptRequest.class); if (decryptAnnotation null) { return true; } // 3. 按请求方式处理POST/PUT/GET String requestMethod request.getMethod().toUpperCase(); switch (requestMethod) { case POST: case PUT: return handlePostPut(request, response); case GET: return handleGet(request, response); default: return true; } } /** * POST/PUT处理解密encrypted合并到原有JSON */ private boolean handlePostPut(HttpServletRequest request, HttpServletResponse response) throws IOException { // 复用过滤器已包装的request避免重复创建 BufferedServletRequestWrapper requestWrapper; if (request instanceof BufferedServletRequestWrapper) { requestWrapper (BufferedServletRequestWrapper) request; } else { requestWrapper new BufferedServletRequestWrapper(request); } String originalBody requestWrapper.getBody(); // 校验请求体 if (!StringUtils.hasText(originalBody)) { writeError(response, 请求体为空); return false; } // 解析原始JSON JSONObject originalJson; try { originalJson JSONObject.parseObject(originalBody); } catch (Exception e) { writeError(response, 请求体不是合法JSON); return false; } // 提取encrypted字段无则直接放行存入原始JSON String encryptedStr originalJson.getString(encrypted); if (!StringUtils.hasText(encryptedStr)) { DecryptDataHolder.setDecryptedBody(originalBody); return true; } // 调用Feign解密 String decryptedData decrypt(encryptedStr, request, response); if (decryptedData null) { return false; } // 合并数据原有JSON 解密后的JSON解密字段覆盖同名原始字段 JSONObject decryptedJson JSONObject.parseObject(decryptedData); originalJson.remove(encrypted); // 移除密文字段 originalJson.putAll(decryptedJson); // 合并新字段 String finalBody originalJson.toJSONString(); // 存入ThreadLocal供参数解析器读取 DecryptDataHolder.setDecryptedBody(finalBody); return true; } /** * GET处理解密encrypted合并到原有参数 */ private boolean handleGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // 提取原始GET参数MapString, String[] MapString, String[] originalParams new HashMap(request.getParameterMap()); // 提取encrypted字段无则直接放行 if (!originalParams.containsKey(encrypted)) { return true; } String[] encryptedArr originalParams.get(encrypted); String encryptedStr (encryptedArr ! null encryptedArr.length 0) ? encryptedArr[0] : ; if (!StringUtils.hasText(encryptedStr)) { return true; } // 调用Feign解密 String decryptedData decrypt(encryptedStr, request, response); if (decryptedData null) { return false; } // 合并数据原有参数 解密后的参数解密字段覆盖同名原始参数 JSONObject decryptedJson JSONObject.parseObject(decryptedData); originalParams.remove(encrypted); // 移除密文字段 // 转换为MapString, String[]格式适配GET参数规范 decryptedJson.forEach((key, value) - { if (value null) { originalParams.put(key, new String[]{}); } else if (value.getClass().isArray() || value instanceof Iterable) { // 处理数组/集合类型 Iterable? iterable; if (value.getClass().isArray()) { // 数组转List实现Iterable iterable Arrays.asList((Object[]) value); } else { // 集合直接强转 iterable (Iterable?) value; } // 转为String数组 ListString strList new ArrayList(); iterable.forEach(item - strList.add(item null ? : item.toString())); String[] strArr strList.toArray(new String[0]); originalParams.put(key, strArr); } else { // 普通类型数字、布尔等转为字符串数组 originalParams.put(key, new String[]{value.toString()}); } }); // 存入ThreadLocal供参数解析器读取 DecryptDataHolder.setDecryptedGetParams(originalParams); return true; } /** * 通用解密逻辑调用Feign接口 */ private String decrypt(String encryptedStr, HttpServletRequest request, HttpServletResponse response) throws IOException { // 校验appid从请求头获取可根据业务调整 String appid request.getHeader(appid); if (StringUtils.isEmpty(appid)){ appid null; } // 调用Feign解密接口 AjaxResult decryptResult remoteSMService.sm4DecryptStr(encryptedStr, appid); if (!decryptResult.isSuccess()) { writeError(response, 解密失败 decryptResult.get(msg)); return null; } String decryptedData decryptResult.get(data).toString(); if (!JSONValidator.from(decryptedData).validate()) { writeError(response, 解密后数据不是合法JSON); return null; } return decryptResult.get(data).toString(); } /** * 输出错误响应 */ private void writeError(HttpServletResponse response, String msg) throws IOException { response.setContentType(application/json;charsetUTF-8); response.getWriter().write(JSONObject.toJSONString(AjaxResult.error(msg))); response.getWriter().flush(); } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info(清理ThreadLocal); // 清理ThreadLocal避免内存泄漏 DecryptDataHolder.clear(); } }5、零侵入参数解析器Get/POST的非RequestBody 请求参数6、零侵入解析器: POST/PUT 请求的RequestBody参数则通过DecryptRequestBodyAdvice增强7、存储解密后的请求体POST/PUT供拦截器和参数解析器共享package com.ruoyi.common.security.utils; import java.util.Map; /** * 存储解密后的请求体POST/PUT供拦截器和参数解析器共享 */ public class DecryptDataHolder { // 存储POST/PUT解密后的完整JSON字符串 private static final ThreadLocalString DECRYPTED_BODY new ThreadLocal(); // 存储GET解密后的完整参数MapString[]适配多值参数 private static final ThreadLocalMapString, String[] DECRYPTED_GET_PARAMS new ThreadLocal(); // POST/PUT相关 public static void setDecryptedBody(String body) { DECRYPTED_BODY.set(body); } public static String getDecryptedBody() { return DECRYPTED_BODY.get(); } // GET相关 public static void setDecryptedGetParams(MapString, String[] params) { DECRYPTED_GET_PARAMS.set(params); } public static MapString, String[] getDecryptedGetParams() { return DECRYPTED_GET_PARAMS.get(); } // 清理资源避免内存泄漏 public static void clear() { DECRYPTED_BODY.remove(); DECRYPTED_GET_PARAMS.remove(); } }8、WebMvcConfig拦截器配置修改package com.ruoyi.common.security.config; import com.ruoyi.common.security.interceptor.DecryptRequestInterceptor; import com.ruoyi.common.security.interceptor.HeaderInterceptor; import com.ruoyi.common.security.resolver.DecryptedArgumentResolver; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * 拦截器配置 * * author ruoyi */ Configuration Slf4j public class WebMvcConfig implements WebMvcConfigurer { /** * 不需要拦截地址 */ public static final String[] excludeUrls {/login, /logout, /refresh, /static/**, /favicon.ico}; // 从 Spring 容器中注入拦截器而不是手动 new Autowired Lazy private DecryptRequestInterceptor decryptRequestInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getHeaderInterceptor()) .addPathPatterns(/**) .excludePathPatterns(excludeUrls) .order(-10); // 打印日志验证是否执行了注册逻辑 log.info( 开始注册DecryptRequestInterceptor ); registry.addInterceptor(decryptRequestInterceptor) .addPathPatterns(/**) .excludePathPatterns(excludeUrls) .order(-1); log.info( DecryptRequestInterceptor 注册完成 ); } /** * 自定义请求头拦截器 */ public HeaderInterceptor getHeaderInterceptor() { return new HeaderInterceptor(); } // 新增注册自定义参数解析器 Override public void addArgumentResolvers(ListHandlerMethodArgumentResolver resolvers) { // 将自定义解析器添加到列表中 resolvers.add(0, new DecryptedArgumentResolver()); } }9、实际使用示范及效果截图1、使用方式示例在需要解密的接口上方打上注解即可2、Post请求示例1、纯明文请求1、请求参数2、接收接收参数3、返回结果2、纯加密请求1、请求参数2、拦截器效果展示3、接口实际接收参数显示4、返回结果5、数据库展示6、入参说明{encrypted:m5Z8vwV7JTE0l1qQ5Ha7/IMnjdVTU4vWTqsgYXby77m1yXVsAF1k3ECpfjtHV119vePPtZdVuf/ib6wU8kj6uGgdS7f8dIr0f8mscHjsqS2/n0iSTYjk/m5oMnDk89}这里是后端直接生成的加密数据前端相同规则的话生成出来的密文和后端生成的密文相同红色参数部分明文为3、部分明文部分加密1、请求参数截图encrypted 参数说明示例如下图所示红色框框区域则为被加密的明文json字符串数据2、明密混搭请求数据结构3、拦截器解密后组装后数据4、实际接口接收到参数已对帐号密码解密完成并绑定赋值给对象5、请求结果3、Get请求示例1、无任何加密数据传参1、请求参数2、接口接收参数2、纯加密请求1、请求参数2、接口接收参数3、部分明文部分加密1、请求参数2、接口接收参数4、前端加密说明1、前端所有加密传递的信息key为encryptedvalue:加密后密文2、待加密明文必须为json字符串形式不能是其他格式3、返回加密后结果data中的字符串即为加密后密文4、具体前端加密示例可参照本博主另一篇SM4加解密文章中的前端示例源码中的加解密方式

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询