Article directory
Last blogHands on interface for external security (written on the freezing ninth day of the first month)
PrefaceThank 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:
- The encryption process is too simple. My last blog also talked about the need for improvement, including the addition of appSecret to the signature.
- Time stamp should also be added to prevent data tampering. The problem with timestamps is preventing data replays
- Encryption algorithm needs to be improved, MD5 needs to add some salt, etc
- 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.
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() @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 go big chicken legs Published 216 original articles, won praise 31, visited 190000+ Private letter follow