springboot aop配合反射统一签名验证实践

作者:ysk_xh_521 时间:2023-08-04 08:14:07 

目录
  • aop配合反射统一签名验证

  • 接口统一签名校验

    • 第一种aop 方式实现

    • 第二种 *

aop配合反射统一签名验证

直接上代码,作为记录。

CheckSignAspect.java


@Aspect   //定义一个切面
@Configuration
@Log4j2
public class CheckSignAspect {
   // 定义切点Pointcut
   @Pointcut("execution(* com.lsj.xxl.controller.*.*CheckSign(..))")
   public void excudeService() {
   }
   @Around("excudeService()")
   public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
       String class_name = pjp.getTarget().getClass().getName();
       String method_name = pjp.getSignature().getName();
       String[] paramNames = getFieldsName(class_name, method_name);
       Object[] method_args = pjp.getArgs();
       SortedMap<String, String> map = logParam(paramNames, method_args);
       if (map != null) {
           String sign = map.get("sign").toUpperCase();
           map.remove("sign");
           String realSign = SignUtil.createSign("utf8", map);
           if (!realSign.equals(sign)) {
               return "签名校验错误";
           }
       }
       Object result = pjp.proceed();
       return result;
   }
   /**
    * 使用javassist来获取方法参数名称
    *
    * @param class_name  类名
    * @param method_name 方法名
    * @return
    * @throws Exception
    */
   private String[] getFieldsName(String class_name, String method_name) throws Exception {
       Class<?> clazz = Class.forName(class_name);
       String clazz_name = clazz.getName();
       ClassPool pool = ClassPool.getDefault();
       ClassClassPath classPath = new ClassClassPath(clazz);
       pool.insertClassPath(classPath);
       CtClass ctClass = pool.get(clazz_name);
       CtMethod ctMethod = ctClass.getDeclaredMethod(method_name);
       MethodInfo methodInfo = ctMethod.getMethodInfo();
       CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
       LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
       if (attr == null) {
           return null;
       }
       String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
       int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
       for (int i = 0; i < paramsArgsName.length; i++) {
           paramsArgsName[i] = attr.variableName(i + pos);
       }
       return paramsArgsName;
   }
   /**
    * 打印方法参数值  基本类型直接打印,非基本类型需要重写toString方法
    *
    * @param paramsArgsName  方法参数名数组
    * @param paramsArgsValue 方法参数值数组
    */
   private SortedMap<String, String> logParam(String[] paramsArgsName, Object[] paramsArgsValue) {
       Map<String, String> map = new HashMap();
       if (ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)) {
           log.info("该方法没有参数");
           return null;
       }
//        StringBuffer buffer = new StringBuffer();
       for (int i = 0; i < paramsArgsName.length; i++) {
           //参数名
           String name = paramsArgsName[i];
           //参数值
           Object value = paramsArgsValue[i];
//            if ("sign".equals(name)){
//                continue;
//            }
           if (isPrimite(value.getClass())) {
               map.put(name, String.valueOf(value));
           } else {
               map.put(name, value.toString());
           }
       }
       return new TreeMap<>(map);
   }
   /**
    * 判断是否为基本类型:包括String
    *
    * @param clazz clazz
    * @return true:是;     false:不是
    */
   private boolean isPrimite(Class<?> clazz) {
       if (clazz.isPrimitive() || clazz == String.class) {
           return true;
       } else {
           return false;
       }
   }
}

SignUtil.java


public class SignUtil {
   public static String createSign(String characterEncoding, SortedMap<String, String> parameters) {
       StringBuffer sb = new StringBuffer();
       for (Map.Entry<String, String> entry : parameters.entrySet()) {
           if (!Strings.isNullOrEmpty(entry.getValue()) && !"sign".equals(entry.getKey()) && !"key".equals(entry.getKey())) {
               sb.append(entry.getKey() + "=" + entry.getValue() + "&");
           }
       }
       String s = sb.toString();
       if (s.length() > 0) {
           s = s.substring(0, sb.toString().length() - 1);
       }
       System.out.println("待加密字符串:" + s);
       String sign = MD5Util.MD5Encode(s, characterEncoding).toUpperCase();
       return sign;
   }
}


测试


@PostMapping("test1")
   public String bbCheckSign( @RequestParam("value") String value,
                           @RequestParam("bb")String bb,
                           @RequestParam("sign")String sign){
       return "ok";
   }


补充:

上述方式也可换为注解实现,具体修改代码如下:

添加注解


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME )
public @interface CheckSign {
}


改变切点


@Pointcut("@annotation(com.lsj.xxl.annotation.CheckSign)")

接口统一签名校验

实现接口请求签名校验,时间戳判断,响应数据返回签名等内容。

这个签名校验,和返回签名可以用多种方法实现。

第一种aop 方式实现

