How to design the API interface? How to ensure security? How to sign? How to prevent heavy?

In actual business, it is inevitable to interact and transmit data with third-party systems. So how to ensure the security of data during transmission (anti-theft)? In addition to the https protocol, can a general set of algorithms and specifications be added to ensure the security of transmission?

 

Below we will discuss some commonly used security methods of API design, which may not be the best, and there are more awesome implementation methods, but this is my own experience sharing.

 

1. Token introduction

 

Token: access token, used in the interface, used to identify the identity and credentials of the interface caller, and reduce the number of transmissions of user names and passwords. Generally, the client (interface caller) needs to first apply for an account for calling the interface from the server. The server will give an appId and a key. The key is used for parameter signing. Note that the key is saved to the client. Some security needs to be done. Treat to prevent leakage.

 

The value of Token is generally UUID. After the server generates the Token, it needs to use the token as the key, and save some information associated with the token as the value in the cache server (redis). When a request comes, the server will query the cache server. Whether this Token exists, the interface is called if it exists, and there is no return interface error, which is generally implemented through interceptors or filters. Tokens are divided into two types:

 

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

  • USER Token (user token): Used to access the interface that requires the user to log 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

 

Regarding the timeliness of tokens: tokens can be one-time or valid for a period of time, depending on business needs.

 

Under normal circumstances, the interface is best to use the https protocol. If the http protocol is used, the token mechanism is just a way to reduce the possibility of being hacked. In fact, it can only prevent the gentleman but not the villain.

 

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

 

timestamp: The timestamp is the current timestamp when the client calls the interface. The timestamp is used to prevent DoS attacks.

 

When a hacker hijacks the requested URL for a DoS attack, the interface will judge the difference between the current system time of the server and the timestamp passed in the interface every time the interface is called. If the difference exceeds a certain set time (say 5 minutes), Then this request will be intercepted. If it is within the set timeout period, the DoS attack cannot be prevented. The timestamp mechanism can only reduce the time of DoS attacks and shorten the attack time. If the hacker modifies the value of the timestamp, it can be processed through the sign signature mechanism.

 

From

 

DoS is the abbreviation of Denial of Service, that is, denial of service. Attacks that cause DoS are called DoS attacks, and their purpose is to make computers or networks 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 flaws of the network protocol implementation or directly using brutal means to cruelly exhaust the resources of the attacked object, the purpose is to make the target computer or network unable to provide normal services or resource access, so that the target system service system stops responding Even crash, and this attack does not include intrusion into the target server or target network device. These service resources include network bandwidth, file system space capacity, open processes or allowed connections. This kind of attack will lead to a lack of resources. No matter how fast the computer's processing speed, memory capacity, or network bandwidth is, the consequences of this attack cannot be avoided.

 

  • Pingflood: This attack sends a large number of ping packets to the destination host in a short period of time, causing network congestion or exhaustion of host resources.

     

  • Synflood: The attack sends SYN packets to the destination host with multiple random source host addresses, but does not respond after receiving the SYN ACK from the destination host. In this way, the destination host establishes a large number of connection queues for these source hosts, and These queues have been maintained without receiving ACKs, resulting in a large consumption of resources and unable to provide services to normal requests.

     

  • Smurf: The attack sends a packet with a specific request (such as an ICMP response request) to the broadcast address of a subnet, and disguise the source address as the address of the host that it wants to attack. All hosts on the subnet respond to the broadcast packet request and send packets to the attacked host, so that the host is attacked.

     

  • Land-based: The attacker sets the source address and destination address of a packet to the address of the target host, and then sends the packet to the attacked host through IP spoofing. This kind of packet can cause the attacked host to try to contact itself The connection is established and falls into an endless loop, which greatly reduces 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 subject to a Ping of Death attack, which will cause the host to crash.

     

  • Teardrop: When IP data packets are transmitted over the network, the data packets can be divided into smaller fragments. An attacker can achieve a TearDrop attack by sending two (or more) data 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 allocates extraordinarily huge resources, resulting in a lack of system resources and even a restart of the machine.

     

  • PingSweep: Use ICMP Echo to poll multiple hosts.

 

Three, sign introduction

 

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

 

sign: Generally used for parameter signatures to prevent illegal tampering of parameters. The most common is to modify important sensitive parameters such as amount. The value of sign is generally to sort all non-empty parameters in ascending order and then +token+key+timestamp+nonce (random Number) are spliced ​​together, and then encrypted using a certain encryption algorithm, and passed as a parameter sign in the interface, or sign can be put in the request header.

 

