目的,在每次请求的时候,对每次传的参数,进行解析,验签,并返回解码后的参数, 以json传递;
例子背景:
IOT平台提供对外可访问的接口, 需要对所有参数的传递做到 不泄露、认证的目的;所以需要在每次请求的时候:
1、对需要传递的参数进行加密,
byte[] encrypt = encrypt(content, "6AEB57F8DA93860A19514592A154BEF8");
 
 
String hexStr = parseByte2HexStr(encrypt);
System.out.println("加密后的2进制密文:" + hexStr);
2、通过对时间戳、随机数、请求类型、路径和加密后的参数进行加签
String method = "POST";
String timestamp = String.valueOf(System.currentTimeMillis());
System.out.println("timestamp:"+timestamp);
String salt = String.valueOf((int)((Math.random()*9+1)*100000));
System.out.println("salt:"+salt);
String url  ="/iot/open/device/v1/sync";
String sign = generateSign(salt,timestamp,"407af810068111ea95f4852d6a259567",method,url,hexStr, "b645d880068111ea8f09cf8592eb9fbc");
System.out.println("sign:"+sign);
 
/**
* 签名算法
*
* @param salt 随机数
* @param timestamp 时间戳
* @param appKey 应用Key
* @param httpRequestMethod 添加POST
* @param canonicalURI 接口地址
* @param canonicalParameterString 加签内容
*/
public static String generateSign(String salt, String timestamp, String appKey,
    String httpRequestMethod, String canonicalURI, String canonicalParameterString,
    String appSecret) {
    String sign = null;
    try {
        String canonicalHeaders = salt + timestamp + appKey;
        String stringToSign = httpRequestMethod + "\n"
            + canonicalURI + "\n"
            + canonicalParameterString + "\n"
            + canonicalHeaders;
        Mac mac = null;
        mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec sk = new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8),
            "HmacSHA256");
        mac.init(sk);
        sign = Hex
            .encodeHexString(mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
        log.error(
            "生成签名失败,appKey:{},salt:{},timestamp:{},httpRequestMethod:{},canonicalURI:{},canonicalParameterString:{}",
            appKey, salt, timestamp, httpRequestMethod, canonicalURI, canonicalParameterString);
        e.printStackTrace();
    }
    return sign;
}
3、最后发送请求
 
此时,参数是加密的,请求头中包含着约定的 签名、时间戳、随机数、约定的key; 已经对传递的参数做了加密,页身份的加签
 
后面开始 切面编程:
 
4、创建注解:
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SignValidate {
    /**
     * 方法上用于接收接收业务参数字段名称
     *
     * @return 参数名称
     */
    String paramName() default "signParam";
    /**
     * 参数中加签字段的名称
     *
     * @return 加签字段的名称
     */
    String signName() default "parameters";
}
使用的时候
只需要在方法上加上:
@SignValidate
@PostMapping("/v1/sync")
public CommonResponse syncOne(HttpServletRequest request,
    @RequestBody SignParamDTO signParam) {
    log.info("设备同步请求ip为{},参数为{}", WebUtils.getIp(request), signParam.getParameters());
    return CommonResponse.success(deviceSyncService.syncOne(signParam.getParameters()));
}
 
5、开始切面类
HttpServletRequest的功能可以分为以下几种:
封装了请求头数据;
封装了请求正文数据,如果是GET请求,那么就没有正文;
request是一个域对象,可以把它当成Map来添加获取数据;
做请求的转发
package cn.video110.iot.open.aspect;
 
 
import cn.video110.iot.base.errorcode.SignErrorCode;
import cn.video110.iot.open.utils.SignUtil;
import cn.video110.starter.mvc.common.CommonResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
 
