Hands on interface for external security (improvement)

Article directory

Last blog

Hands on interface for external security (written on the freezing ninth day of the first month)

Preface

Thank you, brother Zhu, one of Ali's security tycoons, for your advice when I posted my first blog.
Let me summarize the shortcomings of the previous article:

  1. The encryption process is too simple. My last blog also talked about the need for improvement, including the addition of appSecret to the signature.
  2. Time stamp should also be added to prevent data tampering. The problem with timestamps is preventing data replays
  3. Encryption algorithm needs to be improved, MD5 needs to add some salt, etc
  4. Parameters need to be sorted, which is a little different. Generally speaking, the internal interface uses the class to transfer parameters. You can override the toString() method of the class to regulate the sorting of parameters. But there is a flaw: parameters that are not received by object can't be matched through, so toString can't be rewritten. We can only sort them manually.

show you the code

get processing method

new interceptor

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Map;

public class MyBetterInterceptor implements HandlerInterceptor {

    public static final ThreadLocal<String> local = new ThreadLocal<>();

    public static final ThreadLocal<String> localSign = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String sign = request.getHeader("sign");
        //This corresponds to the appId. You can check the Secret in the database
        String appId = request.getHeader("appId");
        if (valid(sign) || valid(appId)) {
            throw new IllegalAccessException("Illegal inspection");
        }
        String time = request.getHeader("createTime");
        if (valid(time)) {
            throw new IllegalAccessException("Time stamp is empty");
        }
        long second = DateUtil.between(new Date(), DateUtil.parse(time), DateUnit.MINUTE);
        if (!(second <= 30 && DateUtil.compare(new Date(), DateUtil.parse(time)) > 0)) {
            throw new IllegalAccessException("Illegal timestamp");
        }
        String sb;
        if ("POST".equals(request.getMethod())) {
            //System.out.println("post parameter:" + new String(IoUtil.readBytes(request.getInputStream(), false)));
            /*String parameter = new String(IoUtil.readBytes(request.getInputStream(), false));
            sb = ParameterUtil.generateSign(ParameterUtil.postParameter(parameter, time));*/
            local.set(time);
            localSign.set(sign);
            return true;
        } else {
            Map<String, String[]> map = request.getParameterMap();
            System.out.println(JSON.toJSONString(map));
            if (map == null || map.size() == 0) {
                return false;
            }
            sb = ParameterUtil.generateSign(ParameterUtil.getParameter(map, time));
        }

        System.out.println("String after encryption obfuscation:" + sb);
        //Todo goes to the database to query the Sercert corresponding to the appId. For example, dajitui is found here
        sb = sb + "dajitui";
        System.out.println("sign:" + SecureUtil.md5(sb));
        if (SecureUtil.md5(sb).equals(sign)) {
            return true;
        }

