アンチ制限ブラシインタフェース
(例えば1秒)のブラシのウェブアクセスを短時間で(例えば数百倍など)何回も、悪意のあるユーザーを防ぐために、我々は、抗ブラシを制限し、このインターフェイスを最適化しています。
暫定版(Redisの)
アイデアは次のとおりです。数秒でサイト内のスパイク制限ユーザアクセス数回。(このようなわずか5秒でサイトを5回にアクセスできるユーザーを制限するなど)
コードのロジック:
- ユーザーがクリックすると、これは訪問のか数を決定するにはあまりにも多くのだろう前に「即時スパイク」ボタンは、最初の訪問/ miaosha /パスパスは、ランダムなスパイクアドレスを取得します。
- ユーザーが最初にアクセスしたときのRedis今すぐ設定:5秒(Redisの独自の属性によって、時間制限のセット)に1回、有効なセットを。
- クエリは、ユーザーの数が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:上記のみカスタムアノテーションを追加
- 以下は、最初のアノテーションを定義する必要があります。
@Retention(RUNTIME) //运行时
@Target(METHOD) //作用于方法体上
public @interface AccessLimit {
//三个参数
int seconds();
int maxCount();
boolean needLogin() default true;
}
- インターセプタを実装
@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();
}
}
- インターセプターは、WebConfigに、このクラスが実装さWebMvcConfigurerに登録されています。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
AccessInterceptor accessInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessInterceptor);
}
}
これは、インターフェイスのセキュリティの最適化を完了します。
スパイクプロジェクトが終わりました。