How should API interfaces be designed, how to ensure security, how to sign, and how to prevent duplication

Posted by robburne on Wed, 27 Oct 2021 19:29:55 +0200

In the actual business, it is inevitable to interact and transfer data with the third-party system, so how to ensure the security of data in the transmission process (anti theft)? In addition to the https protocol, can we add a general set of algorithms and specifications to ensure the security of transmission?

Let's discuss some common security methods of API design, which may not be the best and have more powerful implementation methods, but this is my own experience sharing

1. Introduction to token

Token: access token, which is used in the interface to identify the identity and credentials of the interface caller and reduce the transmission times of user name and password. Generally, the client (interface caller) needs to first apply to the server for an interface call account. The server will give an appId and a key. The key is used for parameter signature. Note that some security processing needs to be done to save the key to the client to prevent disclosure.

The value of the token is generally UUID. After generating the token, the server needs to use the token as a key and save some information associated with the token as a value to the cache server (redis). When a request comes, the server will query the cache server for the existence of the token. If it exists, the interface will be called. There is no return interface error. It is generally implemented through interceptors or filters, There are two types of Tokens:

  • API token: used to access interfaces that do not require user login, such as login, registration, acquisition of some basic data, etc. To obtain the interface token, you need to replace appId, timestamp and sign, sign = encryption (timestamp+key)

  • User token: used to access the interface after the user logs in, such as obtaining my basic information, saving, modifying, deleting, etc. To obtain a user token, you need to exchange the user name and password

On the timeliness of Tokens: tokens can be one-time or effective over a period of time. The specific use depends on the business needs.

In general, it is best to use https protocol for the interface. If http protocol is used, the Token mechanism is only a way to reduce the possibility of being hacked. In fact, it can only prevent gentlemen, not villains.

Generally, the three parameters of token, timestamp and sign will be passed as parameters in the interface at the same time, and each parameter has its own purpose.

2. Introduction to timestamp

2.1,timestamp:

Timestamp is the current timestamp corresponding to the interface invoked by the client. The timestamp is used to prevent DoS attacks.

When the hacker hijacks the requested url to DOS attack, the interface will judge the difference between the current system time of the server and the timestamp transmitted in the interface every time the interface is called. If the difference exceeds a set time (if 5 minutes), the request will be intercepted. If it is within the set timeout, the DoS attack cannot be prevented. Timestamp mechanism can only reduce the time of DoS attack and shorten the attack time. If the hacker modifies the value of the timestamp, it can be handled through the sign signature mechanism.

2.2,DoS:

DOS is the abbreviation of Denial of Service, which is called DoS attack. Its purpose is to make the computer or network unable to provide normal services. The most common DoS attacks are computer network bandwidth attacks and connectivity attacks.

DoS attack refers to deliberately attacking the defects of network protocol implementation or brutally exhausting the resources of the attacked object directly through barbaric means. The purpose is to make the target computer or network unable to provide normal services or resource access, and make the target system service system stop responding or even collapse. This attack does not include invading the target server or target network equipment. These service resources include network bandwidth, file system space capacity, open processes or allowed connections. This attack will lead to the lack of resources. No matter how fast the processing speed of the computer, how large the memory capacity and how fast the network bandwidth are, the consequences of this attack can not be avoided.

  • Pingflood: this attack sends a large number of ping packets to the destination host in a short time, causing network congestion or host resource depletion.

  • Synflood: this attack sends SYN packets to the destination host at multiple random source host addresses, but does not respond after receiving SYN acks from the destination host. In this way, the destination host establishes a large number of connection queues for these source hosts, and maintains these queues all the time because no ACK is received, resulting in a large consumption of resources and unable to provide services to normal requests.

  • Smurf: this attack sends a packet with a specific request (such as ICMP response request) to the broadcast address of a subnet, and disguises the source address as the host address to be attacked. All hosts on the subnet respond to the broadcast packet request and contract to the attacked host, so that the host is attacked.

  • Land based: the attacker sets the source address and destination address of a packet as the address of the target host, and then sends the packet to the attacked host through IP spoofing. This packet can cause the attacked host to fall into an endless loop due to trying to establish a connection with itself, thus greatly reducing the system performance.

  • Ping of Death: according to the TCP/IP specification, the maximum length of a packet is 65536 bytes. Although the length of a packet cannot exceed 65536 bytes, the superposition of multiple fragments divided into a packet can be achieved. When a host receives a packet with a length greater than 65536 bytes, it is attacked by Ping of Death, which will cause downtime of the host.

  • Teardrop: when IP packets are transmitted over the network, the packets can be divided into smaller segments. An attacker can implement teardrop attack by sending two (or more) packets. The offset of the first packet is 0 and the length is N, and the offset of the second packet is less than N. In order to merge these data segments, the TCP/IP stack will allocate unusually large resources, resulting in the lack of system resources and even the restart of the machine.

  • PingSweep: use ICMP Echo to poll multiple hosts.