/**
* 参数签名验证
* @author 
* @date 2020/07/23
*/
@Slf4j
@Aspect //参数
@Component //组件
public class SignValidateAspect {
 
 
    private static String APP_KEY = "407af810068111ea95f4852d6a259567";
    private static String APP_SECRET = "b645d880068111ea8f09cf8592eb9fbc";
    private static String APP_PASSWORD = "6AEB57F8DA93860A19514592A154BEF8";
    /**
     * header 中验签参数
     */
    private final String[] SIGN_HEADER = new String[]{
        "x-clss-iot-authorization",
        "x-clss-iot-timestamp",
        "x-clss-iot-salt",
        "x-clss-iot-appkey"
    };
    private final String headerAuthorization = "x-clss-iot-authorization";
    private final String headerTimestamp = "x-clss-iot-timestamp";
    private final String headerSalt = "x-clss-iot-salt";
    private final String headerAppKey = "x-clss-iot-appkey";
 
 
    /**
     * 签名验证  表示所有的带有SignValidate的注解
     */
    @Pointcut("@annotation(cn.video110.iot.open.aspect.SignValidate)")
    public void signValidate() {
    }
 
 
    @Around("signValidate()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object response = null;
        ServletRequestAttributes attributes = (ServletRequestAttributes)
            RequestContextHolder.getRequestAttributes();
            // 获取当前的Request的实体
        HttpServletRequest request = attributes.getRequest();
        Map<String, String> signHeader = getSignHeader(request);
        //校验header
        CommonResponse checkResult = checkHeader(signHeader);
        if (!checkResult.isStatus()) {
            return checkResult;
        }
        checkResult = verifySignAndDecrypt(request, signHeader, joinPoint);
        if (!checkResult.isStatus()) {
            return checkResult;
        }
        response = joinPoint.proceed((Object[]) checkResult.getObj());
        return response;
    }
 
 
    /**
     * 获取header签名用的字段
     */
    private Map<String, String> getSignHeader(HttpServletRequest request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, String> headerMap = new HashMap();
        List<String> signList = Arrays.asList(SIGN_HEADER);
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            if (signList.contains(headerName)) {
                String headerValue = request.getHeader(headerName);
                headerMap.put(headerName, headerValue);
            }
        }
        return headerMap;
    }
 
 
    /**
     * 校验header内参数
     *
     * @param signHeader header签名参数
     * @return CommonResponse 校验结果
     */
    private CommonResponse checkHeader(Map<String, String> signHeader) {
        if (!signHeader.containsKey(headerAuthorization) || StringUtils
            .isBlank(signHeader.get(headerAuthorization))) {
            return CommonResponse.error(SignErrorCode.NON_AUTHORIZATION);
        }
        if (!signHeader.containsKey(headerTimestamp) || StringUtils
            .isBlank(signHeader.get(headerTimestamp))) {
            return CommonResponse.error(SignErrorCode.NON_TIMESTAMP);
        }
        if (!signHeader.containsKey(headerSalt) || StringUtils
            .isBlank(signHeader.get(headerSalt))) {
            return CommonResponse.error(SignErrorCode.NON_SALT);
        }
        if (!signHeader.containsKey(headerAppKey) || StringUtils
            .isBlank(signHeader.get(headerAppKey))) {
            return CommonResponse.error(SignErrorCode.NON_APPKEY);
        }
        return CommonResponse.success();
    }
 
 
    private CommonResponse<Object[]> verifySignAndDecrypt(HttpServletRequest request,
        Map<String, String> signHeader,
        ProceedingJoinPoint joinPoint) {
        //获取参数
        Object[] args = joinPoint.getArgs();
        CommonResponse response = null;
            // 获取连接点的方法签名对象;
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] params = methodSignature.getParameterNames();
        if (params.length == 0) {
            log.info("当前切入点方法{}没有需要验签的参数。", methodSignature.getName());
            return CommonResponse.success(args);
        }
        // 获得注解中的信息
        Method method = methodSignature.getMethod();
        SignValidate signValidate = method.getAnnotation(SignValidate.class);
        Integer index = ArrayUtils.indexOf(params, signValidate.paramName());
        if (args[index] != null) {
            String sign = getSign(args[index], signValidate.signName());
            if (StringUtils.isBlank(sign)) {
                return CommonResponse.error(SignErrorCode.NON_PARAM);
            }
            response = verifySign(request, signHeader, sign);
            if (!response.isStatus()) {
                return response;
            }
            args[index] = decryptParam(args[index], signValidate.signName(), APP_PASSWORD);
 
 
        }
        return CommonResponse.success(args);
    }
 
 
 
 
    /**
     * 验签
     *
     * @param request 请求
     * @param signHeader 签名header
     * @param parameters 参数
     * @return 验签结果
     */
    private CommonResponse verifySign(HttpServletRequest request, Map<String, String> signHeader,
        String parameters) {
        //根据appKey查询
        String appSecret = APP_SECRET;
        //验签
        String method = request.getMethod();
        String canonicalURI = request.getServletPath() + request.getPathInfo();
        String salt = signHeader.get(headerSalt);
        String timestamp = signHeader.get(headerTimestamp);
        String appKey = signHeader.get(headerAppKey);
        String sign = SignUtil
            .generateSign(salt, timestamp, appKey, method, canonicalURI, parameters, appSecret);
        if (!Objects.equals(sign, signHeader.get(headerAuthorization))) {
            return CommonResponse.error(SignErrorCode.VERIFY_FAIL);
        }
        return CommonResponse.success();
    }
 
 
    private String getSign(Object signObject, String signName) {
        Class<?> resultClass = signObject.getClass();
        Field[] fieldInfo = resultClass.getDeclaredFields();
        for (Field field : fieldInfo) {
            if (signName.equals(field.getName())) {
                field.setAccessible(true);
                Object fieldValue = null;
                try {
                    fieldValue = field.get(signObject);
                    if (fieldValue == null) {
                        return null;
                    }
                    return fieldValue.toString();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
        return null;
    }
 
 
 
 
    /**
     * 解密参数
     *
     * @param args 入参实体
     * @param signName 签名字段名称
     * @param appPassword 解密密码
     * @return 解密后的参数
     */
    private Object decryptParam(Object args, String signName, String appPassword) {
        Class<?> resultClass = args.getClass();
        Field[] fieldInfo = resultClass.getDeclaredFields();
        for (Field field : fieldInfo) {
            if (signName.equals(field.getName())) {
                field.setAccessible(true);
                Object fieldValue = null;
                try {
                    fieldValue = field.get(args);
                    String decryptValue = SignUtil.decrypt(fieldValue.toString(), appPassword);
                    field.set(args, decryptValue);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
        return args;
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

SpringBoot中使用切面的每次传的参数,进行解析,验签,并返回解码后的参数的更多相关文章

  1. 在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库

    文章目录 1.导入相关的依赖 2.创建要保存的数据信息实体类 3 .编写对应的sql语句 4.使用spring 的 aop 技术切到自定义注解上,所以先创建一个自定义注解类 5. 创建aop切面实现类 ...

  2. 如何让springboot中的某些html文件不经过thymeleaf模板解析?

    这个thymeleaf有时有用,有时用不着. 但默认设置,所有的html都会经过它解析. 我的作法,是新建public,在resource里,所有css,js所放里面.(当然,static下也是OK的 ...

  3. springBoot中使用使用junit测试文件上传,以及文件下载接口编写

    本篇文章将介绍如何使junit在springBoot中测试文件的上传,首先先阅读如何在springBoot中进行接口测试. 文件上传操作测试代码 import org.junit.Before; im ...

  4. Springboot中Aspect实现切面(以记录日志为例)

    前言今天我们来说说spring中的切面Aspect,这是Spring的一大优势.面向切面编程往往让我们的开发更加低耦合,也大大减少了代码量,同时呢让我们更专注于业务模块的开发,把那些与业务无关的东西提 ...

  5. SpringBoot中使用Maven插件,上传docker镜像

    开启docker远程端口 我上一篇里面写了,这里暴露的路径: 18.16.202.95:2375 简单构建 配置pom.xml文件 在properties中增加一行指定远程主机的位置 <prop ...

  6. 由浅入深学习springboot中使用redis

    很多时候,我们会在springboot中配置redis,但是就那么几个配置就配好了,没办法知道为什么,这里就详细的讲解一下 这里假设已经成功创建了一个springboot项目. redis连接工厂类 ...

  7. springboot中使用自定义两级缓存

    工作中用到了springboot的缓存,使用起来挺方便的,直接引入redis或者ehcache这些缓存依赖包和相关缓存的starter依赖包,然后在启动类中加入@EnableCaching注解,然后在 ...

  8. (一)由浅入深学习springboot中使用redis

    很多时候,我们会在springboot中配置redis,但是就那么几个配置就配好了,没办法知道为什么,这里就详细的讲解一下 这里假设已经成功创建了一个springboot项目. redis连接工厂类 ...

  9. SpringBoot图文教程5—SpringBoot 中使用Aop

    有天上飞的概念,就要有落地的实现 概念+代码实现是本文的特点,教程将涵盖完整的图文教程,代码案例 文章结尾配套自测面试题,学完技术自我测试更扎实 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例 ...

随机推荐

  1. requests模块使用

    一.python环境下安装requests Windows下使用win+r打开cmd命令提示符,输入pip install requests,回车. 二.requests模块导入 import req ...

  2. 【java】java获取JVM启动参数 System.getProperty

    java获取JVM启动参数 System.getProperty取 -D后的key即可 public class Test { public static void main(String[] arg ...

  3. 前端路由、后端路由——想要学好vue-router 或者 node.js 必须得明白的两个概念

    前端路由和后端路由的概念讲解 引言 正文 一.路由的概念 二.后端路由 三.前端路由 四.其他知识 结束语 引言 无论你是正在学习vue 还是在学习node, 你一定会碰到前端路由和后端路由这两个概念 ...

  4. 记一次因为Gradle与Lombok不兼容导致编译时的内存溢出 Expiring Daemon because JVM heap space is exhausted

    1.现象 版本 Gradel:6.1.1 / 6.5.1 Lombok:1.8.6 / 1.8.10 截图 解决过程 调大idea的堆内存 不行 × idea安装目录中找到 idea64.exe.vm ...

  5. 操作系统-PV习题

    (1)阅览室问题:加入阅览室入口有一本登记册,每个人都必须按顺序签名进去. 想法:登记册可以用结构数组A[]表示,包含name和number.此外,还需要信号量seatcount表示剩余座位数. 使用 ...

  6. 经典游戏--24点--c++代码实现和总体思路(简单暴力向)

    24点 24点是一个非常经典的游戏,从扑克牌里抽4张牌,其中J=11,Q=12,K=13,然后经过+,-,*,/,(),的计算后,使得计算得值为24,例如抽到1,2,2,5四张牌,那么 (1+5)*( ...

  7. Shell编程—正则表达式

    1什么是正则表达式 1.1定义 正则表达式是你所定义的模式模板,Linux工具可以用它来过滤文本.Linux 工具(比如sed编辑器或gawk程序)能够在处理数据时使用正则表达式对数据进行模式匹配. ...

  8. Linux系统下部署项目流程

    一.系统架构 linux系统 centOS 6.9 应用服务器:Tomcat /JDK 数据库服务器:MySQL 二.连接远程工具FinalShell 1.Centos 6: 启动服务:service ...

  9. JavaScript闭包(内存泄漏、溢出以及内存回收),超直白解析

    1 引言 变量作用域 首先我们先铺垫一个知识点--变量作用域: 变量根据作用域的不同分为两种:全局变量和局部变量. 函数内部可以使用全局变量. 函数外部不可以使用局部变量. 当函数执行完毕,本作用域内 ...

  10. java前端知识点整理

    1.jsp内置对象?作用? request :客户端请求,包含来自 GET/POST,提供了几个用于获取 cookie, 和 session 的方法 response :网页传回用户端的回应 page ...