从客户端转为服务端日记(一)
应用场景:
应用A请求应用B为保障数据不被非法篡改我们通常会对数据进行md5加密。
加密算法流程:
1.加入时间戳参数
2.根据字典树对请求的参数(Map<String,String>)进行冒泡排序。
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<String, String> params) { if (!params.containsKey("timestamp")) { params.put("timestamp", timestamp()); } SortedMap<String, String> sortedParamMap = new TreeMap<String, String>(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<String, String>(params); this.setUrlParams(canonicalize(sortedParamMap)); return signed; } public boolean validSign(Map<String, String> 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<String, String> sortedParamMap) { if (sortedParamMap.isEmpty()) { return ""; } StringBuffer buffer = new StringBuffer(); Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, String> 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</br> * 此处建议使用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<String, String> params = new HashMap<String, String>(); 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<String, String> 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 } }