Java spike system solution optimizes high-performance and high-concurrency combat, learning notes (9)

Hello everyone, I’m just 方圆
talking about the final security optimization


1. Dynamic spike address

1.1 Front-end changes

When we implemented the spike before, we jumped directly to the spike interface, so that our spike address is the same every time, which has a security risk, so we changed it to a dynamic address and jumped by writing a method on the front end Turn as shown below.

  • It will jump to first /miaosha/path, get the one in the spike address, path值and store it in RedisInsert picture description here
  • Then carry path值to access the real spike method, compare the pathvalue with the value in Redis, and then continue to spike.
    Insert picture description here

1.2 Java code to get the path

    @ResponseBody
    @RequestMapping(value = "/path",method = RequestMethod.GET)
    public Result<String> getMiaoshaPath(MiaoShaUser user,@RequestParam("goodsId")long goodsId,
                                         @RequestParam(value = "verifyCode",defaultValue = "0")int verifyCode){
    
    
        if(user == null)
            return Result.error(CodeMsg.SESSION_ERROR);


        String path = miaoshaService.createMiaoshaPath(user,goodsId);

        return Result.success(path);
    }
  • First call the createMiaoshaPath() method, in which a string of random values ​​will be created and stored in Redis. The specific method is as follows. After execution, the path value is returned to the front end
    public String createMiaoshaPath(MiaoShaUser user, long goodsId) {
    
    
        if(user == null || goodsId <= 0)
            return null;

        String str = MD5Util.md5(UUIDUtil.getUUID());
        redisService.set(MiaoshaKey.miaoshaPathPrefix,user.getId() + "_" + goodsId,str);

        return str;
    }

1.3 Modification of the execution spike interface

Insert picture description here

  • On the path, we adopt the RestFul style, get the path value through the @PathVariable annotation, and compare it with the value in the redis server, and then proceed to the next step if they are consistent

2. Add verification code verification

We add a verification code to the seckill button immediately, 防止机器人对我们的系统进行多次秒杀and you can also enable the seckill to 错峰访问reduce the amount of concurrency. What we use isScriptEngine

2.1 Implementation process

  1. First of all, we added the verification code verification step
    Insert picture description here
    in the path acquisition. In this method, the verification code obtained from the front end is verified with the verification code stored in Redis. After the verification is completed, it is removed from Redis Remove, the method code is as follows
    Insert picture description here
  2. Before that, the front-end verification code will respond with the back-end, and every time the verification code is refreshed, the correct result will be synchronized to the server's Redis.
    Insert picture description here

3. Interface current limit and anti-brush

  • The function of interface current limiting and anti-brushing is to access a fixed number of times within a specified time. Our idea is to add annotations to the method to limit the anti-brushing, and limit the number of accesses through the interceptor

3.1 Create this annotation

This comment contains the number of visits within the required visit time, as well as to determine whether to log in

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {
    
    
    int seconds();
    int maxCount();
    boolean needLogin() default true;
}
  • @Retention(RetentionPolicy.RUNTIME): The annotation is not only saved in the class file, but still exists after the jvm loads the class file;
  • @Target(ElementType.METHOD): Indicates that the annotation is modified by the method

Mark the method we want to limit
Insert picture description here

3.2 Create an interceptor

public class AccessInterceptor extends HandlerInterceptorAdapter {
    
    

    @Autowired
    MiaoShaUserService userService;
    @Autowired
    RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    

        if(handler instanceof HandlerMethod){
    
    
            MiaoShaUser user  = getUser(request,response);
            UserContext.setUser(user);
            HandlerMethod hm = (HandlerMethod) handler;
            //处理方法的对象,获取的是方法的注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if(accessLimit == null){
    
    
                return false;
            }
            int seconds = accessLimit.seconds();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();
            String key = request.getRequestURI();//获取请求的地址
            if (needLogin) {
    
    
                if(user == null){
    
    
                    //user为空,递交错误信息
                    render(response, CodeMsg.SESSION_ERROR);
                    return false;
                }
                key += "_" + user.getId();
            }
            AccessKey accessKey = AccessKey.withExpireSecond(seconds);
            Integer count = redisService.get(accessKey, key, Integer.class);
            if(count == null){
    
    
                redisService.set(accessKey,key,1);
            }else if(count < maxCount){
    
    
                redisService.incr(accessKey,key);
            }else{
    
    
                render(response,CodeMsg.ACCESS_LIMIT_REACHED);
                return false;
            }
        }
        return true;
    }
	......
}
  • Inherit HandlerInterceptorAdapter, override preHandlemethod
  • The important UserContext
    Insert picture description here
    let's look at the specific implementation
public class UserContext {
    
    

    //用ThreadLocal来装user信息,调用它的set和get方法,向其中存储值
    //ThreadLocal是为当前线程存储值,所以,在多线程下,各个线程的user并不冲突
    private static ThreadLocal<MiaoShaUser> userHolder = new ThreadLocal<>();

    public static void setUser(MiaoShaUser user){
    
    
        userHolder.set(user);
    }

    public static MiaoShaUser getUser(){
    
    
        return userHolder.get();
    }
}

The source code of ThreadLocal() is as follows
Insert picture description here

3.3 Explanation of subsequent steps

The method is relatively simple
Insert picture description here

3.4 Don't forget to configure, no configuration is equivalent to no interceptor

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
    
    

    @Autowired
    UserArgumentResolver userArgumentResolver;
    @Autowired
    AccessInterceptor accessInterceptor;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    
    
        super.addArgumentResolvers(argumentResolvers);
        argumentResolvers.add(userArgumentResolver);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        InterceptorRegistration interceptorRegistration = registry.addInterceptor(accessInterceptor);
        interceptorRegistration.addPathPatterns("/miaosha/path");

    }
}

Insert picture description here

In this configuration class, we rewrite the addInterceptors method to inject the interceptor into the configuration,(The step of specifying the address to be intercepted can be omitted, because we are using the annotation mark, and there is a mistake in the front. If there is no annotation at the beginning, return false, so that the global is intercepted, it should be written as true, so Can be released), Then you can use it!


The project is over!

Guess you like

Origin blog.csdn.net/qq_46225886/article/details/107416625