How to design a secure external interface is summarized

Posted by Vince on Wed, 05 Jan 2022 22:22:12 +0100

The blogger has previously worked in the collection and payment system of Hengfeng Bank (equivalent to the payment interface), including the current OLTP API transaction interface and the external data interface of virtual business. In short, when you have done a lot of projects and written a lot of code, you need to go back and summarize more, so that you can see more things you can't see when writing code before, and you can better understand why you want to do so.

Problems to be considered when making interfaces

What is an interface

An interface is nothing more than that the client requests your interface address, passes in a pile of parameters defined by the interface, and returns the data agreed by the interface and the corresponding data format through the logical processing of the interface itself.

How to develop interfaces

Due to the nature of the interface and the docking data with partners, the following points need to be paid attention to during development:

1. Define interface input parameters: write interface documents

2. Define the data type returned by the interface: generally, it needs to be encapsulated into a certain format to determine whether to return json or xml message

See the following return data definition format:

package com.caiex.vb.model;
 
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
 
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Result", propOrder = { "resultCode", "resultMsg" })
public class Result implements Serializable {
 private static final long serialVersionUID = 10L;
 protected int resultCode;
 protected String resultMsg;
 
 public int getResultCode() {
  return this.resultCode;
 }
 
 public void setResultCode(int value) {
  this.resultCode = value;
 }
 
 public String getResultMsg() {
  return this.resultMsg;
 }
 
 public void setResultMsg(String value) {
  this.resultMsg = value;
 }
}
package com.caiex.vb.model;
 
import java.io.Serializable;
 
public class Response implements Serializable {
 
 private static final long serialVersionUID = 2360867989280235575L;
 
 private Result result;
 
 private Object data;
 
 public Result getResult() {
  if (this.result == null) {
   this.result = new Result();
  }
  return result;
 }
 
 public void setResult(Result result) {
  this.result = result;
 }
 
 public Object getData() {
  return data;
 }
 
 public void setData(Object data) {
  this.data = data;
 }
 
}

3. Determine the method of accessing the interface, such as get or post. You can define rules according to the restful interface. RESTful API: RESTful API

4. Define a set of globally unified and universal return codes to help troubleshoot problems;

 public static int NO_AGENT_RATE = 1119;  //Exchange rate not found
 
 public static int SCHEME_COMMIT_FAIL = 4000;  //Scheme submission failed
 
 public static int SCHEME_CONFIRMATION = 4001;  //Scheme confirmation in progress
 
 public static int SCHEME_NOT_EXIST = 4002;  //Scheme does not exist
 
 public static int SCHEME_CANCEL= 4005;  //Scheme does not exist
 
 //. . . . 

5. Unified exception handling: each system should require a set of unified exception handling

package com.caiex.vb.interceptor;
 
import javax.servlet.http.HttpServletRequest;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
 
