Java Http介面簽名驗簽執行個體__Java

來源:互聯網
上載者:User
一、業務背景

調用第三方介面或者前後端分離開發,調用業務介面時為防止刷機/違規調用等現象 需要加驗簽機制,比如支付類等介面,介面雙方為了確保資料參數在傳輸過程中未經過篡改,都需要對介面資料進行加簽,然後在介面伺服器端對介面參數進行驗簽,確保兩個簽名是一樣的,驗簽通過之後再進行商務邏輯處理。

二、處理思路

簽名驗簽的大方向無非就是:用戶端與服務端約定好,按照統一演算法,統一參數,統一順序,統一密鑰 進行加密,然後作對比;我這裡就介紹一種比較簡單實用且常用的簽名/驗簽方式思路:
用戶端:
    簽名通過header傳到服務端,key名為:token
      簽名:API版本號碼+AK+簽名到期時間+密鑰(SK)+URL+requestMethod+用戶端已排序的參數 做加密;

        密碼編譯演算法:MD5;
        密鑰(SK): 前後端統一,簽名加密的時候使用相當於加鹽;
        AK : 多用戶端使用不同的SK, 服務端就需要通過AK找到對應的SK進行加密驗簽;
        簽名到期時間:用戶端定義的簽名到期時間,超過該時間該簽章要求不可通過;

    請求參數:已排序的參數通過request-line 或 request-body傳到服務端

服務端:
     同樣的方式進行簽名,驗證。 以確認調用者是否合法,這就是介面簽名驗證的思路。
伺服器端代碼如下(僅供參考):     通過spring攔截器HandlerInterceptor(其內部將request請求body參數轉為line參數,So,直接getParameter)

