简介

当初越来越多人关注接口平安,传统的接口在传输的过程中,容易被抓包而后更改外面的参数值达到某些目标。
传统的做法是用平安框架或者在代码外面做验证,然而有些零碎是不须要登录的,随时能够调。
这时候咱们能够通过对参数进行签名验证,如果参数与签名值不匹配,则申请不通过,间接返回错误信息。

我的项目代码地址:

github.com/MrXuan3168/…

测试

启动我的项目
GET申请能够用浏览器间接拜访 http://localhost:8080/signTes...

A0161DC47118062053567CDD10FBACC6 是 username=admin&password=admin MD5加密后的后果。能够关上 md5jiami.51240.com/ 而后输出 {"password":"admin","username":"admin"} 进行加密验证,json字符串外面,必须保障字段是依照 ascll码
进行排序的,username的ascll码 比 password的ascll码 大,所以要放在前面。

关上 postman 进行POST申请测试,申请Url为 http://localhost:8080/signTes... 参数为

{      "username":"admin",      "password":"admin"  }

调用过程

波及第三方技术

前端:js-md5(vue md5-npm包)、axios(vue ajax申请npm包)

装置命令

npm install js-md5
npm install axios

后端: fastjson、lombok

<dependency>         <groupId>org.projectlombok</groupId>         <artifactId>lombok</artifactId>         <optional>true</optional>     </dependency>     <dependency>         <groupId>com.alibaba</groupId>         <artifactId>fastjson</artifactId>         <version>1.2.47</version>         <scope>compile</scope>     </dependency>

签名逻辑

前端(客户端):

1.不论GET Url 还是 POST Body 的参数,都转换成 json 对象,java培训用 ascll码排序 对参数排序。
2.排序后对参数进行MD5加密,存入 sign 值。
3.把 sign 值 放在 申请URL 前面或者 Head头 外面(该我的项目间接放在URL前面)。

后端(服务端):

1.把参数接管,转成 json对象 ,用 ascll码 排序
2.排序后对参数进行MD5加密,存入 paramsSign 值。
3.和 申请URL 中的 sign值 做比照,雷同则申请通过。

前端代码

加密工具类

import md5 from 'js-md5'

export default class signMd5Utils {    /**     * json参数升序     * @param jsonObj 发送参数     */    static sortAsc(jsonObj) {        let arr = new Array();        let num = 0;        for (let i in jsonObj) {            arr[num] = i;            num++;        }        let sortArr = arr.sort();        let sortObj = {};        for (let i in sortArr) {            sortObj[sortArr[i]] = jsonObj[sortArr[i]];        }        return sortObj;    }    /**     * @param url 申请的url,应该蕴含申请参数(url的?前面的参数)     * @param requestParams 申请参数(POST的JSON参数)     * @returns {string} 获取签名     */    static getSign(url, requestParams) {        let urlParams = this.parseQueryString(url);        let jsonObj = this.mergeObject(urlParams, requestParams);        let requestBody = this.sortAsc(jsonObj);        return md5(JSON.stringify(requestBody)).toUpperCase();    }    /**     * @param url 申请的url     * @returns {{}} 将url中申请参数组装成json对象(url的?前面的参数)     */    static parseQueryString(url) {        let urlReg = /^[^\?]+\?([\w\W]+)$/,            paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,            urlArray = urlReg.exec(url),            result = {};        if (urlArray && urlArray[1]) {            let paramString = urlArray[1], paramResult;            while ((paramResult = paramReg.exec(paramString)) != null) {                result[paramResult[1]] = paramResult[2];            }        }        return result;    }    /**     * @returns {*} 将两个对象合并成一个     */    static mergeObject(objectOne, objectTwo) {        if (Object.keys(objectTwo).length > 0) {            for (let key in objectTwo) {                if (objectTwo.hasOwnProperty(key) === true) {                    objectOne[key] = objectTwo[key];                }            }        }        return objectOne;    }    static urlEncode(param, key, encode) {        if (param == null) return '';        let paramStr = '';        let t = typeof (param);        if (t == 'string' || t == 'number' || t == 'boolean') {            paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param);        } else {            for (let i in param) {                let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i);                paramStr += this.urlEncode(param[i], k, encode);            }        }        return paramStr;    };}