import com.caiex.vb.model.Response;
 
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
 
 private  Logger  logger = LoggerFactory.getLogger(this.getClass()); 
 
    /**
     * All abnormal errors are reported
     * @param request
     * @param exception
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value=Exception.class)  
    public Response allExceptionHandler(HttpServletRequest request,  
            Exception exception) throws Exception  
    {  
     logger.error("Exception intercepted:", exception);
        Response response = new Response();
        response.setData(null);
        response.getResult().setResultCode(9999);
        response.getResult().setResultMsg("System busy");
        return response;  
    }  
 
}

6. Interceptor chain setting: when the partner accesses the interface, it will access your interface server according to the transmission parameters defined by your interface, but there will be problems such as wrong interface parameter type or format, no transmission of required parameters, and even some malicious requests, which can be intercepted in the early stage through the interceptor chain to avoid the pressure of interface service.

Learning materials: Java advanced video resources

Another important point is that signature verification can also be set in the interceptor. Inherit WebMvcConfigurerAdapter to implement the blocker chain of springboot. Implement the HandlerInterceptor method to write a business interceptor.

package com.caiex.vb.interceptor;
 
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
import com.alibaba.fastjson.JSON;
import com.caiex.redis.service.api.RedisApi;
import com.caiex.vb.model.Response;
import com.caiex.vb.utils.CaiexCheckUtils;
 
@Component
public class SignInterceptor extends BaseValidator implements HandlerInterceptor{
 
 private Logger logger = LogManager.getLogger(this.getClass());
 
 @Resource
 private RedisApi redisApi;
 
 
 public void afterCompletion(HttpServletRequest arg0,
   HttpServletResponse arg1, Object arg2, Exception arg3)
   throws Exception {
  // TODO Auto-generated method stub
  
 }
 
 public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
   Object arg2, ModelAndView arg3) throws Exception {
  // TODO Auto-generated method stub
  
 }
 
 public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
   Object arg2) throws Exception {
  if(isTestIpAddr(arg0)){
   return true;
  }
  String securityKey = redisApi.hGet("securityKey", arg0.getParameter("agentid"));
  if(StringUtils.isEmpty(securityKey)){
   Response response = new Response();
   response.setData(null);
   response.getResult().setResultCode(8001);
   response.getResult().setResultMsg("Missing private key, channel number:" + arg0.getParameter("agentid"));
   logger.error("Missing private key, channel number:" + arg0.getParameter("agentid"));
   InterceptorResp.printJson(arg1, response);
   return false;
  }
  
  if(StringUtils.isEmpty(arg0.getParameter("sign")) || !arg0.getParameter("sign").equals(CaiexCheckUtils.getSign(arg0.getParameterMap(), securityKey))){
   Response response = new Response();
   response.setData(null);
   response.getResult().setResultCode(3203);
   response.getResult().setResultMsg("Parameter signature authentication failed");
   logger.error("Parameter signature authentication failed:" + JSON.toJSONString(arg0.getParameterMap()) + " securityKey = " + securityKey);
   InterceptorResp.printJson(arg1, response);
   return false;
  }else{
   return true;
  }
  
 }
 
}
package com.caiex.oltp.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
import com.caiex.oltp.interceptor.APILimitRateValidator;
import com.caiex.oltp.interceptor.CommonValidator;
import com.caiex.oltp.interceptor.DDSAuthValidator;
import com.caiex.oltp.interceptor.QueryPriceParamsValidator;
import com.caiex.oltp.interceptor.TradeParamsValidator;
 
 
@EnableWebMvc
@Configuration
@ComponentScan
public class WebAppConfigurer extends WebMvcConfigurerAdapter {
 
   @Bean
   CommonValidator commonInterceptor() {
         return new CommonValidator();
     }
 
   @Bean
   DDSAuthValidator ddsAuthInterceptor() {
         return new DDSAuthValidator();
     }
 
   @Bean
   QueryPriceParamsValidator queryPriceParamsInterceptor() {
         return new QueryPriceParamsValidator();
     }
 
   @Bean
   TradeParamsValidator tradeParamsInterceptor() {
         return new TradeParamsValidator();
     }
   
  @Bean
   APILimitRateValidator aPILimitRateInterceptor() {
         return new APILimitRateValidator();
     }
 
 
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
      
      //Access rate limit
      registry.addInterceptor(aPILimitRateInterceptor())
      .addPathPatterns("/*/*");
      //.addPathPatterns("/price/getPriceParam");
 
      //Parameter signature authentication
         registry.addInterceptor(ddsAuthInterceptor())
         .addPathPatterns("/tradeState/*")
         .addPathPatterns("/recycle/*")
         .addPathPatterns("/matchInfo/*")
         .addPathPatterns("/price/tradeTicketParam");
         
         //Common parameter check
         registry.addInterceptor(commonInterceptor())
         .addPathPatterns("/price/tradeTicketParam")
         .addPathPatterns("/tradeState/*")
         .addPathPatterns("/recycle/*");
         
         //RFQ parameter verification
         registry.addInterceptor(queryPriceParamsInterceptor())
         .addPathPatterns("/price/getPriceParam");
         
         //Transaction parameter check
         registry.addInterceptor(tradeParamsInterceptor())
         .addPathPatterns("/price/tradeTicketParam");
         
         super.addInterceptors(registry);
     }
}

