一般在做接口请求的时候为了保证参数的一致性和防止有人利用正确的参数做大量的重复请求,一般都会做签名验证。
在spring cloud的项目一般是在网关,利用过滤器的方式进行验证。
public class SignFilter extends ZuulFilter{
@Override
public Object run() throws ZuulException {
// TODO Auto-generated method stub
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
System.out.println(RestUtil.getRequestParameter(request));
return null;
}
/**
* 判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。
* 实际运用中我们可以利用该函数来指定过滤器的有效范围。
*
* @return
*/
@Override
public boolean shouldFilter() {
// TODO Auto-generated method stub
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//获取请求URL
String url = request.getRequestURI();
System.out.println("请求URL"+url);
//获取basePath
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
//服务名
if(url.startsWith(basePath+"pay/")){
return true;
}
return false;
}
/**
* filter执行顺序,通过数字指定。
* 数字越大,优先级越低。
*
* @return
*/
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}
/**
* 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
* 这里定义为pre,代表会在请求被路由之前执行。
*
* @return
*/
@Override
public String filterType() {
// TODO Auto-generated method stub
return FilterConstants.PRE_TYPE;
}
以下是签名验证的方法。
public boolean validateSign(Map<String, String> paramMap) {
// TODO Auto-generated method stub
String sign = Pub_Tools.getString_TrimZeroLenAsNull(paramMap.get("sign"));
if(sign==null){
return false;
}
//获取AccessKey 下面这部分,可以针对用于做认证,此处暂时忽略
// String AccessKey = Pub_Tools.getString_TrimZeroLenAsNull(paramMap.get("AccessKey"));
// String SecretKey = null;
// if(AccessKey==null){
// return false;
// }else{
// //判断当前AccessKey是否是正确的。通过查询用户信息判断,并查询得到当前用户的SecretKey
// VTS_User vts_User = com_VTSUserDao.getByKey(AccessKey);
// SecretKey = vts_User.getSecretKey();
// if(SecretKey==null){
// return false;
// }
// }
//第一步判断时间戳 timestamp=201808091113
//和服务器时间差值在10分钟以上不予处理
String timestamp = Pub_Tools.getString_TrimZeroLenAsNull(paramMap.get("timestamp"));
//如果没有带时间戳返回
if(timestamp==null){
return false;
}else{
try {
Date clientDate = new SimpleDateFormat("yyyyMMddHHmmss").parse(timestamp);
if((System.currentTimeMillis()-clientDate.getTime())>(10*60*1000L)){//时间差值过大
return false;
}
//第二步获取随机值
String nonce = Pub_Tools.getString_TrimZeroLenAsNull(paramMap.get("nonce"));
if(nonce==null){//非法请求
return false;
}else{//判断请求是否重复攻击
//从redis中获取当前nonce,如果不存在则允许通过,并在redis中存储当前随机数,并设置过期时间为10分钟,单用户区分
// String save_nonce = Pub_Tools.getString_TrimZeroLenAsNull(redisTemplate.opsForValue().get(AccessKey+nonce));
String save_nonce = Pub_Tools.getString_TrimZeroLenAsNull(redisTemplate.opsForValue().get(nonce));
if(save_nonce==null){
// redisTemplate.opsForValue().set(AccessKey+nonce, nonce,10,TimeUnit.MINUTES);
redisTemplate.opsForValue().set(nonce, nonce,10,TimeUnit.MINUTES);
}else{//如果存在,不允许继续请求。
return false;
}
}
/**
* 上述验证通过,开始验证参数是否正确
* 1、按照请求参数名的字母升序排列非空请求参数(包含AccessKey),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA,这其中包含timestamp和nonce
* 2、在stringA最后拼接上Secretkey得到字符串stringSignTemp
* 3、对stringSignTemp进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值。
* 4、比较计算出的sign值和用户传入的sign值是否相等
*/
//排序
Set<String> keySet = paramMap.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals("sign")) {
continue;
}
if (paramMap.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(paramMap.get(k).trim()).append("&");
}
//暂时不需要个人认证
// sb.append("key=").append(SecretKey);
//加密
String server_sign = KeyUtil.MD5(sb.toString()).toUpperCase();
//判断当前签名是否正确
if(server_sign.equals(sign)){
return true;
}
} catch (Exception e) {
return false;
}
}
return false;
}