从客户端转为服务端日记(一)
应用场景:
应用A请求应用B为保障数据不被非法篡改我们通常会对数据进行md5加密。
加密算法流程:
1.加入时间戳参数
2.根据字典树对请求的参数(Map)进行冒泡排序。
3.对数据进行格式化==> A=a&B=b×tamp=121364565。
4.对格式化后的参数进行加密并加在格式化参数的末尾
验证算法流程
1.取出Map中的sigin
2.直接将map格式化并加密。比较md5是否一致
我将这个类用于拦截器中。当配置的请求非法时自动返回错误信息。
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class SignedRequestHelperTest {
private static final String UTF8 = “UTF-8”;
private static final String HMAC_SHA256 = “HmacSHA256”;
private String secretKey = “myscretKey”;
private Mac mac;
private String urlParams;
public SignedRequestHelperTest(){
init();
}
private void init() {
try {
byte[] secretyKeyBytes = secretKey.getBytes(UTF8);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256);
mac = Mac.getInstance(HMAC_SHA256);
mac.init(secretKeySpec);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
*
* @param params
* @return 没有urlecode的签名 params中sign是urlecode的签名
*/
public String sign(Map params) {
if (!params.containsKey(“timestamp”)) {
params.put(“timestamp”, timestamp());
}
SortedMap sortedParamMap = new TreeMap(params);
String canonicalQS = canonicalize(sortedParamMap);
String toSign = canonicalQS;
System.out.println(toSign);
String hmac = hmac(toSign);
String signed = percentEncodeRfc3986(hmac);
params.put(“sign”, signed);
sortedParamMap = new TreeMap(params);
this.setUrlParams(canonicalize(sortedParamMap));
return signed;
}
public boolean validSign(Map params) {
if (params.containsKey(“sign”)) {
String sign1 = params.get(“sign”);
params.remove(“sign”);
String sign2 = sign(params);
//System.out.println(sign1);
//System.out.println(sign2);
if (sign2.equals(sign1)) {
return true;
}
}
return false;
}
private String hmac(String stringToSign) {
String signature = null;
byte[] data;
byte[] rawHmac;
try {
data = stringToSign.getBytes(UTF8);
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(UTF8 + ” is unsupported!”, e);
}
return signature;
}
/**
* @return
*/
private String timestamp() {
return System.currentTimeMillis() + “”;
}
/**
* @param sortedParamMap
* @return
*/
private String canonicalize(SortedMap sortedParamMap) {
if (sortedParamMap.isEmpty()) {
return “”;
}
StringBuffer buffer = new StringBuffer();
Iterator> iter = sortedParamMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry kvpair = iter.next();
buffer.append(percentEncodeRfc3986(kvpair.getKey()));
buffer.append(“=”);
buffer.append(percentEncodeRfc3986(kvpair.getValue()));
if (iter.hasNext()) {
buffer.append(“&”);
}
}
String canonical = buffer.toString();
return canonical;
}
/**
* Rfc3986
* 此处建议使用spring的encodeUri方法
* http://docs.spring.io/spring/docs/4.0.x/javadoc-api/org/springframework/
* web/util/UriUtils.html
*
* @param s
* @return
*/
private String percentEncodeRfc3986(String s) {
String out;
try {
out = URLEncoder.encode(s, UTF8).replace(“+”, “%20”).replace(“*”, “%2A”).replace(“%7E”, “~”);
} catch (UnsupportedEncodingException e) {
out = s;
}
return out;
}
public String getUrlParams() {
return urlParams;
}
public void setUrlParams(String urlParams) {
this.urlParams = urlParams;
}
public static void main(String[] args) throws Exception {
SignedRequestHelperTest signReqHelper = new SignedRequestHelperTest();
Map params = new HashMap();
params.put(“uname”, “username”);
params.put(“passwd”, “password”);
params.put(“a”, “a”);
System.out.println(signReqHelper.sign(params));
System.out.println(signReqHelper.validSign(params));
}
}
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fh.controller.base.BaseController;
import com.fh.util.SignedRequestHelper;
public class MD5Filter extends BaseController implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
logBefore(logger, “验证签名开始”);
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
Enumeration e = (Enumeration) request.getParameterNames();
Map map = new HashMap();
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
map.put(key, request.getParameter(key));
}
try {
SignedRequestHelper sign = new SignedRequestHelper();
System.out.println(sign.validSign(map));
if(sign.validSign(map)){
chain.doFilter(req, res); // 调用下一过滤器
return;
}
} catch (Exception e2) {
// TODO: handle exception
logBefore(logger, “验证签名异常”);
}
response.setHeader(“Content-type”, “text/html;charset=UTF-8”);
response.setCharacterEncoding(“utf-8”);
PrintWriter out = response.getWriter();
String reString = “{\”errcode\”:10003,\”data\”:{},\”errmsg\”:\”签名错误\”}”;
out.write(reString);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}