If the interface is hijacked by a hacker during network transmission, and the parameter value is modified, and then the interface is called again, although the parameter value is modified, the hacker does not know how the sign is calculated, or what the sign is. Value composition, I don’t know the order in which they are spliced ​​together. The most important thing is that you don’t know what the key in the signature string is, so hackers can tamper with the value of the parameter, but cannot modify the value of sign. When the server calls the interface before It will recalculate the value of sign according to the rules of sign and 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 not, it means that the parameter has been illegally tampered with, and the interface is not executed.

 

Four, prevent duplicate submission

 

For some important operations that need to be prevented from being repeatedly submitted 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, timeout and Timestamp The set difference is the same.

 

When the same request is accessed for the second time, it will first check whether the sign exists in redis. If it exists, it proves that it has been submitted repeatedly, and the interface will no longer be called. If the sign is deleted in the cache server because the expiration time has expired, when the url requests the server again, because the expiration time of the token and the expiration time of the sign have been constant, the sign expiration also means that the token expires, so the same url Re-visiting the server will be blocked due to token errors, which is why the expiration time of sign and token must be consistent. The mechanism of rejecting repeated calls ensures that the URL is intercepted by others and cannot be used (such as crawling data).

 

You can customize an annotation to mark which interfaces need to prevent duplicate submissions.

 

Note: If all security measures are used, it is sometimes unavoidable that they are too complicated. In actual projects, they need to be tailored according to their own conditions. For example, you can only use a signature mechanism to ensure that the information will not be tampered with, or only when you provide targeted services. Use the Token mechanism. How to cut depends on the actual situation of the project and the requirements for interface security.

 

Five, use process

 

1. The interface caller (client) applies for an interface call account from the interface provider (server). 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 server-side API token, where sign=encryption (appId + timestamp + key)

 

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

 

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

 

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

 

Six, sample code

 

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>

 

 

2. RedisConfiguration


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


    /**
     * 支持存储对象
     * @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;
    }
}


 

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), "参数错误");


        long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp);
        Assert.isTrue(reqeustInterval < 5 * 60 * 1000, "请求过期,请重新请求");


        // 1. 根据appId查询数据库获取appSecret
        AppInfo appInfo = new AppInfo("1", "12345678954556");


        // 2. 校验签名
        String signString = timestamp + appId + appInfo.getKey();
        String signature = MD5Util.encode(signString);
        log.info(signature);
        Assert.isTrue(signature.equals(sign), "签名错误");


        // 3. 如果正确生成一个token保存到redis中,如果错误返回错误信息
        AccessToken accessToken = this.saveToken(0, appInfo, null);


        return ApiResponse.success(accessToken);
    }




    @NotRepeatSubmit(5000)
    @PostMapping("user_token")
    public ApiResponse<UserInfo> userToken(String username, String password) {
        // 根据用户名查询密码, 并比较密码(密码可以RSA加密一下)
        UserInfo userInfo = new UserInfo(username, "81255cb0dca1a5f304328a70ac85dcbd", "111111");
        String pwd = password + userInfo.getSalt();
        String passwordMD5 = MD5Util.encode(pwd);
        Assert.isTrue(passwordMD5.equals(userInfo.getPassword()), "密码错误");


        // 2. 保存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();


        // token有效期为2小时
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.SECOND, 7200);
        Date expireTime = calendar.getTime();


        // 4. 保存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);
    }
}

 

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);
    }
}

5. TokenInterceptor


@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {


    @Autowired
    private RedisTemplate redisTemplate;


    /**
     *
     * @param request
     * @param response
     * @param handler 访问的目标方法
     * @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");
        // 随机字符串
        String nonce = request.getHeader("nonce");
        String sign = request.getHeader("sign");
        Assert.isTrue(!StringUtils.isEmpty(token) && !StringUtils.isEmpty(timestamp) && !StringUtils.isEmpty(sign), "参数错误");


        // 获取超时时间
        NotRepeatSubmit notRepeatSubmit = ApiUtil.getNotRepeatSubmit(handler);
        long expireTime = notRepeatSubmit == null ? 5 * 60 * 1000 : notRepeatSubmit.value();


        // 2. 请求时间间隔
        long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp);
        Assert.isTrue(reqeustInterval < expireTime, "请求超时,请重新请求");


        // 3. 校验Token是否存在
        ValueOperations<String, TokenInfo> tokenRedis = redisTemplate.opsForValue();
        TokenInfo tokenInfo = tokenRedis.get(token);
        Assert.notNull(tokenInfo, "token错误");


        // 4. 校验签名(将所有的参数加进来,防止别人篡改参数) 所有参数看参数名升续排序拼接成url
        // 请求参数 + 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, "签名错误");


        // 5. 拒绝重复调用(第一次访问时存储,过期时间和请求超时时间保持一致), 只有标注不允许重复提交注解的才会校验
        if (notRepeatSubmit != null) {
            ValueOperations<String, Integer> signRedis = redisTemplate.opsForValue();
            boolean exists = redisTemplate.hasKey(sign);
            Assert.isTrue(!exists, "请勿重复提交");
            signRedis.set(sign, 0, expireTime, TimeUnit.MILLISECONDS);
        }


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

6. MD5Util ----MD5 tool class, encrypt and 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;
    }
}

7. @NotRepeatSubmit -----Custom annotations to prevent repeated submissions.

/**
 * 禁止重复提交
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotRepeatSubmit {
    /** 过期时间,单位毫秒 **/
    long value() default 5000;
}

