1. 程式人生 > >java HTTP簽名使用MD5加密程式碼

java HTTP簽名使用MD5加密程式碼

 

工具類



import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

/**
 * @author wangwei
 * @version v1.0.0
 * @description 簽名
 * @date 2019-01-12
 */
@RefreshScope
public class SignUtil {

    private static Logger logger = LoggerFactory.getLogger(SignUtil.class);

    public static SignUtil signUtil;

    // 對外的金鑰
//    @Value("${sign-secret-secret}")
    public String secretkey = "mysecret123456";

    /** 加密金鑰 */
    //@Value("${sign.secret.appkey}")
    public String appkey = "mykey123456";

    // 間隔時間
    //@Value("${sign.timeout}")
    public int timeout = 1 * 30 * 1000;

    /** 加密金鑰 */
    // private final static String APP_KEY = "mykey123456";

    // public final static String SECRET_KEY = "mysecret123456";

    /** 字元編碼 */
    private final static String INPUT_CHARSET = "UTF-8";

//    /** 超時時間 */
//    private final static int TIME_OUT = 1 * 30 * 1000;

    public static SignUtil getInstance() {
        if(signUtil == null) {
            signUtil = new SignUtil();
        }
        return signUtil;
    }

    /**
     * 請求引數Map轉換驗證Map
     * @param requestParams 請求引數Map
     * @param charset 是否要轉utf8編碼
     * @return
     * @throws UnsupportedEncodingException
     */
    public static Map<String,String> toVerifyMap(Map<String, String[]> requestParams, boolean charset) {
        Map<String,String> params = new HashMap<>();
        for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            String[] values = requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
            }
            //亂碼解決,這段程式碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段程式碼轉化
            if(charset)
                valueStr = getContentString(valueStr, INPUT_CHARSET);
            params.put(name, valueStr);
        }
        return params;
    }

    /**
     * 除去陣列中的空值和簽名引數
     * @param sArray 簽名引數組
     * @return 去掉空值與簽名引數後的新簽名引數組
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

    /**
     * 把陣列所有元素排序,並按照“引數=引數值”的模式用“&”字元拼接成字串
     * @param params 需要排序並參與字元拼接的引數組
     * @return 拼接後字串
     */
    public static String createLinkString(Map<String, String> params) {
        return createLinkString(params, false);
    }

    /**
     * 把陣列所有元素排序,並按照“引數=引數值”的模式用“&”字元拼接成字串
     * @param params 需要排序並參與字元拼接的引數組
     * @param encode 是否需要UrlEncode
     * @return 拼接後字串
     */
    public static String createLinkString(Map<String, String> params, boolean encode) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (encode)
                value = urlEncode(value, INPUT_CHARSET);
            if (i == keys.size() - 1) {//拼接時,不包括最後一個&字元
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }

    /**
     * 編碼轉換
     * @param content
     * @param charset
     * @return
     * @throws UnsupportedEncodingException
     */
    private static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5簽名過程中出現錯誤,指定的編碼集不對,您目前指定的編碼集是:" + charset);
        }
    }

    /**
     * 編碼轉換
     * @param content
     * @param charset
     * @return
     */
    private static String getContentString(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return new String(content.getBytes());
        }
        try {
            return new String(content.getBytes("ISO-8859-1"), charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("指定的編碼集不對,您目前指定的編碼集是:" + charset);
        }
    }

    /**
     * URL轉碼
     * @param content
     * @param charset
     * @return
     */
    private static String urlEncode(String content, String charset) {
        try {
            return URLEncoder.encode(content, charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("指定的編碼集不對,您目前指定的編碼集是:" + charset);
        }
    }



    //TODO 簽名
    /**
     * 生成要請求的簽名引數陣列
     * @param sParaTemp 需要簽名的引數Map
     * @return 要請求的簽名引數陣列
     */
    public static Map<String, String> signMap(Map<String, String[]> sParaTemp) {
        //請求引數Map轉換驗證Map,並生成要請求的簽名引數陣列
        return SignUtil.getInstance().sign(toVerifyMap(sParaTemp, false));
    }

    /**
     * 生成要請求的簽名引數陣列
     * @param sParaTemp 需要簽名的引數
     * @return 要請求的簽名引數陣列
     */
    public Map<String, String> sign(Map<String, String> sParaTemp) {
        //時間戳加入簽名引數組中
        sParaTemp.put("timestamp", String.valueOf(System.currentTimeMillis()));
        //除去陣列中的空值和簽名引數
        Map<String, String> sPara = paraFilter(sParaTemp);
        //把陣列所有元素,按照“引數=引數值”的模式用“&”字元拼接成字串
        String prestr = createLinkString(sPara);
        //生成簽名結果
        String mysign = DigestUtils.md5Hex(getContentBytes(prestr + appkey, INPUT_CHARSET));
        //簽名結果加入請求提交引數組中
        sPara.put("sign", mysign);
        return sPara;
    }

    public static String getSignStr(Map<String, String> sParaTemp) {
        return SignUtil.getInstance().sign(sParaTemp).get("sign");
    }



    /**
     * 生成要請求的簽名引數字串“引數=引數值”&連結
     * @param sParaTemp 需要簽名的引數Map
     * @return 請求的簽名引數字串
     */
    public static String signStringMap(Map<String, String[]> sParaTemp) {
        //生成要請求的簽名引數陣列
        Map<String, String> sign = signMap(sParaTemp);
        //生成要請求的簽名引數字串“引數=引數值”&連結
        return createLinkString(sign, true);
    }

    /**
     * 生成要請求的簽名引數字串“引數=引數值”&連結
     * @param sParaTemp 需要簽名的引數
     * @return
     */
    public static String signString(Map<String, String> sParaTemp) {
        //生成要請求的簽名引數陣列
        Map<String, String> sign = SignUtil.getInstance().sign(sParaTemp);
        //生成要請求的簽名引數字串“引數=引數值”&連結
        return createLinkString(sign, true);
    }


    //TODO 驗證簽名
    /**
     * 根據反饋回來的資訊,生成簽名結果
     * @param paramsMap 通知返回來的請求引數Map
     * @return 驗證結果
     */
    public static boolean verifyMap(Map<String, String[]> paramsMap) {
        //請求引數Map轉換驗證Map,並根據反饋回來的資訊,生成簽名結果
        return SignUtil.getInstance().verify(toVerifyMap(paramsMap, false));
    }

    /**
     * 根據反饋回來的資訊,生成簽名結果
     * @param params 通知返回來的引數陣列
     * @return 驗證結果
     */
    public boolean verify(Map<String, String> params) {
        String sign = "";
        if (params.get("sign") != null) {
            sign = params.get("sign");
        }else {
            logger.info("sign is null");
            return false;
        }
        String timestamp = "";
        if (params.get("time") != null) {
            timestamp = params.get("time");
        }else {
            return false;
        }
        //過濾空值、sign
        Map<String, String> sParaNew = paraFilter(params);
        //獲取待簽名字串
        String preSignStr = createLinkString(sParaNew);
        //獲得簽名驗證結果
        String mysign = DigestUtils.md5Hex(getContentBytes(preSignStr + appkey, INPUT_CHARSET));
        if (mysign.equals(sign)) {
            //是否超時
            long curr = System.currentTimeMillis();
            if ((curr - Long.valueOf(timestamp)) > timeout){
                logger.info("api is time out" + curr);
                return false;
            }
            return true;
        } else {
            return false;
        }
    }


}

 

 

