所有的模块的输入都需要进行数据完整性校验,需要增加如下的额外字段,
名称 |
字段 |
类型 |
是否必须 |
数据校验码 |
sign |
String |
必须 |
所有模块的接口使用Json格式,该校验码的计算方式如下:
将所有输入字段按照ASCII码表进行排序(无需sign字段),然后格式为key=value(例如userId=123456),然后将数值使用字符“&”(半角的&字符)连接,并进行SM3运算,校验码为SM3的运算结果。
为了让包含中文的URL可以使用,需要在SM3计算之前,进行UrlEncode编码
例如:用户登录接口,加密步骤如下
platformId=1&authPassword=417938&challageCode=&userId=1
authPassword=417938&challageCode=&platformId=1&userId=1
在排序完成后在字符串后面拼接上加密KEY:PEOPLESEC2020
authPassword=417938&challageCode=&platformId=1&userId=1PEOPLESEC2020
(例子中的“=”被替换为了“%3D”,“&”被替换为了“%26”)编码后的结果为:
authPassword%3D417938%26challageCode%3D%26platformId%3D1%26userId%3D1PEOPLESEC2020
加密结果为
09a1c02abdf79102b145cd18d855248c8828069ce1a388d0143a2fd3d593bac5
package com.people.util;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import com.google.gson.Gson;
import com.people.domain.QueryCriminalRecordResponseVo;
import com.people.domain.SceneNameVo;
import com.people.domain.SourceDataVo;
import com.people.security.encrypt.Sha256;
import com.people.security.sm3.SM3Utils;
/**
* @author qi_zhou
* @description 校验参数完整性工具类
* @date: 2019-09-20
*/
public class SignUtil {
// private static final String ENCRYPT_KEY = "PEOPLESEC2020";
public static String createSign(String characterEncoding,
SortedMap parameters) {
StringBuffer sb = new StringBuffer();
for (Map.Entry entry : parameters.entrySet()) {
if (!StringUtils.isEmpty(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);
}
String sign = MakeHEX.md5(s).toUpperCase();
return sign;
}
/**
* 根据对象,检查对象SING值是否存在且正确
*
* @param obj
* 需要计算SIGN值的任意对象
* @return 对象SIGN值是否存在且正确
*/
public static boolean checkSign(Object obj,String interfaceKey) {
String[] signRight = calcSign(obj,interfaceKey);
try {
Field field = FieldUtils.getField(obj.getClass(), "sign", true);
if (field == null) {
return false;
}
String sign = (String) FieldUtils.readField(obj, "sign", true);
sign = sign.replace(" ", "");
return StringUtils.equalsIgnoreCase(sign, signRight[0])
|| StringUtils.equalsIgnoreCase(sign, signRight[1]);
} catch (IllegalAccessException e) {
Slf4jLogUtil.error(e.getMessage(), e);
return false;
}
}
/**
* 根据对象,计算SIGN值
*
* @param map
* 需要计算SIGN值的任意对象
* @return SIGN值
*/
public static String[] calcSign(Map map,String ENCRYPT_KEY) {
try {
String paramsStr = toQueryString(map);
// Slf4jLogUtil.info("Request paramsStr : " + paramsStr);
String tempStrFirst = URLEncoder.encode(paramsStr, "UTF-8") + ""
+ ENCRYPT_KEY;
Slf4jLogUtil.debug(tempStrFirst);
// String resultFirst = MakeHEX.md5(tempStrFirst).toLowerCase();
// String resultFirst = Sha256.getSHA256(tempStrFirst);
String resultFirst = SM3Utils.SM3Encrypt(tempStrFirst);
// 修正BUG:空格(“ ”)转换成了(+)
String tempStrSecond = tempStrFirst.replace("+", "%20").replace(
"*", "%2A");
// String resultSecond = MakeHEX.md5(tempStrSecond).toLowerCase();
String resultSecond = SM3Utils.SM3Encrypt(tempStrSecond);
return new String[] { resultFirst, resultSecond };
} catch (Exception e) {
Slf4jLogUtil.error(e.getMessage(), e);
return new String[] { "", "" };
}
}
/**
* 根据对象,计算SIGN值
*
* @param obj
* 需要计算SIGN值的任意对象
* @return sign值
*/
public static String[] calcSign(Object obj,String interfaceKey) {
Map paramsMap = convertToMap(obj);
return calcSign(paramsMap, interfaceKey);
}
/**
* 将obj里面的所有字段获取并按照ASCLL码顺序排序
*
* @param obj
* 等待处理的对象
* @return Map对象
*/
private static Map convertToMap(Object obj) {
Map map = new TreeMap<>();
List fields = FieldUtils.getAllFieldsList(obj.getClass());
for (Field field : fields) {
String fieldName = field.getName();
Object fieldValue = "";
try {
fieldValue = FieldUtils.readField(field, obj, true);
} catch (IllegalAccessException e) {
Slf4jLogUtil.error(e.getMessage(), e);
}
if (fieldValue != null && !StringUtils.equals(fieldName, "sign")) {
map.put(fieldName, fieldValue);
}
}
return map;
}
/**
* 对Map内所有value作utf8编码,拼接返回结果
*
* @param data
* 等待处理的Map对象
* @return 字符串拼接的返回结果
* @throws UnsupportedEncodingException
* 不支持的编码异常
*/
private static String toQueryString(Map, ?> data)
throws UnsupportedEncodingException {
StringBuilder queryString = new StringBuilder();
for (Map.Entry, ?> pair : data.entrySet()) {
queryString.append(pair.getKey()).append("=");
// queryString.append(URLEncoder.encode(String.valueOf(pair.getValue()),
// "UTF-8") + "&");
queryString.append(String.valueOf(pair.getValue())).append("&");
}
if (queryString.length() > 0) {
queryString.deleteCharAt(queryString.length() - 1);
}
return queryString.toString();
}
/**
* 初始化请求参数
*
* @param vo
* @return
*/
public static SortedMap initQueryInfoMap(SceneNameVo vo) {
Map map = new HashMap();
map.put("custNumber", vo.getCustNumber());
map.put("sceneName", vo.getSceneName());
return new TreeMap<>(map);
}
/**
* 初始化请求参数
*
* @param vo
* @return
*/
public static SortedMap initQueryInfoMap(SourceDataVo vo) {
Map map = new HashMap();
map.put("custNumber", vo.getCustNumber());
map.put("requestName", vo.getRequestName());
return new TreeMap<>(map);
}
public static void main(String arg[]) throws Exception {
Gson gson = new Gson();
List
列子:
客户端请求
// 获取客户编号
InterfaceInfoVo selectInfoKey = interfaceInfoDao.selectInfoKey();
// 调用服务端接口
Map params = new HashMap();
params.put("cipherInfo", Long.parseLong(appId));
params.put("queryName", selectInfoKey.getCustNumber());
SortedMap map = new TreeMap();
map.put("cipherInfo", appId);
map.put("queryName", selectInfoKey.getCustNumber());
String[] signRight = SignUtil.calcSign(map,
selectInfoKey.getInterfaceKey());
String msign = signRight[0];
params.put("sign", msign);
String json = gson.toJson(params);
String postParameters = OkHttpUtil.postJsonParams(url + downloadApp, json);
服务端校验
private boolean verifySign(QueryCriminalRecordRequestVo vo,
String interfaceKey) throws Exception {
String[] signRight = SignUtils.calcSign(
initQueryMap(vo.getCipherInfo(), vo.getQueryName()),
interfaceKey);
// 验证签名参数
if (!(StringUtils.equalsIgnoreCase(vo.getSign(), signRight[0]) || StringUtils
.equalsIgnoreCase(vo.getSign(), signRight[1]))) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
private Map initQueryMap(String cipherInfo, String queryName) {
Map map = new HashMap();
map.put("cipherInfo", cipherInfo);
map.put("queryName", queryName);
return new TreeMap<>(map);
}
package com.people.domain;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.NotEmpty;
public class QueryCriminalRecordRequestVo implements Serializable{
@ApiModelProperty(value = "sign")
@NotEmpty(message = "签名信息不能为空")
private String sign;//签名
@ApiModelProperty(value = "cipherInfo")
@NotEmpty(message = "数据集合不能为空")
private String cipherInfo;//密文信息
@NotEmpty(message = "查询名称不能为空")
private String queryName;//密文信息
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getCipherInfo() {
return cipherInfo;
}
public void setCipherInfo(String cipherInfo) {
this.cipherInfo = cipherInfo;
}
public String getQueryName() {
return queryName;
}
public void setQueryName(String queryName) {
this.queryName = queryName;
}
}
服务端校验第二种方法
private Object checkmakeloadKey(String code, String sign) {
ChckCodeSign check = new ChckCodeSign();
check.setCode(code);
check.setSign(sign);
BaseVo baseVo = check;
if (!SignUtil.checkSign(baseVo)) {
return ResponseUtils.map("-101",
i18nUtils.getKey("result.err.sign"));
}
return null;
}
package com.people.domain.finsherman.sign;
import java.io.Serializable;
import com.people.domain.BaseVo;
public class ChckCodeSign extends BaseVo implements Serializable {
private String code;
private String sign;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}
package com.people.domain;
import java.io.Serializable;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* 基类VO
*/
@SuppressWarnings("serial")
public class BaseVo implements Serializable{
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
private String sign; //签名验证
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}
服务端校验第三种方法(切面校验)
package com.people.aspect;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.google.gson.Gson;
import com.people.common.utils.I18nUtils;
import com.people.common.utils.ResponseUtils;
import com.people.common.utils.Slf4jLogUtil;
import com.people.domain.BaseVo;
import com.people.util.SignUtil;
// 定义一个切面
@Configuration
@Aspect
public class CheckSignAspect {
@Autowired
private I18nUtils i18nUtils;
Gson gson = new Gson();
/**
* Controller 接口调用方法切入点
*/
@Pointcut("@annotation(com.people.annotation.CheckSign)")
public void jsonMethodCall() {
// 定义一个pointcut
}
/**
* 调用外部接口时,检查sn和snTime字是否合法
* 若不合法,停止接口调用并且返回错误消息
*/
@Around(value = "jsonMethodCall()")
public Object checkControllerObjectSn(ProceedingJoinPoint pjp)
throws Throwable {
Object[] args = pjp.getArgs();
BaseVo baseVo = toBaseVo(args);
if (null == baseVo || StringUtils.isEmpty(baseVo.getSign())) {
return ResponseUtils.map("-101",
i18nUtils.getKey("result.err.sign"));
}
printRequestInput(baseVo);
Object result = null;
if (!SignUtil.checkSign(baseVo)) {
result = ResponseUtils.map("-101",
i18nUtils.getKey("result.err.sign"));
} else {
result = pjp.proceed();
}
printRequestOutput(baseVo, result);
return result;
}
private BaseVo toBaseVo(Object[] args) {
if (args != null && args.length >= 1 && args[0] != null
&& args[0] instanceof BaseVo) {
return (BaseVo) args[0];
}
return null;
}
private String toRequestSn(BaseVo baseVo) {
if (baseVo == null) {
return "null";
}
return baseVo.getSign();
}
private void printRequestInput(BaseVo baseVo) {
HttpServletRequest request = getRequest();
Map map = new LinkedHashMap<>();
map.put("url", request.getRequestURL().toString());
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
map.put(headerName, request.getHeader(headerName));
}
// Slf4jLogUtil.info("Request " + toRequestSn(baseVo) + " Info : " +
// map);
}
private void printRequestOutput(BaseVo baseVo, Object result) {
// logger.info("Request " + toRequestSn(baseVo) + " Result : " +
// result);
// Slf4jLogUtil.info("Method = " + getRequest().getRequestURI()
// + ", Response Result : " + gson.toJson(result));
}
public HttpServletRequest getRequest() {
if (RequestContextHolder.getRequestAttributes() == null) {
return null;
}
return ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
}
}
然后再请求的方法上加上注解字段@CheckSign
@CheckSign
@ApiOperation("生成主密钥km(主密钥合成)")
@RequestMapping(value = "/makeKMSecretKey", method = RequestMethod.POST)
public Object makeKMSecretKey(@RequestBody MakeKMSecretKeyReqVo req) {
ValidatorUtils.validateEntity(req);
if (Integer.parseInt(req.getType()) != 4
|| !req.getKeyNumber().equals("1"))
return ResponseUtils.map("-131",
i18nUtils.getKey("result.fisherman.numberandtye.error"));
Slf4jLogUtil
.info("Method = /encryCard-api/fishermanjec/makeKMSecretKey, "
+ "Request paramsStr : " + gson.toJson(req));
Object makeKMSecretKey = fishermanService.makeKMSecretKey(req);
Slf4jLogUtil
.info("Method = /encryCard-api/fishermanjec/makeKMSecretKey, "
+ "Response Result : " + gson.toJson(makeKMSecretKey));
return makeKMSecretKey;
}
package com.people.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 参数签名验证注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckSign {
String value() default "";
}
package com.people.domain.finsherman.vo;
import java.io.Serializable;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotEmpty;
import com.people.domain.BaseVo;
public class MakeKMSecretKeyReqVo extends BaseVo implements Serializable {
@ApiModelProperty(value = "type")
@NotEmpty(message = "类型不能为空")
private String type;
@ApiModelProperty(value = "keyNumber")
@NotEmpty(message = "密钥存储编号不能为空")
private String keyNumber;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKeyNumber() {
return keyNumber;
}
public void setKeyNumber(String keyNumber) {
this.keyNumber = keyNumber;
}
}