8. AccessToken

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


    /** 失效时间 */
    private Date expireTime;
}

 

9. AppInfo

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

10. TokenInfo

@Data
public class TokenInfo {
    /** token类型: api:0 、user:1 */
    private Integer tokenType;


    /** App 信息 */
    private AppInfo appInfo;


    /** 用户其他数据 */
    private UserInfo userInfo;
}

 

11. UserInfo

@Data
public class UserInfo {
    /** 用户名 */
    private String username;
    /** 手机号 */
    private String mobile;
    /** 邮箱 */
    private String email;
    /** 密码 */
    private String password;
    /** 盐 */
    private String salt;


    private AccessToken accessToken;


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

12. ApiCodeEnum

/**
 * 错误码code可以使用纯数字,使用不同区间标识一类错误,也可以使用纯字符,也可以使用前缀+编号
 *
 * 错误码:ERR + 编号
 *
 * 可以使用日志级别的前缀作为错误类型区分 Info(I) Error(E) Warning(W)
 *
 * 或者以业务模块 + 错误号
 *
 * TODO 错误码设计
 *
 * Alipay 用了两个code,两个msg(https://docs.open.alipay.com/api_1/alipay.trade.pay)
 */
public enum ApiCodeEnum {
    SUCCESS("10000", "success"),
    UNKNOW_ERROR("ERR0001","未知错误"),
    PARAMETER_ERROR("ERR0002","参数错误"),
    TOKEN_EXPIRE("ERR0003","认证过期"),
    REQUEST_TIMEOUT("ERR0004","请求超时"),
    SIGN_ERROR("ERR0005","签名错误"),
    REPEAT_SUBMIT("ERR0006","请不要频繁操作"),
    ;


    /** 代码 */
    private String code;


    /** 结果 */
    private String msg;


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


    public String getCode() {
        return code;
    }


    public String getMsg() {
        return msg;
    }
}

13. ApiResult

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResult {


    /** 代码 */
    private String code;


    /** 结果 */
    private String msg;
}

 

14. ApiUtil -------This is written with reference to Alipay's encryption algorithm. I copied it directly.

 

public class ApiUtil {


    /**
     * 按参数名升续拼接参数
     * @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]));
        // 按照key升续排序,然后拼接参数
        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 (k.equals("sign")) {
                continue;
            }
            if (paramterMap.get(k).trim().length() > 0) {
                // 参数值为空,则不参与签名
                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));
        // 按照key升续排序,然后拼接参数
        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) {
                // 参数值为空,则不参与签名
                sb.append(k).append("=").append(paramterMap.get(k).trim()).append("&");
            }
        }
        return sb.toString();
    }


    /**
     * 获取方法上的@NotRepeatSubmit注解
     * @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;
    }
}

 

15. ApiResponse

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


    /** 数据 */
    private T data;


    /** 签名 */
    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 查询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 反射的对象,获取对象的字段名和值
     * @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;
    }
}

 

七、ThreadLocal

 

ThreadLocal is the global context within the thread. It is the memory shared between methods in a single thread, and each method can get values ​​and modify values ​​from the context.

 

actual case:

 

When calling the api, a token parameter is passed. Usually, an interceptor is 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 store the user information in ThreadLocal. The user's information can be accessed regardless of the level of controller, service, or dao. The role is similar to the request scope in the Web.

 

In the traditional way, we need to access a certain variable in the method. You can pass parameters to the method in the form of passing parameters. If multiple methods are to be used, then each method must pass parameters; if you use ThreadLocal all methods do not need to pass this Parameters, each method can access the value through ThreadLocal.

 

  • ThreadLocalUtil.set("key", value); 保存值

  • T value = ThreadLocalUtil.get("key"); Get the 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 commonly used parameters and usage examples in the interactive process of third-party data interfaces at present, I hope to help you a little bit.

 

Of course, if you want to ensure more security, you can add RSA, RSA2, AES, etc. encryption methods to ensure more security of the data, but the only disadvantage is that encryption and decryption consume more CPU resources.

Guess you like

Origin blog.csdn.net/mrlin6688/article/details/106069139