目的,在每次請求的時候,對每次傳的參數,進行解析,驗簽,並返回解碼后的參數, 以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;
}
}