3. Introduction to sign

nonce: random value is a value randomly generated by the client and passed as a parameter. The purpose of random value is to increase the variability of sign signature. Random values are generally a combination of numbers and letters, with a length of 6 bits. There are no fixed rules for the composition and length of random values.

Sign: it is generally used for parameter signature to prevent illegal tampering. The most common is to modify important sensitive parameters such as amount. The value of sign is generally to sort all non empty parameters according to ascending sequence, and then splice them together with + token + key + timestamp + nonce (random number), and then encrypt them with some encryption algorithm, which is passed as a parameter sign in the interface, You can also put the sign in the request header.

In the process of network transmission, if the interface is hijacked by a hacker, modify the parameter value, and then continue to call the interface. Although the parameter value is modified, the hacker does not know how the sign is calculated, what the values of the signs are composed of, and in what order they are spliced together. The most important thing is that he does not know what the key in the signature string is, Therefore, hackers can tamper with the value of parameters, but cannot modify the value of sign. Before calling the interface, the server will recalculate the value of sign according to the sign rules, and then compare it with the value of the sign parameter passed by the interface. If it is equal, it means that the parameter value has not been tampered with. If it is unequal, it means that the parameter has been illegally tampered with, and the interface will not be executed.

4. Prevent duplicate submissions

For some important operations that need to be prevented from being submitted repeatedly by the client (such as non idempotent important operations), the specific method is to save the sign as a key to redis when the request is submitted for the first time, and set the timeout time. The timeout time is the same as the difference set in Timestamp.

When the same request is accessed for the second time, it will first check whether the sign exists in redis. If it exists, it will prove that the repeated submission has been made, and the interface will not continue to call. If the sign is deleted in the cache server because the expiration time is up, when the URL requests the server again, because the expiration time of the token and the expiration time of the sign are the same, the expiration of the sign also means that the token has expired. In that case, the re access server of the same URL will be intercepted due to the token error, This is why the expiration time of sign and token should be consistent. The repeated call rejection mechanism ensures that the URL is intercepted by others and cannot be used (such as capturing data).

For which interfaces need to prevent repeated submission, you can customize an annotation to mark.

Note: if all security measures are used, it is sometimes too complex. In the actual project, it needs to be tailored according to its own situation. For example, only the signature mechanism can be used to ensure that the information will not be tampered with, or only the Token mechanism can be used when providing services. How to cut depends on the actual situation of the project and the requirements for interface security.

5. Use process

1. The interface caller (client) applies to the interface provider (server) for an interface call account. After the application is successful, the interface provider will give the interface caller an appId and a key parameter

2. The client carries the parameters appId, timestamp and sign to call the API token on the server side, where sign = encryption (appId + timestamp + key)

3. The client holds the api_token to access the interface that can be accessed without login

4. When accessing the interface that the user needs to log in, the client jumps to the login page and calls the login interface through the user name and password. The login interface will return a usertoken. The client takes the usertoken to access the interface that needs to be logged in