springboot aop配合反射统一签名验证实践

自定义注解体


/**
* @author xxx
*/
@Retention(value = RetentionPolicy.RUNTIME)
public @interface SignatureValidation {
}

aop实现


/**
* @author xxx
*/
@Aspect
@Component
public class SignatureValidation {
   /**
    * 时间戳请求最小限制(600s)
    * 设置的越小,安全系数越高,但是要注意一定的容错性
    */
   private static final long MAX_REQUEST = 10 * 60 * 1000L;
   /**
    * 秘钥
    */
   private static final String SECRET= "test";
   /**
    * 验签切点(完整的找到设置的文件地址)
    */
   @Pointcut("execution(@com.xx.xxx.xxxxx.aop.SignatureValidation * *(..))")
   private void verifyUserKey() {
   }
   /**
    * 获取请求数据,并校验签名数据
    */
   @Before("verifyUserKey()")
   public void doBasicProfiling(JoinPoint point) {
       ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
       HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
       String sign = "" ;
       String timestamp = "" ;
       String version = "";
       SortedMap<String,String> sortedMap = new TreeMap<String,String>();
       for ( Object obj  :point.getArgs()) {
           JSONObject jsonObject =JSONUtil.parseObj(obj);
           if( !StrUtil.isEmptyIfStr(jsonObject.get("sign"))){
               sign=jsonObject.get("sign").toString();
           }
           if(!StrUtil.isEmptyIfStr(jsonObject.get("timestamp"))){
               timestamp=jsonObject.get("timestamp").toString();
               sortedMap.put("timestamp", timestamp);
           }
           if(!StrUtil.isEmptyIfStr(jsonObject.get("version"))){
               version=jsonObject.get("version").toString();
               sortedMap.put("version", version);
           }
           if(!StrUtil.isEmptyIfStr(jsonObject.get("data"))){
               String dataStr= jsonObject.get("data").toString();
               if(!JSONUtil.isJsonObj(dataStr)) {
                   if(JSONUtil.isJsonArray(dataStr)){
                       sortedMap.put("data", JSONUtil.parseArray(dataStr).toString());
                   }
                   sortedMap.put("data", dataStr);
               }
               else
               {
                   JSONObject dataJson= JSONUtil.parseObj(dataStr);
                   @SuppressWarnings("unchecked")
                   Set<String> keySet = dataJson.keySet();
                   String key = "";
                   Object value = null;
                   // 遍历json数据,添加到SortedMap对象
                   for (Iterator<String> iterator = keySet.iterator(); iterator.hasNext();) {
                       key = iterator.next();
                       value = dataJson.get(key);
                       String  valueStr="";
                       if(!StrUtil.isEmptyIfStr(value)){
                           valueStr=value.toString();
                       }
                       sortedMap.put(key, valueStr);
                   }
               }
           }
       }
       if (StrUtil.isEmptyIfStr(sign)) {
           throw new CustomException(BaseResultInfoEnum.ERROR_MISSING_SIGN_1009.getMsg(),BaseResultInfoEnum.ERROR_MISSING_SIGN_1009.getCode());
       }
       //新的签名
       String newSign = createSign(sortedMap,SECRET);
           if (!newSign.equals(sign.toUpperCase())) {
               throw new CustomException(BaseResultInfoEnum.ERROR_SIGN_1010.getMsg(),BaseResultInfoEnum.ERROR_SIGN_1010.getCode());
           }
   }
   /**
    *
    * @param point
    * @param responseObject 返回参数
    */
   @AfterReturning(pointcut="verifyUserKey()",returning="responseObject")
   public void afterReturning(JoinPoint point,Object responseObject) {
       HttpServletResponse response=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
       if(responseObject instanceof ResponseModel){
           ResponseModel responseModel= (ResponseModel) responseObject;
           responseModel.setTimestamp(System.currentTimeMillis());
           responseModel.setVersion(0);
           String sign= Md5Utils.createSign(Md5Utils.createParameters(responseModel),SECRET);
           responseModel.setSign(sign);
       }
   }
}

md5签名


/**
* @author xxx
*/
public class Md5Utils {
   /**
    * 生成签名
    * @param parameters
    * @param key 商户ID
    * @return
    */
   public static String createSign(SortedMap<String,String> parameters, String key){
       StringBuffer sb = new StringBuffer();
       Set es = parameters.entrySet();
       Iterator it = es.iterator();
       while(it.hasNext()) {
           Map.Entry entry = (Map.Entry)it.next();
           String k = (String)entry.getKey();
           Object v = entry.getValue();
           if(null != v && !"".equals(v)
                   && !"sign".equals(k) && !"key".equals(k)) {
               sb.append(k + "=" + v + "&");
           }
       }
       sb.append("key=" + key);
       String sign = SecureUtil.md5(sb.toString()).toUpperCase();
       return sign;
   }
   /**
    * 签名参数
    * @param responseModel 响应数据签名返回给调用者
    * @return
    */
   public  static SortedMap<String,String> createParameters(ResponseModel responseModel){
       SortedMap<String,String> sortedMap = new TreeMap<String,String>();
       if(responseModel!=null) {
           sortedMap.put("timestamp", Convert.toStr(responseModel.getTimestamp()) );
           sortedMap.put("version", Convert.toStr(responseModel.getVersion()));
           JSONObject json = JSONUtil.parseObj(responseModel, false);
           if(responseModel.getData()!=null) {
               sortedMap.put("data", json.get("data").toString());
           }
       }
       return sortedMap;
   }
}

