スパイク機能(9)セキュリティの最適化3

アンチ制限ブラシインタフェース

(例えば1秒)のブラシのウェブアクセスを短時間で(例えば数百倍など)何回も、悪意のあるユーザーを防ぐために、我々は、抗ブラシを制限し、このインターフェイスを最適化しています。

暫定版(Redisの)

アイデアは次のとおりです。数秒でサイト内のスパイク制限ユーザアクセス数回。(このようなわずか5秒でサイトを5回にアクセスできるユーザーを制限するなど)

コードのロジック:

  1. ユーザーがクリックすると、これは訪問のか数を決定するにはあまりにも多くのだろう前に「即時スパイク」ボタンは、最初の訪問/ miaosha /パスパスは、ランダムなスパイクアドレスを取得します。
  2. ユーザーが最初にアクセスしたときのRedis今すぐ設定:5秒(Redisの独自の属性によって、時間制限のセット)に1回、有効なセットを。
  3. クエリは、ユーザーの数が5倍未満であることが判明した場合Redisの5倍以上の直接的なエラーが返された場合、その後、アップデート、スパイクへのリンクをRedisの。

MiaoshaController関連するメソッドコード:

	@RequestMapping(value = "/path", method = RequestMethod.GET)
    @ResponseBody
    public Result<String> getMiaoshaPath(HttpServletResponse response, HttpServletRequest request, @RequestParam("goodsId") long goodsId,
                                         @CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
                                         @RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken,
                                         @RequestParam(value = "verifyCode", defaultValue = "0") int verifyCode) {
        if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
            return Result.error(CodeMsg.SESSION_ERROR);//token不存在或失效
        }
        String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
        MiaoshaUser user = userService.getByToken(response, token);//从token中读用户信息

        //查询访问的次数(5秒中访问不超过5次)
        String uri = request.getRequestURI();
        String key = uri + "_" + user.getId();
        Integer count = redisService.get(AccessKey.access,key,Integer.class);
        if (count == null){
            redisService.set(AccessKey.access,key,1); //时间设的5秒
        }else if (count < 5){ //这样的设置是指5秒内访问5次是正常,否则返回失败
            redisService.incr(AccessKey.access,key);
        }else {
            return Result.error(CodeMsg.ACCESS_LIMIT_REACHED);
        }


        //验证码校验
        boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
        if (!check) {
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }
        String path = miaoshaService.createMiaoshaPath(user, goodsId);
        return Result.success(path);
    }

前記アクセスキーは、次のように定義されて関与しました。

public class AccessKey extends BasePrefix {

    private AccessKey(int expireSeconds, String prefix) {
        super(expireSeconds, prefix);
    }

    public static AccessKey withExpire(int expireSeconds) {
        return new AccessKey(expireSeconds, "access");
    }

    public static AccessKey access = new AccessKey(5,"access");
}

この方法では、需要が5回に5秒の制限アクセスインターフェイス/パスにある場合、問題は、一般的に十分であり、かつ10秒別のインターフェイスなどで8回を訪問し、このような要求は、一般的に適用この方法を使用することはできません。したがって、より一般的な方法を以下に説明しました。

インターセプタバージョン

私たちは、あなたが注釈/インプリメンテーション・インターセプターを使用することができ、上記の機能を実現するために、より一般的なバージョンが必要です。

ユーザーが前に傍受されているので、ユーザーは、「即時スパイク」、アドレスを確認するために、実際のスパイク要求に最初に書き換えgetMiaoshaPath MiaoshaController層そう()メソッドをクリックします。

//第二版:通过注解限流
    @AccessLimit(seconds = 5, maxCount = 5, needLogin = true)
    @RequestMapping(value = "/path", method = RequestMethod.GET)
    @ResponseBody
    public Result<String> getMiaoshaPath(HttpServletResponse response, @RequestParam("goodsId") long goodsId,
                                         @CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
                                         @RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken,
                                         @RequestParam(value = "verifyCode", defaultValue = "0") int verifyCode) {
        if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
            return Result.error(CodeMsg.SESSION_ERROR);//token不存在或失效
        }
        String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
        MiaoshaUser user = userService.getByToken(response, token);//从token中读用户信息

        //验证码校验
        boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
        if (!check) {
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }
        String path = miaoshaService.createMiaoshaPath(user, goodsId);
        return Result.success(path);
    }

@AccessLimit:上記のみカスタムアノテーションを追加

  1. 以下は、最初のアノテーションを定義する必要があります。
@Retention(RUNTIME)  //运行时
@Target(METHOD)  //作用于方法体上
public @interface AccessLimit {
    //三个参数
    int seconds();

    int maxCount();

    boolean needLogin() default true;
}
  1. インターセプタを実装
@Service
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 true;
            }
            int seconds = accessLimit.seconds();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();
            String key = request.getRequestURI();
            if (needLogin) {
                if (user == null) { //不进入页面
                    render(response, CodeMsg.SESSION_ERROR);
                    return false;
                }
                key += "_" + user.getId();
            } else {
                //do nothing
            }
            AccessKey ak = AccessKey.withExpire(seconds);
            Integer count = redisService.get(ak, key, Integer.class);
            if (count == null) {
                redisService.set(ak, key, 1);
            } else if (count < maxCount) {
                redisService.incr(ak, key);
            } else {
                render(response, CodeMsg.ACCESS_LIMIT_REACHED);
                return false;
            }
        }
        return true;
    }

    private void render(HttpServletResponse response, CodeMsg cm) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str = JSON.toJSONString(Result.error(cm));
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();
    }

    private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) {
        String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
        String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);
        if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
            return null;
        }
        String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
        return userService.getByToken(response, token);
    }

    private String getCookieValue(HttpServletRequest request, String cookiName) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null || cookies.length <= 0) {
            return null;
        }
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals(cookiName)) {
                return cookie.getValue();
            }
        }
        return null;
    }

}

HandlerInterceptorAdapter継承インターセプタは、特定のプロセスである:
1)トークンから(ユーザ情報を取得するために)、ユーザはThreadLocal変数に設定され、それはスレッドのローカルコピーであり、かつ結合糸は、アクセスがネイティブ・スレッドにアクセスするだけであろう;及び
、注釈needloginは、ユーザが着弾したかどうかを決定する必要がある場合2)、注釈プロセスを取得する
3)Redisの組成からユーザIDとURLからキーを除去し、設定回数以上か否かを大きく決定します。

次のようにここで、UserContextが定義しました:

public class UserContext {

    private static ThreadLocal<MiaoshaUser> userHolder = new ThreadLocal<MiaoshaUser>();

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

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

}
  1. インターセプターは、WebConfigに、このクラスが実装さWebMvcConfigurerに登録されています。
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    AccessInterceptor accessInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(accessInterceptor);
    }

}

これは、インターフェイスのセキュリティの最適化を完了します。
スパイクプロジェクトが終わりました。

公開された33元の記事 ウォンの賞賛9 ビュー8714

おすすめ

転載: blog.csdn.net/Serena0814/article/details/90173055