The function of sign is to prevent the parameters from being tampered with. When the client calls the server, it needs to pass the sign parameter. When the server responds to the client, it can also return a sign to verify whether the returned value has been tampered with illegally. The sign algorithm transmitted by the client and the sign algorithm responded by the server may be different.

6. Sample code

6.1,dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

6.2,RedisConfiguration

@Configuration
public class RedisConfiguration {
    @Bean
    public JedisConnectionFactory jedisConnectionFactory(){
        return new JedisConnectionFactory();
    }


    /**
     * Support storage objects
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(){
        RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);


        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();


        return redisTemplate;
    }
}

6.3,TokenController

@Slf4j
@RestController
@RequestMapping("/api/token")
public class TokenController {


    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * API Token
     *
     * @param sign
     * @return
     */
    @PostMapping("/api_token")
    public ApiResponse<AccessToken> apiToken(String appId, @RequestHeader("timestamp") String timestamp, @RequestHeader("sign") String sign) {
        Assert.isTrue(!StringUtils.isEmpty(appId) && !StringUtils.isEmpty(timestamp) && !StringUtils.isEmpty(sign), "Parameter error");


        long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp);
        Assert.isTrue(reqeustInterval < 5 * 60 * 1000, "Request expired, please re request");


        // 1. Query the database according to appId to obtain appSecret
        AppInfo appInfo = new AppInfo("1", "12345678954556");


        // 2. Verification signature
        String signString = timestamp + appId + appInfo.getKey();
        String signature = MD5Util.encode(signString);
        log.info(signature);
        Assert.isTrue(signature.equals(sign), "Signature error");


        // 3. If a token is generated correctly and saved in redis, an error message will be returned
        AccessToken accessToken = this.saveToken(0, appInfo, null);


        return ApiResponse.success(accessToken);
    }


    @NotRepeatSubmit(5000)
    @PostMapping("user_token")
    public ApiResponse<UserInfo> userToken(String username, String password) {
        // Query the password according to the user name and compare the password (the password can be encrypted by RSA)
        UserInfo userInfo = new UserInfo(username, "81255cb0dca1a5f304328a70ac85dcbd", "111111");
        String pwd = password + userInfo.getSalt();
        String passwordMD5 = MD5Util.encode(pwd);
        Assert.isTrue(passwordMD5.equals(userInfo.getPassword()), "Password error");


        // 2. Save Token
        AppInfo appInfo = new AppInfo("1", "12345678954556");
        AccessToken accessToken = this.saveToken(1, appInfo, userInfo);
        userInfo.setAccessToken(accessToken);
        return ApiResponse.success(userInfo);
    }


    private AccessToken saveToken(int tokenType, AppInfo appInfo,  UserInfo userInfo) {
        String token = UUID.randomUUID().toString();


        // The valid period of the token is 2 hours
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.SECOND, 7200);
        Date expireTime = calendar.getTime();


        // 4. Save token
        ValueOperations<String, TokenInfo> operations = redisTemplate.opsForValue();
        TokenInfo tokenInfo = new TokenInfo();
        tokenInfo.setTokenType(tokenType);
        tokenInfo.setAppInfo(appInfo);


        if (tokenType == 1) {
            tokenInfo.setUserInfo(userInfo);
        }


        operations.set(token, tokenInfo, 7200, TimeUnit.SECONDS);


        AccessToken accessToken = new AccessToken(token, expireTime);


        return accessToken;
    }


    public static void main(String[] args) {
        long timestamp = System.currentTimeMillis();
        System.out.println(timestamp);
        String signString = timestamp + "1" + "12345678954556";
        String sign = MD5Util.encode(signString);
        System.out.println(sign);


        System.out.println("-------------------");
        signString = "password=123456&username=1&12345678954556" + "ff03e64b-427b-45a7-b78b-47d9e8597d3b1529815393153sdfsdfsfs" + timestamp + "A1scr6";
        sign = MD5Util.encode(signString);
        System.out.println(sign);
    }
}