/**   * @Description:驗簽攔截   * @Author pengju   * @date 2018/4/11 13:44 */public class ApiAkSkInterceptor implements HandlerInterceptor {    private final Logger logger = LoggerFactory.getLogger(ApiAkSkInterceptor.class);    @Autowired    private BaseOpenApiConfig baseOpenApiConfig;    /**     * Intercept the execution of a handler. Called after HandlerMapping determined     * an appropriate handler object, but before HandlerAdapter invokes the handler.     * <p>DispatcherServlet processes a handler in an execution chain, consisting     * of any number of interceptors, with the handler itself at the end.     * With this method, each interceptor can decide to abort the execution chain,     * typically sending a HTTP error or writing a custom response.     * <p><strong>Note:</strong> special considerations apply for asynchronous     * request processing. For more details see     * {@link AsyncHandlerInterceptor}.     *     * @param request  current HTTP request     * @param response current HTTP response     * @param handler  chosen handler to execute, for type and/or instance evaluation     * @return {@code true} if the execution chain should proceed with the     * next interceptor or the handler itself. Else, DispatcherServlet assumes     * that this interceptor has already dealt with the response itself.     * @throws Exception in case of errors     */    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        if (handler == null || !(handler instanceof HandlerMethod)) {            return true;        }        HandlerMethod handlerMethod = (HandlerMethod) handler;        if (handlerMethod.getBeanType().isAnnotationPresent(ApiAkSkNotValidator.class)                || handlerMethod.getMethod().isAnnotationPresent(ApiAkSkNotValidator.class)) {            return true;        }        final long bt = System.currentTimeMillis();        response.addHeader(HTTP.CONTENT_TYPE, "application/json;charset=UTF-8");        try {            // get header token            String requestUri = request.getRequestURI();            final String requestMethod = request.getMethod();            final String token = request.getHeader(TokenHelper.X_IBEE_AUTH_TOKEN);            logger.info("checkApiAkSkToken: check token,request uri={},method={},header token={}", requestUri, requestMethod, token);            // token            if (StringUtils.isBlank(token) || token.split("-").length != 4) {                logger.error("checkApiAkSkToken: token invalid,head key={},token={}", TokenHelper.X_IBEE_AUTH_TOKEN, token);                ResultBean result = new ResultBean(CommonEnum.ResponseEnum.AKSK_ERROR.getCode(), CommonEnum.ResponseEnum.AKSK_ERROR.getMsg(), null);                return BaseOpenApiConfig.ajaxTimeOut(result, response);            }            // check ak value            final String ak = token.split("-")[1];            if (StringUtils.isBlank(ak)) {                logger.error("checkApiAkSkToken: ak is blank,token={}", token);                ResultBean result = new ResultBean(CommonEnum.ResponseEnum.AKSK_ERROR.getCode(), CommonEnum.ResponseEnum.AKSK_ERROR.getMsg(), null);                return BaseOpenApiConfig.ajaxTimeOut(result, response);            }            // get request body            String jsonReqBody = parameter2Json(request);            final String askReqBody = BaseOpenApiConfig.BLANK_JSON.equals(jsonReqBody) ? null : jsonReqBody;            final String queryStr = request.getQueryString();            // check sk value            final String sk = baseOpenApiConfig.getSkValue(ak);            if (StringUtils.isBlank(sk)) {                logger.error("checkApiAkSkToken: sk is blank,ak={}", ak);                ResultBean result = new ResultBean(CommonEnum.ResponseEnum.AKSK_ERROR.getCode(), CommonEnum.ResponseEnum.AKSK_ERROR.getMsg(), null);                return BaseOpenApiConfig.ajaxTimeOut(result, response);            }            // do check            if (!new TokenHelper(ak, sk).verifyToken(token, requestUri, requestMethod, queryStr, askReqBody)) {                logger.error("checkApiAkSkToken: token invalid,do not access,uri={},token={}", requestUri, token);                ResultBean result = new ResultBean(CommonEnum.ResponseEnum.AKSK_ERROR.getCode(), CommonEnum.ResponseEnum.AKSK_ERROR.getMsg(), null);                return BaseOpenApiConfig.ajaxTimeOut(result, response);            }            // set response header            response.addHeader(TokenHelper.X_IBEE_AUTH_TOKEN, token);            return true;        } catch (Exception e) {            logger.error("checkApiAkSkToken: catch e.", e);            ResultBean result = new ResultBean(CommonEnum.ResponseEnum.AKSK_ERROR.getCode(), CommonEnum.ResponseEnum.AKSK_ERROR.getMsg(), null);            return BaseOpenApiConfig.ajaxTimeOut(result, response);        } finally {            logger.debug("checkApiAkSkToken: sn={},end,use time={}ms.", bt, (System.currentTimeMillis() - bt));        }    }    /**     * Intercept the execution of a handler. Called after HandlerAdapter actually     * invoked the handler, but before the DispatcherServlet renders the view.     * Can expose additional model objects to the view via the given ModelAndView.     * <p>DispatcherServlet processes a handler in an execution chain, consisting     * of any number of interceptors, with the handler itself at the end.     * With this method, each interceptor can post-process an execution,     * getting applied in inverse order of the execution chain.     * <p><strong>Note:</strong> special considerations apply for asynchronous     * request processing. For more details see     * {@link AsyncHandlerInterceptor}.     *     * @param request      current HTTP request     * @param response     current HTTP response     * @param handler      handler (or {@link HandlerMethod}) that started asynchronous     *                     execution, for type and/or instance examination     * @param modelAndView the {@code ModelAndView} that the handler returned     *                     (can also be {@code null})     * @throws Exception in case of errors     */    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {    }    /**     * Callback after completion of request processing, that is, after rendering     * the view. Will be called on any outcome of handler execution, thus allows     * for proper resource cleanup.     * <p>Note: Will only be called if this interceptor's {@code preHandle}     * method has successfully completed and returned {@code true}!     * <p>As with the {@code postHandle} method, the method will be invoked on each     * interceptor in the chain in reverse order, so the first interceptor will be     * the last to be invoked.     * <p><strong>Note:</strong> special considerations apply for asynchronous     * request processing. For more details see     * {@link AsyncHandlerInterceptor}.     *     * @param request  current HTTP request     * @param response current HTTP response     * @param handler  handler (or {@link HandlerMethod}) that started asynchronous     *                 execution, for type and/or instance examination     * @param ex       exception thrown on handler execution, if any     * @throws Exception in case of errors     */    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {    }    /**     * 複製一份request map     */    @SuppressWarnings("unchecked")    public static String parameter2Json(HttpServletRequest request) {        LinkedHashMap<String, String> linkMap = new LinkedHashMap<>();        Enumeration<String> enu = request.getParameterNames();        while (enu.hasMoreElements()) {            String paraName = enu.nextElement();            String value = request.getParameter(paraName);            linkMap.put(paraName, value);            System.out.println(paraName + ":" + value);        }        return JSONObject.toJSONString(linkMap);    }}

AKSKConfig:

/**   * @Description:AKSKConfig   * @Author pengju   * @date 2018/4/11 13:32 */@Componentpublic class BaseOpenApiConfig {    private static final Logger logger = LoggerFactory.getLogger(BaseOpenApiConfig.class);    @Value("${api.ak.sk}")    private String KEY_API_AK_SK;    public final static String BLANK_JSON = "{}";    public final static Map<String, String> akSkMap = new ConcurrentHashMap<>();    @PostConstruct    public void initAKSKMap() {        String[] akSksArray = KEY_API_AK_SK.split("#");        if (ArrayUtils.isEmpty(akSksArray)) {            logger.error("initAKSKMap: akSksArray is empty");        }        for (String akSk : akSksArray) {            String[] akSkArray = akSk.split(":");            if (ArrayUtils.isEmpty(akSkArray) && akSkArray.length < 2) {                logger.error("initAKSKMap: akSkArray is error");                continue;            }            String localAk = akSkArray[0];            String localSk = akSkArray[1];            akSkMap.put(localAk, localSk);        }    }    public static String streamToStr(final boolean closeReader, BufferedReader reader) throws IOException {        if (reader == null) {            return null;        }        String inLine;        String str = "";        try {            while ((inLine = reader.readLine()) != null) {                str += inLine;            }            return str;        } finally {            if (closeReader) {                reader.close();            }        }    }    public String getSkValue(final String ak) throws Exception {        if (StringUtils.isBlank(ak)) {            logger.error("getSkValue: ak is blank");            return null;        }        return akSkMap.get(ak);    }    public static String getSignErrorMsg(final ReloadableResourceBundleMessageSource messageSource, String errorKey) {        if (messageSource != null) {            Locale locale = LocaleContextHolder.getLocale();            if (locale == null) {                locale = Locale.SIMPLIFIED_CHINESE;            }            return messageSource.getMessage(errorKey, null, locale);        }        return "error msg";    }    public static boolean ajaxTimeOut(ResultBean result, HttpServletResponse response) {        response.setCharacterEncoding("UTF-8");        response.setContentType("application/json; charset=utf-8");        response.setHeader("Pragma", "No-cache");        response.setHeader("Cache-Control", "no-cache");        response.setHeader("Cache-Control", "no-store");        response.setDateHeader("Expires", 0);        String json = JsonUtil.objectToJson(result);        response.setHeader("X-Response-Json", json);        logger.info("------------ajaxTimeOutResponse: logs--------------");        logger.info(json);        return false;    }}
簽名:
/**   * @Description:簽名工具   * @Author pengju   * @date 2018/4/11 13:34 */public class TokenHelper {    private static final Logger logger = LoggerFactory.getLogger(TokenHelper.class);    private String accessKey;    private String secretKey;    private static final String TOKEN_VERSION = "v2";    public static final String X_IBEE_AUTH_TOKEN = "X-IbeeAuth-Token";    private final List<String> allowedMethods = Arrays.asList("GET", "POST", "PUT", "DELETE", "HEAD");    public TokenHelper() {    }    public TokenHelper(String ak, String sk) {        this.accessKey = ak;        this.secretKey = sk;    }    /**     * generate the token according the request or response contents     *     * @param urlPath    the url of request     * @param method     request method, must be one of 'GET', 'POST', 'DELETE', 'HEAD',     *                   'PUT'     * @param queryParam the query string of request     * @param body       the post body for request, or response body     * @param expireTime the token expired time     * @return the token     */    public String generateToken(String urlPath, String method, String queryParam, String body, int expireTime) {        if (accessKey == null || accessKey.isEmpty() || secretKey == null || secretKey.isEmpty()) {            throw new IllegalArgumentException("Invalid AK or SK");        }        if (urlPath == null || urlPath.isEmpty()) {            throw new IllegalArgumentException("Empty url path");        }        if (!allowedMethods.contains(method)) {            throw new IllegalArgumentException("invalid request method");        }        String token;        try {            // |v2-{AK}-{ExpireTime}|{SK}|            StringBuffer sbSign = new StringBuffer(String.format("|%s-%s-%d|%s|", TOKEN_VERSION, accessKey, expireTime, secretKey));            // {UrlPath}|            sbSign.append(decodeUtf8(urlPath)).append("|");            // {Method}|            sbSign.append(method).append("|");            // {QueryParam}|            if (StringUtils.isNoneBlank(queryParam)) {                List<String> qsArray = new ArrayList<String>();                for (String kv : queryParam.split("&")) {                    String[] t = kv.split("=");                    if (t.length > 1) {                        qsArray.add(String.format("%s=%s", decodeUtf8(t[0]), decodeUtf8(t[1])));                    } else {                        qsArray.add(String.format("%s=", decodeUtf8(t[0])));                    }                }                Collections.sort(qsArray);                boolean first = true;                for (String s : qsArray) {                    if (first) {                        first = false;                    } else {                        sbSign.append("&");                    }                    sbSign.append(s);                }            }            sbSign.append("|");            // {body}|            if (StringUtils.isNoneBlank(body)) {                sbSign.append(body);            }            sbSign.append("|");            MessageDigest digest = MessageDigest.getInstance("MD5");            digest.reset();            digest.update(sbSign.toString().getBytes("UTF-8"));            // v2-{AK}-{ExpireTime}-{Signature}            token = String.format("%s-%s-%s-%s", TOKEN_VERSION, accessKey, expireTime, new String(Hex.encodeHex(digest.digest())));        } catch (Exception e) {            logger.error("failed to decode url or query path,e.msg={}", e.getMessage());            throw new IllegalStateException("Bad encoded url path or query string");        }        return token;    }    private static String decodeUtf8(String url) {        try {            return URLDecoder.decode(url, "UTF-8");        } catch (UnsupportedEncodingException var2) {            return url;        }    }    /**     * verify the token     *     * @param token      the token for verification     * @param urlPath    the url of request     * @param method     request method, must be one of 'GET', 'POST', 'DELETE', 'HEAD',     *                   'PUT'     * @param queryParam the query string of request     * @param body       the post body for request, or response body     */    public boolean verifyToken(String token, String urlPath, String method, String queryParam, String body) {        if (StringUtils.isBlank(token)) {            logger.warn("token is null");            return false;        }        try {            String[] tokenParts = token.split("-");            if (tokenParts.length != 4) {                logger.warn("invalid token format");                return false;            }            if (!TOKEN_VERSION.equals(tokenParts[0])) {                logger.warn("invalid token protocol version");                return false;            }            int expireTime = Integer.parseInt(tokenParts[2]);            if (expireTime < System.currentTimeMillis() / 1000) {                logger.warn("expired token");                return false;            }            String tokenVerify = generateToken(urlPath, method, queryParam, body, expireTime);            if (token.equals(tokenVerify)) {                return true;            }        } catch (Exception e) {            logger.error("failed to parse token '{}',e.msg={}", token, e.getMessage());        }        return false;    }    public String getAccessKey() {        return accessKey;    }    public void setAccessKey(String accessKey) {        this.accessKey = accessKey;    }    public String getSecretKey() {        return secretKey;    }    public void setSecretKey(String secretKey) {        this.secretKey = secretKey;    }}


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.