介面控制器呼叫


import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author wangwei
 * @version v1.0.0
 * @description 測試簽名用
 * @date 2019-01-12
 */
@RestController
@RequestMapping("/api/open/rest/{version}/signtest")
public class SignTestController {

    /**
     * 模擬客戶端請求API介面
     * @param request
     * @return
     */
    @RequestMapping("send")
    public String send(HttpServletRequest request){
        Map<String,String> param = new HashMap<>();
        param.put("userId","9527");
        param.put("amount","9.99");
        param.put("productId","9885544154");
        param.put("secretKey","mysecret123456");
        try {
            String postResult = HttpClient.get("http://127.0.0.1:8773/api/open/v1.0.1/ad/list", SignUtil.getInstance().sign(param));
            return postResult;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "success";
    }

    /**
     * showdoc
     * @catalog v1.0.1/測試相關
     * @title 模擬服務的API介面
     * @description 模擬服務的API介面
     * @method post
     * @url /api/open/rest/v1.0.1/signtest/checkSign
     * @param secretKey 必選 string 祕鑰
     * @param time 必選 string 請求時間戳
     * @param sign 必選 string 引數MD5簽名
     * @return {"status":"200","message":"請求成功","data":,"page":null,"ext":null}
     * @return_param code int 驗證碼
     * @return_param status string 狀態
     * @remark 這裡是備註資訊
     * @number 99
     */
    @RequestMapping("checkSign")
    public Resp checkSign(HttpServletRequest request){
        //從request中獲取引數列表,轉成map
        Map<String, String> map = SignUtil.toVerifyMap(request.getParameterMap(),false);
        String secretKey =  map.get("secretKey");
        if (StringUtils.isEmpty(secretKey) || !map.get("secretKey").equals(SignUtil.getInstance().secretkey)){
            System.out.println("secretKey is err");
            return Resp.fail(ErrorCode.CODE_431);
        }
        if (SignUtil.getInstance().verify(map)){
            return Resp.success();
        }else {
            return Resp.fail(ErrorCode.CODE_430);
        }
    }

}

 

 

簽名驗證過濾器


import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

/**
 * @author wangwei
 * @version v1.0.0
 * @description 簽名過濾器, 簽名不攔截open下的介面
 * @date 2019-01-12
 */
@Component
@WebFilter(filterName="SignFilter", urlPatterns="/*")
@RefreshScope
public class SignFilter implements Filter {

    @Value("${version}")
    private String version;

    @Override
    public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        String path = request.getRequestURI();

        if(path.indexOf("/api/open/")> -1 ){
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        Map<String, String> map = SignUtil.toVerifyMap(request.getParameterMap(),false);
        String secretKey =  map.get("secretKey");
        if (StringUtils.isEmpty(secretKey) || !map.get("secretKey").equals(SignUtil.getInstance().secretkey)){
            System.out.println("secretKey is err");
            PrintWriter writer = null;
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.setContentType("text/html; charset=utf-8");
            try {
                writer = servletResponse.getWriter();
                String userJson = "{\"code\":\" "+ ErrorCode.CODE_431.getCode() +"\", \"message\": \""+ ErrorCode.CODE_431.getMessage() +"\"}";
                writer.print(userJson);
            } catch (IOException e1) {
            } finally {
                if (writer != null)
                    writer.close();
            }
        }
        if (SignUtil.getInstance().verify(map)){
            // 簽名成功
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }else {
            PrintWriter writer = null;
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.setContentType("text/html; charset=utf-8");
            try {
                writer = servletResponse.getWriter();
                String userJson = "{\"code\":\" "+ ErrorCode.CODE_430.getCode() +"\", \"message\": \""+ ErrorCode.CODE_430.getMessage() +"\"}";
                writer.print(userJson);
            } catch (IOException e1) {
            } finally {
                if (writer != null)
                    writer.close();
            }
        }


    }

    @Override
    public void destroy() {

    }
}