Introduction to the

  • Nowadays, more and more people pay attention to interface security. Traditional interfaces are easy to be captured during transmission and change parameter values to achieve some purposes.
  • The traditional approach is to use a security framework or do validation in code, but some systems do not require login and can be tuned at any time.
  • In this case, you can verify the signature of the parameters. If the parameters do not match the signature value, the request is rejected and an error message is directly returned.

Project Code address:

  • Github.com/MrXuan3168/…

test

  1. Start the project
  2. GET requests can be accessed directly with a browserhttp://localhost:8080/signTest?sign=A0161DC47118062053567CDD10FBACC6&username=admin&password=admin
    • A0161DC47118062053567CDD10FBACC6 is username = admin&password = admin MD5 encrypted results. Open md5JIami.51240.com/ and type {“password”:”admin”,”username”:”admin”} to verify encryption. Ensure that the fields are sorted by the ASCLL code. The ASclL code of username is larger than that of password.
  3. Open postman for POST request test, request Url ishttp://localhost:8080/signTest?sign=A0161DC47118062053567CDD10FBACC6Parameters for
      {
          "username":"admin"."password":"admin"
      }
    Copy the code

Call the process

Involving third party technology

  • Front-end: JS-MD5 (VUE MD5-NPM package), AXIos (Vue AJAX request NPM package)
    • Install command
     npm install js-md5
     npm install axios
    Copy the code
  • Backend: 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>
    Copy the code

Signature logic

  • Front-end (client) : 1. No matter the parameters of GET Url or POST Body, they are converted into JSON objects and sorted by ASCLL code. 2. Encrypt the parameters by MD5 and save the sign value. 3. Place the sign value after the request URL or in the Head header (the item is placed directly after the URL).
  • Back-end (server) : 1. Receive the parameters, convert them into JSON objects, and sort them using ASCLL code 2. After sorting, the parameters are encrypted by MD5 and stored in the paramsSign value. 3. Compare the value with the sign value in the request URL. If the value is the same, the request passes.

The front-end code

  • Encryption tool class
import md5 from 'js-md5'

export default class signMd5Utils {
    @param jsonObj send parameters */

    static sortAsc(jsonObj) {
        let arr = new Array(a);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 Specifies the requested URL, which should contain the request parameters. * @param requestParams request parameter (JSON parameter of POST) * @returns {string} get signature */
    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 request url * @returns {{}} /** * @param URL request url * @returns {{}} The following arguments) */
    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 {*} merges two objects into one */
    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;
    };
}
Copy the code
  • Send request class
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,
    // Cross-domain tokens are allowed
    xhrFields: {
        withCredentials: false
    },
    crossDomain: true.emulateJSON: true
});
export default instance
export function signTestPost(query) {

    let url = 'signTest';
    let sign = signMd5Utils.getSign(url, query);
    let requestUrl = url + "? sign=" + sign;  // Add the signature to the request parameter to request the interface
    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;  // Add the signature to the request parameter to request the interface
    return instance({
        url: requestUrl,
        method: 'get'})},Copy the code
  • Call request
let user = {
    "username": "admin"."password": "admin"}; signTestPost(user).then(r= > {
    console.log(r)
});

signTestGet(user).then(r= > {
    console.log(r)
})
Copy the code

The back-end code

  • Filter (executed before reaching 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;

/** * Signature filter *@author show
 * @date 10:03 2019/5/30
 * @ComponentRegister the Filter component */
@Slf4j
@Component 
public class SignAuthFilter implements Filter {
    static final String FAVICON = "/favicon.ico";

    @Override
    public void init(FilterConfig filterConfig) {

        log.info("Initialize SignAuthFilter");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletResponse response = (HttpServletResponse) res;
        // Prevent the stream from being read once and then gone, so you need to write the stream out again
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
        // No signature verification is required to obtain the icon
        if (FAVICON.equals(requestWrapper.getRequestURI())) {
            chain.doFilter(request, response);
        } else {
            // Get all parameters (including URL and body)
            SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);
            // Verify the signature of the parameter
            boolean isSigned = SignUtil.verifySign(allParams);
            if (isSigned) {
                log.info("Signature approved");
                chain.doFilter(requestWrapper, response);
            } else {
                log.info("Parameter verification error");
                // The verification fails and returns to the front end
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json; charset=utf-8");
                PrintWriter out = response.getWriter();
               JSONObject resParam = new JSONObject();
                resParam.put("msg"."Parameter verification error");
                resParam.put("success"."false"); out.append(resParam.toJSONString()); }}}@Override
    public void destroy(a) {

        log.info("Destroy SignAuthFilter"); }}Copy the code
  • BodyReaderHttpServletRequestWrapper class Main function is to copy it input stream, or you take out after attestation in the body parameters, to the Controller, the receiving parameter can be 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;

/** * Saves the stream * in the filter@author show
 * @date10: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"));
    }

    /** * get the request 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: Copy the input stream </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(a) {

        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream(a) {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {

            @Override
            public int read(a) {

                return bais.read();
            }

            @Override
            public boolean isFinished(a) {

                return false;
            }

            @Override
            public boolean isReady(a) {

                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {}}; }}Copy the code
  • Signature utility class
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.util.SortedMap;

/** * Signature tool class *@author show
 * @date10:01 2019/5/30 * /
@Slf4j
public class SignUtil {

    / * * *@paramAll params request parameters are sorted and encrypted here *@returnVerify the signature result */
    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;
        }
        // Encrypt the parameters
        String paramsSign = getParamsSign(params);
        log.info("Param Sign : {}", paramsSign);
        return! StringUtils.isEmpty(paramsSign) && urlSign.equals(paramsSign); }/ * * *@paramAll params request parameters are sorted and encrypted here *@returnGet signature */
    public static String getParamsSign(SortedMap<String, String> params) {
        // Remove the Sign from the Url
        params.remove("sign");
        String paramsJsonStr = JSONObject.toJSONString(params);
        returnDigestUtils.md5DigestAsHex(paramsJsonStr.getBytes()).toUpperCase(); }}Copy the code
  • The HTTP utility class gets the data in the request
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;

/** * the HTTP utility class gets the parameters in the request *@author show
 * @dateThen they 2019/5/29 * /
public class HttpUtils {
    /** * merge the URL argument with the body argument *@author show
     * @dateSave 2019/5/29 *@param request
     */
    public static SortedMap<String, String> getAllParams(HttpServletRequest request) throws IOException {

        SortedMap<String, String> result = new TreeMap<>();
        // Get the parameters of the 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);
        // The get request does not take the body argument
        if(! HttpMethod.GET.name().equals(request.getMethod())) { allRequestParam = getAllRequestParam(request); }// Merge the URL argument with the body argument
        if(allRequestParam ! =null) {
            for(Map.Entry entry : allRequestParam.entrySet()) { result.put((String) entry.getKey(), (String) entry.getValue()); }}return result;
    }

    /** * Gets the Body argument *@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();
        // Read the contents of the body line by line;
        while((str = reader.readLine()) ! =null) {
            wholeStr.append(str);
        }
        // Convert to json object
        return JSONObject.parseObject(wholeStr.toString(), Map.class);
    }

    /** * convert URL request parameters to 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));
        }
        returnresult; }}Copy the code