7.token token and sign digital signature realize data confidentiality.

Create Token

In order to ensure the legitimacy of the request, we provide a third-party token creation interface. Some interfaces need to verify the legitimacy of messages through tokens to avoid illegal attacks.

At present, the expiration time of the token is temporarily set as 1 day. Considering that the partners are often distributed environments, multiple machines may apply for a token. In order to reduce the difficulty for the partners to ensure the consistency of the token, within one minute after the successful creation of the token by the calling interface, the data returned by requesting the token again is the same.

Get private key

Obtain the private key for digital signature. The private key obtained by a third party shall be properly saved and updated regularly. The private key only participates in digital signature and is not transmitted as a parameter.

Digital signature method:

Parameter signature; Signature method: all parameters with non null value (excluding this parameter) participate in digital signature. Get a string in the format of "parameter name + parameter value + private key", and then MD5 this string is the value of this parameter. (example: h15adc39y9ba59abbe56e057e60f883g), so you need to obtain the private key first.

Signature verification method:

Put all non null parameters of the user into the TreeSet with defined sorting rules for sorting, and then use StringBuilder to get a string in the format of "parameter name + parameter value + private key" (the private key is taken from redis), and then MD5 this string is the value of this parameter. Compare this value with the sign signature sent by the user. If it is the same, it will pass, otherwise it will not pass.

private String createToken(){
  String utk = "Msk!D*"+System.currentTimeMillis()+"UBR&FLP";
  logger.info("create token   --- "+Md5Util.md5(utk));
  return Md5Util.md5(utk);
 }

8. Interface current limiting

Sometimes the server is under too much pressure to prevent the transaction interface from being crowded. You can limit the flow of some other interfaces that do not affect the main business functions and have a large amount of computation. RateLimit -- use guava to limit the interface flow. When the interface exceeds the specified flow, the request of the interface will not be processed. See RateLimit for details. Other current limiting frames can also be referred to.

9. Protocol encryption, upgrade http to https;

Why upgrade? To ensure data security. When https is used for access, the data is disconnected from the client to the service, and the server to the client are encrypted. Even if hackers capture packets, they can't see the transmission content. Of course, there are other benefits. I won't talk about them here. But this is also a problem that needs attention in developing interface projects.

How to improve the high concurrency and high availability of the interface

After the interface is developed, we will discuss the availability of the interface. First, we should distinguish between high concurrency and high availability. After all, high availability is available, but it is slow or inefficient. In fact, it can also be classified as one kind of problem, but it doesn't matter. What matters is how to improve the access speed and performance of the interface you write.

Interface high concurrency solution (there is no unique answer, and the industry also has many different methods for different businesses)

When accessing an interface to obtain data, it is found that the return is very slow or always timeout. If the network reason is excluded, the interface server is under too much pressure and can't handle it. During the world cup, we always check the background log connection by reset, Walker pipe and some timeout problems.

At this time, you may encounter high concurrency and high availability problems. However, no matter what problems you encounter, you can't make assumptions and make random changes. You need to find the reasons for the slowness in order to suit the remedy to the case. Random changes may lead to other problems. First, the three directions to solve the high concurrency problem are load balancing, caching and clustering.

Learning materials: Java advanced video resources

load balancing

We use Alibaba cloud server load balancing and background distributed service management. Our operation and maintenance brother has built a set of k8s, which can freely expand service nodes on k8s, and each service node can automatically drift with the use of memory. Needless to say, k8s is really harmful, and interested students can learn it in detail. So the question is, how does alicloud's load balancing correspond to k8s's load balancing?

