简介
本文实现接口加密签名及校验。
可适用于绝大多数系统间接口调用。
签名实现
请求签名实现过程
- 对请求参数名进行升序排序,排序之后生成请求参数字符串queryStr(拼接时要对参数值进行URLEncoder.encode编码,防止中文等问题),形如:参数a=xx&参数b=22。
- 系统间约定加密字符串key ,生成当前时间戳time(毫秒数),获取当前用户编号 uid(注意uid key time 这三个参数均不参与排序)
- 将步骤1中生成的queryStr 和 time 、key 、uid 进行拼接,形成待加密字符串str。形如 queryStr &time=xx&key=xxx&uid=xxx
- 对待加密字符串str进行MD5加密,并转化成大写,即生成签名字符串hash。
- 客户端将uid放到请求header中,将原始请求参数和time、hash一起作参数传递。
校验请求签名过程
- 从请求header中获取用户编号uid
- 从请求参数里获取签名字符串hash,以及请求时间time
- 再将请求参数(除去hash和time)进行一遍签名加密,生成出来正确的签名字符串hash2
- 比较hash和hash2即可
代码实现
public class FkSignUtil {
public static final String UID = "uid";
/**
* 加密秘钥
*/
private static final String KEY = "key可以自定义";
/**
* 日志对象
*/
private static Logger logger = LoggerFactory.getLogger(FkSignUtil.class);
/**
* 生成签名【注意发送请求时一定要带上time 和hash 这2个参数】
*
* 功能:将一个Map按照Key字母升序构成一个QueryString. 并且加入时间混淆的hash串
* @param queryMap query内容
* @param time 加密时候,为当前时间;解密时,为从querystring得到的时间;
* @param uid 表示当前用户id
* @return
*/
public static String createSign(Map<String, Object> queryMap,long time, String uid) {
if(null == uid || "".equals(uid)){
return null ;
}
String qs = sortQueryParamString(queryMap);
if (qs == null) {
return null;
}
String hash = MD5Util.MD5(String.format("%s&time=%d&key=%s&uid=%s", qs, time , KEY ,uid));
hash = hash.toUpperCase();
return hash;
// String params = String.format("%s&time=%d&hash=%s", qs, time, hash);
// return params;
}
/**
* 对请求参数进行排序
* @param params 请求参数 。注意请求参数中不能包含uid、time【这2参数是排序之后拼接的】
* @return
*/
private static String sortQueryParamString(Map<String,Object> params) {
List<String> listKeys = Lists.newArrayList( params.keySet());
Collections.sort(listKeys);
StringBuilder content = new StringBuilder();
for(String param : listKeys){
try {
content.append(param).append("=").append(URLEncoder.encode(params.get(param).toString(),"UTF-8")).append("&");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
if(content.length()> 0){
return content.substring(0 ,content.length() -1 );
}
return content.toString();
}
/**
* 解密判断是否签名正确
* @param params 请求参数map【从request的参数中获取】
* @param uid 表示当前用户id【从header中获取】
* @return
*/
public static boolean checkHashSign(Map<String, Object> params,String uid) {
if(null == uid || "".equals(uid)){
if (logger.isInfoEnabled()) {
logger.info("checkHashSign ERROR: uid is null.");
}
return false ;
}
if (!params.containsKey("hash") || !params.containsKey("time") ) {
if (logger.isInfoEnabled()) {
logger.info("checkHashSign ERROR: hash or time is null.");
}
return false;
}
String hash = (String) params.remove("hash");
Long time =Long.parseLong((String) params.remove("time"));
String signHash = createSign(params, time, uid);
return hash.equals(signHash);
}
public static void main(String[] args) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("zhangId", 12321);
params.put("guanId", true);
params.put("test", "这是11AVC");
params.put("name", "是的商家");
String uid = "asdsa";
long time = System.currentTimeMillis();
String hash = createSign(params, time, uid);
logger.info("hash={} ,time={}", hash, time);
params.put("hash", hash);
params.put("time", time);
boolean flag = checkHashSign(params, uid);
logger.info("校验flag={} ", flag);
}
}
优化方案
可以考虑使用token缓存,免加密解密校验。
同时也可以校验time是否不是在有效期内,比如判断time是不是在当前时间的前2分钟之内,防止接口扩散重复调用。
https安全升级
利用阿里云申请 Symantec 免费版 SSL 证书。
在nginx上添加ssl证书到/etc/nginx/ssl.conf文件夹下面。
配置示例:
# 以下属性中以ssl开头的属性代表与证书配置有关,其他属性请根据自己的需要进行配置。
server {
listen 443;
server_name localhost; # localhost修改为您证书绑定的域名。
ssl on; #设置为on启用SSL功能。
root html;
index index.html index.htm;
ssl_certificate cert/domain name.pem; #将domain name.pem替换成您证书的文件名。
ssl_certificate_key cert/domain name.key; #将domain name.key替换成您证书的密钥文件名。
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #使用此加密套件。
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #使用该协议进行配置。
ssl_prefer_server_ciphers on;
location / {
root html; #站点目录。
index index.html index.htm;
}
}
如无法访问请检查nginx所在机器的443端口是否打开
网友评论