前言
在公司很多接口项目中,都要会有一些签名认证,签名认证的目的很简单,就是为了保护接口,不让别人调用,可能很多初学者没有对签名认证的一个概念,我现在大致可以讲一下签名认证的概念。
如果你写的接口没有签名认证,那么无论是谁都可以进行调用,只要url路劲是对的就行了,参数也没有限制,那么这个时候,签名认证就出现了,sign也是会通过参数进行传递,sign的规则可以随便定义,我现在的规则是这样子的
url=http//xxx/xxx?username=1&pwd=2
那么在前台进行传迪的sign就要按顺序进行拼接参数
sign = MD5(“username=1&pwd=2”+secret); 这个secret叫做密钥,后端和前端要进行商量,可以是随便的一个字符串,但是一定不能泄露出去,不然你的签名就可以进行伪造了。
后端会写一个拦截器,来拦截你的请求,以同样的方式接受你的参数进行拼接然后加上密钥进行加密,跟你的sign进行比较,如果是一样的,那就是合格的请求。
ok,言归正传,还是要进行对jfinal框架进行签名验证的,其实主就是如何编写拦截器,当时个人也一直很纳闷,因为jfinal框架添加自定义拦截器是要实现com.jfinal.aop.Interceptor 这个接口的,但是只有public void intercept(Invocation invocation)这个方法,里面没有我们要的request,没有办法进行参数的接收统计呀,到后面发现一个文档里面有一个方法,可以得到request。
invocation.getController().getRequest() ,重点就在此方法,ok,下面就是我自定义的拦截器,验证签名拦截器。
package com.atuinfo.Interceptors;
import com.alibaba.fastjson.JSON;
import com.atuinfo.core.Result;
import com.atuinfo.core.ResultCode;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SignInterceptor implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(SignInterceptor.class);
@Override
public void intercept(Invocation invocation) {
HttpServletRequest request = invocation.getController().getRequest();//获得request
boolean pass = validateSign(invocation.getController().getRequest());
System.out.println(pass);
if(!pass){
logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
Result result = new Result();
result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败");
invocation.getController().renderJson(result);
}
//System.out.println("进行sign校验");
else{
invocation.invoke();
}
}
/**
* 一个简单的签名认证,规则:
* 1. 将请求参数按ascii码排序
* 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
* 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
*/
private boolean validateSign(HttpServletRequest request) {
String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
if (StringUtils.isEmpty(requestSign)) {
return false;
}
List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
keys.remove("sign");//排除sign参数
Collections.sort(keys);//排序
StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
}
String linkString = sb.toString();
linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'
//获取密钥
Prop p = PropKit.use("demo-config-dev.txt").appendIfExists("atuinfo-config-pro.txt");
String secret = p.get("secret");//密钥
String ls = linkString + secret;
String sign = DigestUtils.md5DigestAsHex(ls.getBytes());//混合密钥md5
System.out.println("sign:"+sign);
return StringUtils.equals(sign, requestSign);//比较
}
/**
* 获取ip地址的方法
* @param request
* @return
*/
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 如果是多级代理,那么取第一个ip为客户端ip
if (ip != null && ip.indexOf(",") != -1) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
return ip;
}
}
怎么起作用呢,肯定要添加到配置文件里面去呀
ok这就配置成功了,我们来看看结果吧。
这是失败的例子
这是成功的例子