发送申请类

import axios from 'axios';import signMd5Utils from "../utils/signMd5Utils"// var config = require('../../config')//config = process.env.NODE_ENV === 'development' ? config.dev : config.build//let apiUrl = config.apiUrl;//var qs = require('qs');const instance = axios.create({    baseURL: 'http://localhost:8080/',    // timeout: 1000 * 30,    // 容许跨域带token    xhrFields: {        withCredentials: false    },    crossDomain: true,    emulateJSON: true});export default instanceexport function signTestPost(query) {    let url = 'signTest';    let sign = signMd5Utils.getSign(url, query);    let requestUrl = url + "?sign=" + sign;  //将签名增加在申请参数前面去申请接口    return instance({        url: requestUrl,        method: 'post',        data: query    })}export function signTestGet(query) {    let url = 'signTest';    let urlParams = signMd5Utils.urlEncode(query);    let sign = signMd5Utils.getSign(url, query);    let requestUrl = url + "?sign=" + sign + urlParams;  //将签名增加在申请参数前面去申请接口    return instance({        url: requestUrl,        method: 'get',    })}

调用申请

let user = {    "username": "admin",    "password": "admin",};signTestPost(user).then(r => {    console.log(r)});signTestGet(user).then(r => {    console.log(r)})

后端代码

过滤器(达到 Controller 前执行)

import com.alibaba.fastjson.JSONObject;import com.show.sign.utils.HttpUtils;import com.show.sign.utils.SignUtil;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.util.SortedMap;
/** * 签名过滤器 * @author show * @date 10:03 2019/5/30 * @Component 注册 Filter 组件 */@Slf4j@Component public class SignAuthFilter implements Filter {    static final String FAVICON = "/favicon.ico";    @Override    public void init(FilterConfig filterConfig) {        log.info("初始化 SignAuthFilter");    }    @Override    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {        HttpServletResponse response = (HttpServletResponse) res;        // 避免流读取一次后就没有了, 所以须要将流持续写出去        HttpServletRequest request = (HttpServletRequest) req;        HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);        //获取图标不须要验证签名        if (FAVICON.equals(requestWrapper.getRequestURI())) {            chain.doFilter(request, response);        } else {            //获取全副参数(包含URL和body上的)            SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);            //对参数进行签名验证            boolean isSigned = SignUtil.verifySign(allParams);            if (isSigned) {                log.info("签名通过");                chain.doFilter(requestWrapper, response);            } else {                log.info("参数校验出错");                //校验失败返回前端                response.setCharacterEncoding("UTF-8");                response.setContentType("application/json; charset=utf-8");                PrintWriter out = response.getWriter();               JSONObject resParam = new JSONObject();                resParam.put("msg", "参数校验出错");                resParam.put("success", "false");                out.append(resParam.toJSONString());            }        }    }    @Override    public void destroy() {        log.info("销毁 SignAuthFilter");    }}

BodyReaderHttpServletRequestWrapper 类 次要作用是复制 HttpServletRequest 的输出流,不然你拿出 body 参数后验签后,到 Controller 时,接管参数会为 null

import javax.servlet.ReadListener;import javax.servlet.ServletInputStream;import javax.servlet.ServletRequest;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.io.*;import java.nio.charset.Charset;/** * 保留过滤器外面的流 * @author show * @date 10:03 2019/5/30 */public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {    private final byte[] body;    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {        super(request);        String sessionStream = getBodyString(request);        body = sessionStream.getBytes(Charset.forName("UTF-8"));    }    /**     * 获取申请Body     *     * @param request     * @return     */    public String getBodyString(final ServletRequest request) {        StringBuilder sb = new StringBuilder();        try (            InputStream inputStream = cloneInputStream(request.getInputStream());            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))        ) {            String line;            while ((line = reader.readLine()) != null) {                sb.append(line);            }        } catch (IOException e) {            e.printStackTrace();        }        return sb.toString();    }    /**     * Description: 复制输出流</br>     *     * @param inputStream     * @return</br>     */    public InputStream cloneInputStream(ServletInputStream inputStream) {        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();        byte[] buffer = new byte[1024];        int len;        try {            while ((len = inputStream.read(buffer)) > -1) {                byteArrayOutputStream.write(buffer, 0, len);            }            byteArrayOutputStream.flush();        } catch (IOException e) {            e.printStackTrace();        }        return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());    }    @Override    public BufferedReader getReader() {        return new BufferedReader(new InputStreamReader(getInputStream()));    }    @Override    public ServletInputStream getInputStream() {        final ByteArrayInputStream bais = new ByteArrayInputStream(body);        return new ServletInputStream() {            @Override            public int read() {                return bais.read();            }            @Override            public boolean isFinished() {                return false;            }            @Override            public boolean isReady() {                return false;            }            @Override            public void setReadListener(ReadListener readListener) {            }        };    }}