        return true;
    }

    private boolean valid(String value) {
        return StrUtil.isBlank(value);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

Attention points

1. Interceptor: we can only process the parameters of get request, and the request of post can only be read once, but it can't be read again later. We use aop to verify signature.
2. The encryption algorithm has been changed, and the special parameters of Sercert have been added. Even if the appid is obtained by others, it has little impact.

Encryption process

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson.JSON;
import org.apache.commons.codec.digest.DigestUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author M
 */
public class ParameterUtil {
    ParameterUtil() {
    }

    public static Map<String, Object> getParameter(Map<String, String[]> map, String time) {
        if (map == null || map.size() == 0) {
            return new HashMap<>(6);
        }
        Map<String, Object> resultMap = new HashMap<>(map.size());
        map.forEach((k, v) -> {
            if (v.length == 1) {
                resultMap.put(k, v[0]);
            } else {
                resultMap.put(k, new ArrayList<>(Arrays.asList(v)));
            }
        });
        resultMap.put("time",time);
        System.out.println("map:"+ JSON.toJSONString(resultMap));
        return resultMap;
    }

    public static Map<String, Object> postParameter(String msg, String time) {
        if (StrUtil.isBlank(msg)) {
            return new HashMap<>(6);
        }
        JSONArray array=new JSONArray(msg);
        Map<String, Object> resultMap = new HashMap<>(25);
        for(JSONObject object:array.jsonIter()){
            for (String s : object.keySet()) {
                resultMap.put(s, object.get(s));
            }
            resultMap.put("time",time);
        }
        System.out.println("map:"+ JSON.toJSONString(resultMap));
        return resultMap;
    }

    public static final String SEPERATE_CHAR = "#%$";
    public static String generateSign(Map<String, Object> param) {
        Set<String> keys = param.keySet();
        //Filter out sign and sort key
        List<String> sortedKeys = keys.stream().filter(key -> !Objects.equals(key, "sign")).sorted().collect(Collectors.toList());
        //Plus a custom string obfuscation algorithm
        String calcSign = sortedKeys.stream().map(param::get).map(String::valueOf).collect(Collectors.joining(SEPERATE_CHAR));
        //hash a string
        calcSign = DigestUtils.sha1Hex(calcSign);
        return calcSign;
    }

    public static void main(String[] args) {
        System.out.println(ParameterUtil.postParameter("{\n" +
                "    \"status\": \"0000\",\n" +
                "    \"message\": \"success\",\n" +
                "    \"data\": {\n" +
                "        \"title\": {\n" +
                "            \"id\": \"001\",\n" +
                "            \"name\" : \"Chinese cabbage\"\n" +
                "        },\n" +
                "        \"content\": [\n" +
                "            {\n" +
                "                \"id\": \"001\",\n" +
                "                \"value\":\"Hello cabbage\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"id\": \"002\",\n" +
                "                 \"value\":\"Hello, radish\" \n" +
                "            }\n" +
                "        ]\n" +
                "    }\n" +
                "}",""));
    }

}

among
postParameter is used to process post parameter, and getParameter is used to process get parameter in the form of map < string, Object >.

generateSign is the real leader

public static final String SEPERATE_CHAR = "#%$";
    public static String generateSign(Map<String, Object> param) {
        Set<String> keys = param.keySet();
        //Filter out sign and sort key
        List<String> sortedKeys = keys.stream().filter(key -> !Objects.equals(key, "sign")).sorted().collect(Collectors.toList());
        //Plus a custom string obfuscation algorithm
        String calcSign = sortedKeys.stream().map(param::get).map(String::valueOf).collect(Collectors.joining(SEPERATE_CHAR));
        //hash a string
        calcSign = DigestUtils.sha1Hex(calcSign);
        return calcSign;
    }

Sort the key s, get the value after sorting, and then separate them with specific separators, so that others can't guess our encryption algorithm.

Last sign

		sb = ParameterUtil.generateSign(ParameterUtil.getParameter(map, time));
        System.out.println("String after encryption obfuscation:" + sb);
        //Todo goes to the database to query the Sercert corresponding to the appId. For example, dajitui is found here
        sb = sb + "dajitui";
        System.out.println("sign:" + SecureUtil.md5(sb));

The unique process is to obtain sercert according to appid, and then add md5 encryption.

post processing method

Use aop + custom annotation

A comment on the definition of blind j

@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
public @interface NoParameter {

}

A handful of Stuart

	@NoParameter
    @PostMapping("/rpc/test1")
    public String a1(@RequestBody Student1 student1){
        System.out.println("list:"+student1);
        return "123";
    }

Section logic

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TestAspect {

    @Pointcut("@annotation(com.example.demo.NoParameter)")
    public void annotationPoinCut() {

    }

    @Before(value = "annotationPoinCut()")
    public void beforeTest(JoinPoint point) throws IllegalAccessException {
        String args = JSON.toJSONString(point.getArgs());
        String sb = ParameterUtil.generateSign(ParameterUtil.postParameter(args, MyBetterInterceptor.local.get()));
        sb = sb + "dajitui";
        System.out.println("sign:" + SecureUtil.md5(sb));
        if (!SecureUtil.md5(sb).equals(MyBetterInterceptor.localSign.get())) {
            throw new IllegalAccessException("Failure of verification");
        }
        MyBetterInterceptor.local.remove();
        MyBetterInterceptor.localSign.remove();
    }

}

Reference blog

API security

Published 216 original articles, won praise 31, visited 190000+
Private letter follow

Tags: JSON Java Database Apache

Posted on Mon, 03 Feb 2020 07:07:47 -0500 by xplosiongames