SpringBoot秒杀系统实战24-安全优化 接口限流防刷
接口限流防刷:
限制同一个用户一秒钟或者一分钟之内只能访问固定次数,在服务端对系统做一层保护。
思路:利用缓存实现,用户每次点击之后访问接口的时候,在缓存中生成一个计数器,第一次将这个计数器置1后存入缓存,并给其设定有效期,比如一分钟,一分钟之内再访问,那么数值加一。一分钟之内访问次数超过限定数值,直接返回失败。下一个一分钟,数据重新从0开始计算。因为缓存具有一个有效期,一分钟之后自动失效。
- 获取访问路径
- 拼接用户用户的Id作为一个记录该用户访问次数的key
- 缓存里面取得该key,做判断
如果缓存里面没有取到,代表是第一次访问,所以给缓存设置该key,并设置初始值value为1
如果缓存里面取得值并且小于5,那么直接将该key对应的值value+1
如果缓存里面的次数大于超过4(>=5),那么代表在限制时间内(在缓存还没有失效的时间内),访问次数达到限制
getMiaoshaPath代码:
[code]@RequestMapping(value ="/getPath") @ResponseBody public Result<String> getMiaoshaPath(HttpServletRequest request,Model model,MiaoshaUser user, @RequestParam("goodsId") Long goodsId, @RequestParam(value="vertifyCode",defaultValue="0") int vertifyCode) { model.addAttribute("user", user); //如果用户为空,则返回至登录页面 if(user==null){ return Result.error(CodeMsg.SESSION_ERROR); } //限制访问次数 String uri=request.getRequestURI(); String key=uri+"_"+user.getId(); //限定key5s之内只能访问5次 Integer count=redisService.get(AccessKey.access, key, Integer.class); if(count==null) { redisService.set(AccessKey.access, key, 1); }else if(count<5) { redisService.incr(AccessKey.access, key); }else {//超过5次 return Result.error(CodeMsg.ACCESS_LIMIT); } //验证验证码 boolean check=miaoshaService.checkVCode(user, goodsId,vertifyCode ); if(!check) { return Result.error(CodeMsg.REQUEST_ILLEAGAL); } System.out.println("通过!"); //生成一个随机串 String path=miaoshaService.createMiaoshaPath(user,goodsId); return Result.success(path); }
新建一个AccessKey作为访问限制的Key,设置一个固定有效期和一个动态设置有效期的Key前缀对象。
[code]public class AccessKey extends BasePrefix{ //考虑页面缓存有效期比较短 public AccessKey(int expireSeconds,String prefix) { super(expireSeconds,prefix); } //限制5s之内访问5次 public static AccessKey access=new AccessKey(5,"access"); //动态设置有效期 public static AccessKey expire(int expireSeconds) { return new AccessKey(expireSeconds,"access"); } }
优化:如何做一个通用的限流防刷逻辑?
思路:
每个方法都需要该判断功能,那么把它抽出来,定义一个拦截器,利用拦截器来拦截这些请求,判断次数,进行操作。
新建一个注解
[code] @AccessLimit(seconds = 5,maxCount = 5,needLogin = true)
1、 新建注解,用于限流作用(在固定时间内限制访问次数)
[code]@Retention(RetentionPolicy.RUNTIME)//运行期间有效 @Target(ElementType.METHOD)//注解类型为方法注解 public @interface AccessLimit { int seconds(); //固定时间 int maxCount();//最大访问次数 boolean needLogin() default true;// 用户是否需要登录 }
2、实现拦截器,自定义AccessInterceptor继承HandlerInterceptorAdapter拦截器基类,通过实现这个接口,拿到方法上的注解
- 判断用户登录
这里将之前原先定义在解析用户参数的代码封装。然后在将用这个封装的用户信息,set到ThreadLocal 中,本地线程副本,该变量与线程绑定,存取只会存取在本地线程中。然后之前获取用户的代码直接取到该用户即可。 - 判断访问次数与失效时间(缓存时间)
判断访问次数count ,从缓存中存取,然后根据注解时间,动态设置缓存的过期时间。
[code]@Service public class AccessInterceptor extends HandlerInterceptorAdapter{ @Autowired MiaoshaUserService miaoshaUserService; @Autowired RedisService redisService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(handler instanceof HandlerMethod) { //先去取得用户做判断 MiaoshaUser user=getUser(request,response); System.out.println("@AccessInterceptor---user"+user); //将user保存下来 UserContext.setUser(user); HandlerMethod hm=(HandlerMethod)handler; AccessLimit aclimit=hm.getMethodAnnotation(AccessLimit.class); //无该注解的时候,那么就不进行拦截操作 if(aclimit==null) { return true; } //获取参数 int seconds=aclimit.seconds(); int maxCount=aclimit.maxCount(); boolean needLogin=aclimit.needLogin(); String key=request.getRequestURI(); System.out.println("------------:"+key); if(needLogin) { if(user==null) { //需要给客户端一个提示 render(response,CodeMsg.SESSION_ERROR); return false; } //需要的登录 key+="_"+user.getId(); }else {//不需要登录 //不需要操作 } //限制访问次数 String uri=request.getRequestURI(); //String key=uri+"_"+user.getId(); //限定key5s之内只能访问5次,动态设置有效期 AccessKey akey=AccessKey.expire(seconds); Integer count=redisService.get(akey, key, Integer.class); if(count==null) { redisService.set(akey, key, 1); }else if(count<maxCount) { redisService.incr(akey, key); }else {//超过5次 //Result.error(CodeMsg.ACCESS_LIMIT); render(response,CodeMsg.ACCESS_LIMIT); //结果给前端 return false; } } return super.preHandle(request, response, handler); } private void render(HttpServletResponse response, CodeMsg cm) throws IOException { //指定输出的编码格式,避免乱码 response.setContentType("application/json;charset=UTF-8"); OutputStream out=response.getOutputStream(); String jres=JSON.toJSONString(Result.error(cm)); out.write(jres.getBytes("UTF-8")); out.flush(); out.close(); } private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) { String paramToken=request.getParameter(MiaoshaUserService.COOKIE1_NAME_TOKEN); String cookieToken=getCookieValue(request,MiaoshaUserService.COOKIE1_NAME_TOKEN); if(StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken)) { return null; } String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken; MiaoshaUser user=miaoshaUserService.getByToken(token,response); return user; } public String getCookieValue(HttpServletRequest request, String cookie1NameToken) {//COOKIE1_NAME_TOKEN-->"token" //遍历request里面所有的cookie Cookie[] cookies=request.getCookies(); if(cookies!=null) { for(Cookie cookie :cookies) { if(cookie.getName().equals(cookie1NameToken)) { System.out.println("getCookieValue:"+cookie.getValue()); return cookie.getValue(); } } } System.out.println("No getCookieValue!"); return null; } }
UserContext 封装用户信息:
public class UserContext {
private static ThreadLocal userHolder=new ThreadLocal();
[code]public static void setUser(MiaoshaUser user) { userHolder.set(user); } public static MiaoshaUser getUser() { return userHolder.get(); } }
3、 将拦截器 注册到WebConfig中,这个类继承WebMvcConfigurerAdapter ,Spring框架的配置类。
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
[code]@Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { //将UserArgumentResolver注册到config里面去 argumentResolvers.add(userArgumentResolver); } /** * 注册拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { //注册 registry.addInterceptor(accessInterceptor); super.addInterceptors(registry); } }
- 【SpringBoot商城秒杀系统项目实战24】安全优化 接口限流防刷
- 【SpringBoot商城秒杀系统项目实战22】安全优化 秒杀接口地址隐藏
- SpringBoot秒杀系统实战22-安全优化 秒杀接口地址隐藏
- 【SpringBoot商城秒杀系统项目实战23】安全优化 数学图形验证码
- 【SpringBoot商城秒杀系统项目实战20】高并发秒杀接口优化
- SpringBoot秒杀系统实战21-高并发秒杀系统接口优化 RabbitMQ异步下单...
- 【SpringBoot商城秒杀系统项目实战21】高并发秒杀系统接口优化 RabbitMQ异步下单
- 【SpringBoot商城秒杀系统项目实战18】页面优化技术-商品详情页面静态化(前后端分离)
- 【SpringBoot商城秒杀系统项目实战17】页面优化技术(页面缓存+URL缓存+对象缓存)
- SpringBoot秒杀系统实战18-页面优化技术-商品详情页面静态化(前后端分离)...
- 【SpringBoot商城秒杀系统项目实战19】秒杀静态化+订单详情静态化
- SSM实战——秒杀系统之DAO层实体定义、接口设计、mybatis映射文件编写、整合Spring与Mybatis
- 【SpringBoot商城秒杀系统项目实战08】两次MD5加密设计
- SSM实战——秒杀系统之Service层接口设计与实现、Spring托管、声明式事务
- 【SpringBoot商城秒杀系统项目实战02】优雅代码编写之封装json输出
- SpringBoot秒杀系统实战19-秒杀静态化+订单详情静态化
- Java秒杀系统实战系列~构建SpringBoot多模块项目
- 【SpringBoot商城秒杀系统项目实战03】集成Thymeleaf做页面模板
- 最新基于SpringBoot Java商城秒杀系统的设计与实战教程
- 【SpringBoot商城秒杀系统项目实战06】安装与集成redis