使用,在控制中的方法上方注解即可

springboot aop配合反射统一签名验证实践

第二种 *

这里只做了时间判断,签名校验可以根据需要修改即可实现。

springboot aop配合反射统一签名验证实践

过滤器


import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author xx
*/
@WebFilter(urlPatterns = "/*",filterName = "channelFilter")
public class ChannelFilter implements Filter {
   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
   }
   @Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
       ServletRequest requestWrapper = null;
       if(servletRequest instanceof HttpServletRequest) {
           requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
       }
       if(requestWrapper == null) {
           filterChain.doFilter(servletRequest, servletResponse);
       } else {
           filterChain.doFilter(requestWrapper, servletResponse);
       }
   }
   @Override
   public void destroy() {
   }
}

* 配置


import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author xxxx
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer{
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(getHandlerInterceptor());
   }
   @Override
   public void addCorsMappings(CorsRegistry registry) {
       registry.addMapping("/**")
               .allowedHeaders("Content-Type", "x-requested-with", "X-Custom-Header")
               .allowedMethods("PUT", "POST", "GET", "DELETE", "OPTIONS")
               .allowedOrigins("*")
               .allowCredentials(true);
   }
   @Bean
   public FilterRegistrationBean repeatedlyReadFilter() {
       FilterRegistrationBean registration = new FilterRegistrationBean();
       ChannelFilter repeatedlyReadFilter = new ChannelFilter();
       registration.setFilter(repeatedlyReadFilter);
       registration.addUrlPatterns("/*");
       return registration;
   }
   @Bean
   public HandlerInterceptor getHandlerInterceptor() {
       return new TimestampInterceptor();
   }
}

RequestWrapper 请求流重写处理


import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* @author xxx
*/
public class RequestWrapper extends HttpServletRequestWrapper {
   private final String body;
   public RequestWrapper(HttpServletRequest request) {
       super(request);
       StringBuilder stringBuilder = new StringBuilder();
       BufferedReader bufferedReader = null;
       InputStream inputStream = null;
       try {
           inputStream = request.getInputStream();
           if (inputStream != null) {
               bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
               char[] charBuffer = new char[128];
               int bytesRead = -1;
               while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                   stringBuilder.append(charBuffer, 0, bytesRead);
               }
           } else {
               stringBuilder.append("");
           }
       } catch (IOException ex) {
       } finally {
           if (inputStream != null) {
               try {
                   inputStream.close();
               }
               catch (IOException e) {
                   e.printStackTrace();
               }
           }
           if (bufferedReader != null) {
               try {
                   bufferedReader.close();
               }
               catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }
       body = stringBuilder.toString();
   }
   @Override
   public ServletInputStream getInputStream() throws IOException {
       final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
       ServletInputStream servletInputStream = new ServletInputStream() {
           @Override
           public boolean isFinished() {
               return false;
           }
           @Override
           public boolean isReady() {
               return false;
           }
           @Override
           public void setReadListener(ReadListener readListener) {
           }
           @Override
           public int read() throws IOException {
               return byteArrayInputStream.read();
           }
       };
       return servletInputStream;
   }
   @Override
   public BufferedReader getReader() throws IOException {
       return new BufferedReader(new InputStreamReader(this.getInputStream()));
   }
   public String getBody() {
       return this.body;
   }
}