6.4,WebMvcConfiguration

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {


    private static final String[] excludePathPatterns  = {"/api/token/api_token"};


    @Autowired
    private TokenInterceptor tokenInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns(excludePathPatterns);
    }
}

6.5,TokenInterceptor

@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {


    @Autowired
    private RedisTemplate redisTemplate;


    /**
     *
     * @param request
     * @param response
     * @param handler Target method of access
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        String timestamp = request.getHeader("timestamp");
        // Random string
        String nonce = request.getHeader("nonce");
        String sign = request.getHeader("sign");
        Assert.isTrue(!StringUtils.isEmpty(token) && !StringUtils.isEmpty(timestamp) && !StringUtils.isEmpty(sign), "Parameter error");


        // Get timeout
        NotRepeatSubmit notRepeatSubmit = ApiUtil.getNotRepeatSubmit(handler);
        long expireTime = notRepeatSubmit == null ? 5 * 60 * 1000 : notRepeatSubmit.value();


        // 2. Request interval
        long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp);
        Assert.isTrue(reqeustInterval < expireTime, "Request timed out, please re request");


        // 3. Verify whether the Token exists
        ValueOperations<String, TokenInfo> tokenRedis = redisTemplate.opsForValue();
        TokenInfo tokenInfo = tokenRedis.get(token);
        Assert.notNull(tokenInfo, "token error");


        // 4. Verify the signature (add all parameters to prevent others from tampering with the parameters). All parameters are concatenated into URLs according to the ascending sequence of parameter names
        // Request parameters + token + timestamp + nonce
        String signString = ApiUtil.concatSignString(request) + tokenInfo.getAppInfo().getKey() + token + timestamp + nonce;
        String signature = MD5Util.encode(signString);
        boolean flag = signature.equals(sign);
        Assert.isTrue(flag, "Signature error");


        // 5. Reject repeated calls (stored during the first access, and the expiration time is consistent with the request timeout). Only those marked that repeated submission of comments is not allowed will be verified
        if (notRepeatSubmit != null) {
            ValueOperations<String, Integer> signRedis = redisTemplate.opsForValue();
            boolean exists = redisTemplate.hasKey(sign);
            Assert.isTrue(!exists, "Do not resubmit");
            signRedis.set(sign, 0, expireTime, TimeUnit.MILLISECONDS);
        }


        return super.preHandle(request, response, handler);
    }
}

6.6. Md5util - MD5 tool class, encrypt to generate digital signature

public class MD5Util {


    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };


    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));


        return resultSb.toString();
    }


    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }


    public static String encode(String origin) {
        return encode(origin, "UTF-8");
    }
    public static String encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }
}

6.7,@NotRepeatSubmit   ----- Custom annotation to prevent repeated submission.

/**
 * Duplicate submission is prohibited
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotRepeatSubmit {
    /** Expiration time in milliseconds**/
    long value() default 5000;
}

6.8,AccessToken

@Data
@AllArgsConstructor
public class AccessToken {
    /** token */
    private String token;


    /** Failure time */
    private Date expireTime;
}

6.9,AppInfo

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AppInfo {
    /** App id */
    private String appId;
    /** API Secret key */
    private String key;
}

6.10,TokenInfo

@Data
public class TokenInfo {
    /** token Type: api:0, user:1 */
    private Integer tokenType;


    /** App information */
    private AppInfo appInfo;


    /** User other data */
    private UserInfo userInfo;
}

6.11,UserInfo

@Data
public class UserInfo {
    /** user name */
    private String username;
    /** cell-phone number */
    private String mobile;
    /** mailbox */
    private String email;
    /** password */
    private String password;
    /** salt */
    private String salt;


    private AccessToken accessToken;