签名工具类

import com.alibaba.fastjson.JSONObject;import lombok.extern.slf4j.Slf4j;import org.springframework.util.DigestUtils;import org.springframework.util.StringUtils;import java.util.SortedMap;/** * 签名工具类 * @author show * @date 10:01 2019/5/30 */@Slf4jpublic class SignUtil {    /**     * @param params 所有的申请参数都会在这里进行排序加密     * @return 验证签名后果     */    public static boolean verifySign(SortedMap<String, String> params) {        String urlSign = params.get("sign");        log.info("Url Sign : {}", urlSign);        if (params == null || StringUtils.isEmpty(urlSign)) {            return false;        }        //把参数加密        String paramsSign = getParamsSign(params);        log.info("Param Sign : {}", paramsSign);        return !StringUtils.isEmpty(paramsSign) && urlSign.equals(paramsSign);    }    /**     * @param params 所有的申请参数都会在这里进行排序加密     * @return 失去签名     */    public static String getParamsSign(SortedMap<String, String> params) {        //要先去掉 Url 里的 Sign        params.remove("sign");        String paramsJsonStr = JSONObject.toJSONString(params);        return DigestUtils.md5DigestAsHex(paramsJsonStr.getBytes()).toUpperCase();    }}

http工具类 获取 申请中 的数据

import com.alibaba.fastjson.JSONObject;import org.springframework.http.HttpMethod;import javax.servlet.http.HttpServletRequest;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.UnsupportedEncodingException;import java.net.URLDecoder;import java.util.HashMap;import java.util.Map;import java.util.SortedMap;import java.util.TreeMap;/** * http 工具类 获取申请中的参数 * @author show * @date 14:23 2019/5/29 */public class HttpUtils {    /**     * 将URL的参数和body参数合并     * @author show     * @date 14:24 2019/5/29     * @param request     */    public static SortedMap<String, String> getAllParams(HttpServletRequest request) throws IOException {        SortedMap<String, String> result = new TreeMap<>();        //获取URL上的参数        Map<String, String> urlParams = getUrlParams(request);        for (Map.Entry entry : urlParams.entrySet()) {            result.put((String) entry.getKey(), (String) entry.getValue());        }        Map<String, String> allRequestParam = new HashMap<>(16);        // get申请不须要拿body参数        if (!HttpMethod.GET.name().equals(request.getMethod())) {            allRequestParam = getAllRequestParam(request);        }        //将URL的参数和body参数进行合并        if (allRequestParam != null) {            for (Map.Entry entry : allRequestParam.entrySet()) {                result.put((String) entry.getKey(), (String) entry.getValue());            }        }        return result;    }    /**     * 获取 Body 参数     * @author show     * @date 15:04 2019/5/30     * @param request     */    public static Map<String, String> getAllRequestParam(final HttpServletRequest request) throws IOException {        BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));        String str = "";        StringBuilder wholeStr = new StringBuilder();        //一行一行的读取body体外面的内容;        while ((str = reader.readLine()) != null) {            wholeStr.append(str);        }        //转化成json对象        return JSONObject.parseObject(wholeStr.toString(), Map.class);    }    /**     * 将URL申请参数转换成Map     * @author show     * @param request     */    public static Map<String, String> getUrlParams(HttpServletRequest request) {        String param = "";        try {            param = URLDecoder.decode(request.getQueryString(), "utf-8");        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        Map<String, String> result = new HashMap<>(16);        String[] params = param.split("&");        for (String s : params) {            int index = s.indexOf("=");            result.put(s.substring(0, index), s.substring(index + 1));        }        return result;    }}