* 这里可以实现请求来的签名处理,这里只处理时间了


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import xxx.xxx.xxx.common.result.BaseResultInfoEnum;
import xxx.xxx.common.core.exception.CustomException;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author xxx
*/
public class TimestampInterceptor implements HandlerInterceptor {
   /**
    * 时间戳请求最小限制(600s)
    * 设置的越小,安全系数越高,但是要注意一定的容错性
    */
   private static final long MAX_REQUEST = 10 * 60 * 1000L;
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       if (handler instanceof HandlerMethod) {
           HandlerMethod handlerMethod = (HandlerMethod) handler;
           if (handlerMethod.getBean() instanceof BasicErrorController) {
               return true;
           }
//            if ("GET".equals(request.getMethod())) {
//                return true;
//            }
           ValidateResponse validateResponse = new ValidateResponse(true, null);
           RequestWrapper myRequestWrapper = new RequestWrapper((HttpServletRequest) request);
           validateResponse= checkTimestamp(myRequestWrapper.getBody());
           if (!validateResponse.isValidate()) {
               throw validateResponse.getException();
           }
       }
       return true;
   }
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
   }
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
   }
   private  ValidateResponse  checkTimestamp(String requestBody) {
       try {
           JSONObject jsonObject = JSONUtil.parseObj(requestBody);
           String timestamp = "" ;
               if(!StrUtil.isEmptyIfStr(jsonObject.get("timestamp"))) {
                   timestamp=jsonObject.get("timestamp").toString();
               }
           if (StrUtil.isEmptyIfStr(timestamp)) {
               return new ValidateResponse(false, new CustomException(BaseResultInfoEnum.ERROR_MISSING_TIMESTAMP_1007.getMsg(), BaseResultInfoEnum.ERROR_MISSING_TIMESTAMP_1007.getCode()));
           }
           long now = System.currentTimeMillis();
           long time = Long.parseLong(timestamp);
           if (now - time > MAX_REQUEST) {
               return new ValidateResponse(false, new CustomException(BaseResultInfoEnum.ERROR_TIMESTAMP_TIMEOUT_1008.getMsg(), BaseResultInfoEnum.ERROR_TIMESTAMP_TIMEOUT_1008.getCode()));
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
       return new ValidateResponse(true, null);
   }
   /**
    * 校验返回对象
    */
   private static class ValidateResponse {
       private boolean validate;
       private CustomException exception;
       public ValidateResponse(boolean validate, CustomException exception) {
           this.validate = validate;
           this.exception = exception;
       }
       public boolean isValidate() {
           return validate;
       }
       public Exception getException() {
           return exception;
       }
   }
}

返回给前端(或其他平台的)处理类


import comzzzz.xx.common.pojo.PageResponseModel;
import com.zzz.xxxx.common.pojo.ResponseModel;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @author sunrh
*/
@ControllerAdvice
public class ResponseBodyTimestamp implements ResponseBodyAdvice {
   @Override
   public boolean supports(MethodParameter methodParameter, Class aClass) {
       return true;
   }
   @Override
   public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
//就是这里处理返回签名数据
       if(o instanceof ResponseModel){
           ResponseModel responseModel= (ResponseModel) o;
           responseModel.setTimestamp(System.currentTimeMillis());
           return responseModel;
       }
       if(o instanceof PageResponseModel){
           PageResponseModel responseModel= (PageResponseModel) o;
           responseModel.setTimestamp(System.currentTimeMillis());
           return responseModel;
       }
       return o;
   }
}

最终实现的效果图

springboot aop配合反射统一签名验证实践

来源:https://blog.csdn.net/ysk_xh_521/article/details/80354544

标签:springboot,aop,反射,签名验证
0
投稿

猜你喜欢

  • Windows下搭建Flutter开发环境

    2023-11-06 01:32:52
  • java 保留两位小数的几种方法

    2022-05-11 16:38:27
  • 实现Android studio设置自动导包及自动导包快捷键

    2022-03-22 00:05:26
  • C++实现的求解多元一次方程示例

    2022-09-26 19:45:47
  • 简单实现安卓里百度地图持续定位

    2023-07-29 07:59:22
  • iOS新浪微博、腾讯微博分享功能实例

    2023-06-16 09:15:53
  • Android下拉刷新ListView——RTPullListView(demo)

    2022-10-11 12:03:10
  • 简单了解java自定义和自然排序

    2022-01-29 02:47:26
  • Java如何把数组转换为ArrayList

    2021-08-14 19:59:45
  • Java编程实现统计一个字符串中各个字符出现次数的方法

    2023-01-24 18:02:20
  • Android实用图文教程之代码混淆、第三方平台加固加密、渠道分发

    2022-06-23 18:12:44
  • SpringBoot框架中Mybatis-plus的简单使用操作汇总

    2022-12-17 19:10:53
  • java学习笔记之DBUtils工具包详解

    2023-07-27 00:25:44
  • mybatisPlus条件构造器常用方法小结

    2023-12-16 07:04:09
  • 基于SpringMVC入门案例及讲解

    2023-04-06 17:34:43
  • 实战分布式医疗挂号通用模块统一返回结果异常日志处理

    2022-01-28 16:31:32
  • Android-App增量更新的使用姿势

    2022-09-22 22:47:38
  • Android实现图片点击放大

    2023-03-19 17:40:59
  • MyBatis3用log4j在控制台输出SQL的方法示例

    2023-07-01 07:32:36
  • Java多线程-线程的同步与锁的问题

    2023-11-29 01:40:12
  • asp之家 软件编程 m.aspxhome.com