    public UserInfo(String username, String password, String salt) {
        this.username = username;
        this.password = password;
        this.salt = salt;
    }
} 

6.12,ApiCodeEnum

/**
 * The error code can use pure numbers, use different intervals to identify a type of error, use pure characters, or use prefix + number
 *
 * Error code: ERR + No
 *
 * You can use the log level prefix as the error type to distinguish Info(I) Error(E) Warning(W)
 *
 * Or business module + error number
 *
 * TODO Error code design
 *
 * Alipay Two code s and two MSG are used( https://docs.open.alipay.com/api_1/alipay.trade.pay )
 */
public enum ApiCodeEnum {
    SUCCESS("10000", "success"),
    UNKNOW_ERROR("ERR0001","unknown error"),
    PARAMETER_ERROR("ERR0002","Parameter error"),
    TOKEN_EXPIRE("ERR0003","Certification expired"),
    REQUEST_TIMEOUT("ERR0004","request timeout"),
    SIGN_ERROR("ERR0005","Signature error"),
    REPEAT_SUBMIT("ERR0006","Please do not operate frequently"),
    ;


    /** code */
    private String code;


    /** result */
    private String msg;


    ApiCodeEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }


    public String getCode() {
        return code;
    }


    public String getMsg() {
        return msg;
    }
}

6.13,ApiResult

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResult {


    /** code */
    private String code;


    /** result */
    private String msg;
}

6.14,ApiUtil  ------- This is a reference to Alipay encryption algorithm. I came directly to Copy.

public class ApiUtil {


    /**
     * Continue splicing parameters by parameter name
     * @param request
     * @return
     */
    public static String concatSignString(HttpServletRequest request) {
        Map<String, String> paramterMap = new HashMap<>();
        request.getParameterMap().forEach((key, value) -> paramterMap.put(key, value[0]));
        // Sort according to the key, and then splice the parameters
        Set<String> keySet = paramterMap.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            // Or omitted fields
            if (k.equals("sign")) {
                continue;
            }
            if (paramterMap.get(k).trim().length() > 0) {
                // If the parameter value is empty, it will not participate in the signature
                sb.append(k).append("=").append(paramterMap.get(k).trim()).append("&");
            }
        }


        return sb.toString();
    }


    public static String concatSignString(Map<String, String> map) {
        Map<String, String> paramterMap = new HashMap<>();
        map.forEach((key, value) -> paramterMap.put(key, value));
        // Sort according to the key, and then splice the parameters
        Set<String> keySet = paramterMap.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (paramterMap.get(k).trim().length() > 0) {
                // If the parameter value is empty, it will not participate in the signature
                sb.append(k).append("=").append(paramterMap.get(k).trim()).append("&");
            }
        }
        return sb.toString();
    }


    /**
     * Gets the @ NotRepeatSubmit annotation on the method
     * @param handler
     * @return
     */
    public static NotRepeatSubmit getNotRepeatSubmit(Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            NotRepeatSubmit annotation = method.getAnnotation(NotRepeatSubmit.class);


            return annotation;
        }


        return null;
    }
}

6.15,ApiResponse

@Data
@Slf4j
public class ApiResponse<T> {
    /** result */
    private ApiResult result;


    /** data */
    private T data;


    /** autograph */
    private String sign;


    public static <T> ApiResponse success(T data) {
        return response(ApiCodeEnum.SUCCESS.getCode(), ApiCodeEnum.SUCCESS.getMsg(), data);
    }


    public static ApiResponse error(String code, String msg) {
        return response(code, msg, null);
    }


    public static <T> ApiResponse response(String code, String msg, T data) {
        ApiResult result = new ApiResult(code, msg);
        ApiResponse response = new ApiResponse();
        response.setResult(result);
        response.setData(data);


        String sign = signData(data);
        response.setSign(sign);


        return response;
    }