This involves some features of k8s service exposure. In short, k8s exposes all cluster services on the specified servers through the specified internal load balancing. Then we connect these servers to Alibaba cloud load balancing. This involves many details and configurations. Of course, in addition to nginx, there are other load balancing solutions, including software and hardware, such as f5.

For Alibaba cloud nginx load balancing, we use the weighted polling strategy. In fact, polling is the most inefficient method;

This is the most basic example of load balancing, but it is not enough to meet the actual needs; At present, the upstream module of the Nginx server supports six modes of allocation:

Load balancing strategy

Polling default method weight weight method ip_hash is based on ip allocation method least_conn least connection method fair (third party) response time method url_hash (third party) allocation method based on URL

colony

First of all, through troubleshooting, it is found that the oltp API interface service handles requests very slowly. A large number of requests always time out and interrupt the connection. At this time, we think the simplest way is to add machines and add several more machines to the oltp interface service.

Well, everything is perfect, as expected, but when you add it to a certain number, you find that it doesn't work. The asynchronous response is still very slow, or more intuitively, there is a serious message accumulation in the message queue. At this time, you find that there are new problems or bottlenecks. This problem can not be solved by adding oltp server. Then, you need to reposition the problem. The discovery is the accumulation of news. The accumulation of news is that the producers are too fast, resulting in the inability of consumers to consume. At this time, you need to increase the consumption of consumers. Add several more machines to the risk control system to achieve a certain balance between consumers and producers.

There is a misunderstanding here. You may think that the number of brokers in rocketmq is too small and increase the number of brokers. In fact, when consumers and producers maintain the same speed, messages will not accumulate. The original number of brokers is enough. However, adding a broker will also enable the message to be processed as soon as possible and improve a certain efficiency.

cache

When adding machines can not solve the problem, or there are not so many servers available, we should focus on the code level to solve the high concurrency problem. Redis is a high-performance key value database. When it is slow to get data from the database, it can be stored in redis and retrieved from redis.

  • Cache objects with ConcurrentHashMap and set the expiration time
  • redis caches data and obtains key s that will not change frequently in combination with spring scheduled tasks
  • Improve the efficiency of using redis: for example, use mGet to obtain multiple key s at one time
  • .... etc.

Interface high availability problem

The problem of high availability should rise to the architecture of the whole service, that is, it should be considered in building the overall system. The high availability problem is dominated by the problem of single point of failure and slow access speed. See service high availability

  • Redis master-slave distributed (single point of failure, improved access speed and master-slave backup of redis)
  • zookeeper master-slave cluster of distributed dubbo service
  • Master-slave cluster of strom
  • ... etc.

summary

The following is a summary of interface development services:

1. Pull or push:

When the interface is used as the data source, we should also consider whether the data is pulled by the partners or pushed when the data changes. Of course, the push effect is better, but how to effectively push the data without pushing duplicate data is a problem that needs to be considered according to the actual business.

2. How to ensure the idempotence of transactions and the uniqueness of orders on multiple distributed servers

When the interface service and partners are distributed, it is easy to apply for multiple transaction requests for an order number. However, according to idempotency, a lottery ticket can only be traded once, and the result should be the same whenever the request is made. In this case, how can we ensure uniqueness? We need to save the order and order status in redis to see whether the order already exists each time. But this transaction may not be successful. Next time, this ticket can continue to be traded and a new order number can be generated.

setNX of redis is a good solution, which means that when the key exists, it returns false. When there is no key, the key and value are inserted successfully. It is used to check whether the order is being submitted. If yes, the request will be blocked to avoid repeated submission. The expiration time can be set to 3s. Lock the order before submission to prevent repeated submission.

3. If the processing time exceeds 10s, the order transaction failure will be returned automatically

In short, the blogger found that in the high concurrency scenario, the cause of service crash is redis and the database. It may be that redis is too slow to read and write, or some sql in the database is improperly used, or no cable guidance is established, resulting in slow reading and writing.

In short, this is a long way. We all need to slowly accumulate experience and learn from the better solutions of our predecessors.

 

Topics: Java Back-end server security