    private static <T> String signData(T data) {
        // TODO query key
        String key = "12345678954556";
        Map<String, String> responseMap = null;
        try {
            responseMap = getFields(data);
        } catch (IllegalAccessException e) {
            return null;
        }
        String urlComponent = ApiUtil.concatSignString(responseMap);
        String signature = urlComponent + "key=" + key;
        String sign = MD5Util.encode(signature);


        return sign;
    }


    /**
     * @param data Get the field name and value of the object
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static Map<String, String> getFields(Object data) throws IllegalAccessException, IllegalArgumentException {
        if (data == null) return null;
        Map<String, String> map = new HashMap<>();
        Field[] fields = data.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);


            String name = field.getName();
            Object value = field.get(data);
            if (field.get(data) != null) {
                map.put(name, value.toString());
            }
        }


        return map;
    }
}

7,ThreadLocal

ThreadLocal is the global context within the thread. Is the memory shared between methods in a single thread. Each method can obtain and modify values from this context.

Actual case:

When calling the api, a token parameter will be passed. Usually, an interceptor will be written to verify whether the token is legal. We can find the corresponding user information (User) through the token. If the token is legal, then the user information will be stored in ThreadLocal, so that the user's information can be accessed at any layer of controller, service and dao. The scope is similar to the request scope in the Web.

In the traditional way, we need to access a variable in the method. We can pass parameters to the method in the form of parameters. If multiple methods are used, each method must pass parameters; If ThreadLocal is used, all methods do not need to pass this parameter. Each method can access this value through ThreadLocal.

  • ThreadLocalUtil.set("key", value); Save value

  • T value = ThreadLocalUtil.get("key"); Get value

ThreadLocalUtil

public class ThreadLocalUtil<T> {
    private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal() {
        @Override
        protected Map<String, Object> initialValue() {
            return new HashMap<>(4);
        }
    };


    public static Map<String, Object> getThreadLocal(){
        return threadLocal.get();
    }


    public static <T> T get(String key) {
        Map map = (Map)threadLocal.get();
        return (T)map.get(key);
    }


    public static <T> T get(String key,T defaultValue) {
        Map map = (Map)threadLocal.get();
        return (T)map.get(key) == null ? defaultValue : (T)map.get(key);
    }


    public static void set(String key, Object value) {
        Map map = (Map)threadLocal.get();
        map.put(key, value);
    }


    public static void set(Map<String, Object> keyValueMap) {
        Map map = (Map)threadLocal.get();
        map.putAll(keyValueMap);
    }


    public static void remove() {
        threadLocal.remove();
    }


    public static <T> Map<String,T> fetchVarsByPrefix(String prefix) {
        Map<String,T> vars = new HashMap<>();
        if( prefix == null ){
            return vars;
        }
        Map map = (Map)threadLocal.get();
        Set<Map.Entry> set = map.entrySet();


        for( Map.Entry entry : set){
            Object key = entry.getKey();
            if( key instanceof String ){
                if( ((String) key).startsWith(prefix) ){
                    vars.put((String)key,(T)entry.getValue());
                }
            }
        }
        return vars;
    }


    public static <T> T remove(String key) {
        Map map = (Map)threadLocal.get();
        return (T)map.remove(key);
    }


    public static void clear(String prefix) {
        if( prefix == null ){
            return;
        }
        Map map = (Map)threadLocal.get();
        Set<Map.Entry> set = map.entrySet();
        List<String> removeKeys = new ArrayList<>();


        for( Map.Entry entry : set ){
            Object key = entry.getKey();
            if( key instanceof String ){
                if( ((String) key).startsWith(prefix) ){
                    removeKeys.add((String)key);
                }
            }
        }
        for( String key : removeKeys ){
            map.remove(key);
        }
    }
}

Summary: This is some parameters and usage examples commonly used in the interaction of third-party data interfaces. I hope it will be helpful to you.

Of course, in order to ensure more security, RSA,RSA2, AES and other encryption methods can be added to ensure more security of data, but the only disadvantage is that encryption and decryption consume CPU resources.

Topics: architecture