接口的安全优化注意包括三个方面:
- 接口地址的隐藏
- 接口验证码机制
- 接口限流
一,接口地址的隐藏
接口地址隐藏的作用:防止恶意的脚本请求,如果直接暴露接口地址,脚本的请求破坏即损坏了用户体验又增加了服务器压力
隐藏接口地址的实现过程:
例:代码演示
① 前端
//暴露接口 function interface() { var id = $("#seckid").val(); var code = $("#captcha").val() $.ajax({ url: "/tSeckillOrder/Seckillfalse/"+id+"/"+code, type: "GET", dataType: "json", success : function (datas) { if(datas.code==200){ interfaceaddr(datas.obj.path); } } }) } //调用隐藏接口 function interfaceaddr(path) { var id = $("#seckid").val(); var datas= { goodsid:id } $.ajax({ url: path, type: "POST", dataType: "json", data: datas, success : function (datas) { if(datas.code==200){ alert("成功") }else { alert("失败") } }
② 地址码
@Service public class Interfacehidden { @Autowired RedisTemplate redisTemplate; //创建隐藏接口的地址并保存 public String createinterfaceaddr(String username,int goodid){ String substring = Md5Hash.fromBase64String(UUID.randomUUID() + "123456").toHex().toString().substring(0, 15); redisTemplate.opsForValue().set(username+":"+goodid,substring,60, TimeUnit.SECONDS); return substring; } //效验 public Boolean checkoutinterfaceaddr(String username,int goodid,String md5random){ String random= (String) redisTemplate.opsForValue().get(username + ":" + goodid); if(md5random.equals(random)){ return true; } return false; }
③ 暴露的接口
//暴露的接口 @GetMapping("/Seckillfalse/{id}") @ResponseBody public ResposeBeanCode seckaddre(HttpSession httpSession, @PathVariable("id") int goodsid) { //获取用户名 String user = (String)httpSession.getAttribute("user"); String createinterfaceaddr = interfacehidden.createinterfaceaddr(user, goodsid); String path = "/tSeckillOrder/" +createinterfaceaddr+"/seckillorder"; HashMap<String,String> map = new HashMap(); map.put("path",path); return ResposeBeanCode.addresssuccess(map) ; }
④ 隐藏的接口
//隐藏的接口 @PostMapping("/{ran}/seckillorder") @ResponseBody public ResposeBeanCode seckillorder(@RequestParam("goodsid") int goodsid,@PathVariable("ran") String ran,HttpSession httpSession){ String user = (String) httpSession.getAttribute("user"); Boolean checkoutinterfaceaddr = interfacehidden.checkoutinterfaceaddr(user, goodsid, ran); if(!checkoutinterfaceaddr){ System.out.println("checkoutinterfaceaddr:"+checkoutinterfaceaddr); return ResposeBeanCode.error(); } boolean seckillserver = seckillOrderService.seckillserver(goodsid, user); if(!seckillserver){ return ResposeBeanCode.error(); } return ResposeBeanCode.success(); }
二,接口验证码机制
验证码使用git上的easy-captcha开源项目:
<dependency> <groupId>com.github.whvcse</groupId> <artifactId>easy-captcha</artifactId> <version>1.6.2</version> </dependency>
接口验证码的作用:
- 为接口加一层安全防护,更进一步防止脚本刷链接
- 再一定程度上可以削峰,输入效验码的过程可以对流量进行分流
接口验证机制实现的过程:和接口隐藏差不多,只是中间加入了效验码
例:代码演示,再接口地址隐藏的基础上改进
①效验码
@RequestMapping("/seckill") public class CheckCode { @Autowired RedisTemplate redisTemplate; @RequestMapping("/captcha/{id}") public void captcha(@PathVariable("id") String id, HttpServletRequest request, HttpServletResponse response) throws Exception { // 设置请求头为输出图片类型 response.setContentType("image/gif"); response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); // 三个参数分别为宽、高、位数 SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5); // 设置字体 specCaptcha.setFont(new Font("Verdana", Font.PLAIN, 32)); // 有默认字体,可以不用设置 // 设置类型,纯数字、纯字母、字母数字混合 specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER); //获取id和用户 String user_id = request.getSession().getAttribute("user")+"_fq:"+id; //以用户名和id键,值为验证码保存到redis中 redisTemplate.opsForValue().set(user_id,specCaptcha.text(),300, TimeUnit.SECONDS); // 输出图片流 specCaptcha.out(response.getOutputStream()); } }
②前端
<img id="captchImg" height="32" width="130" onclick="refresshCaptcha()"/> function interface() { var id = $("#seckid").val(); var code = $("#captcha").val(); if(code!=""){ $.ajax({ url: "/tSeckillOrder/Seckillfalse/"+id+"/"+code, type: "GET", dataType: "json", success : function (datas) { if(datas.code==200){ interfaceaddr(datas.obj.path); } }else { alert("验证码错误")} }} })}else{ alert("验证码不能为空") } }
③接口
@GetMapping("/Seckillfalse/{id}/{verificat}") @ResponseBody public ResposeBeanCode seckaddre(HttpSession httpSession, @PathVariable("id") int goodsid,@PathVariable("verificat") String verificat) { //获取用户名 String user = (String)httpSession.getAttribute("user"); //验证码判断 boolean b = verificationCode.VerificationC(user, goodsid, verificat); if(!b){ return ResposeBeanCode.verificationerror(); } String createinterfaceaddr = interfacehidden.createinterfaceaddr(user, goodsid); String path = "/tSeckillOrder/" +createinterfaceaddr+"/seckillorder"; HashMap<String,String> map = new HashMap(); map.put("path",path); return ResposeBeanCode.addresssuccess(map) ; }
三,接口限流机制
接口限流是高并发系统的一个常见的处理方法,对访问流量进行限制,让系统处于一个最安全的处理环境
接口限流主要有两种:
- 控制并发访问的数量:最简单粗暴的方法,设置最大访问数量超过访问数量就直接拒绝请求
- 控制并发访问的速率:这个是企业开发最常用的方式,它主要有两种算法,漏桶算法和令牌桶算法
自定义控制访问数量来实现限流:
- 依赖SpringMvc的拦截器,对指定接口进行拦截,判断控制并发量
- 使用Redis保存数量,判断是否超过限制标准
- 加入没有超过就返回true放行,假如为flase就拦截
例:
① 自定义限流注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentAnnotation { int second();//redis中的过期时间 int maxcount();//最大流量限制数量 }
② 编写拦截器
@Component public class MyHandlerInterceptor implements HandlerInterceptor { @Autowired RedisTemplate redisTemplate; @Autowired UserContext userContext; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(handler instanceof HandlerMethod){ HandlerMethod handlerMethod = (HandlerMethod) handler; //反射获取CurrentAnnotation注解,handlerMethod在方法上寻找标注CurrentAnnotation的方法 CurrentAnnotation annotation = handlerMethod.getMethodAnnotation(CurrentAnnotation.class); if(annotation==null){ return true; } int second = annotation.second(); int maxcount = annotation.maxcount(); String key = request.getRequestURI(); String name = (String) request.getSession().getAttribute("user"); Object count = redisTemplate.opsForValue().get(name + ":" + key); if(count.toString().equals("")){ redisTemplate.opsForValue().set(name + ":" + key,1,second, TimeUnit.SECONDS); }else if((Integer)count>=maxcount){ userContext.render(response, ResposeBeanCode.currentlimit()); return false; }else{ redisTemplate.opsForValue().increment(name + ":" + key); return true; } } return true; } }
③ 将拦截器添加到WebMvcConfigurer组件定制化中
@Configuration public class MyWebMvcCofig implements WebMvcConfigurer { //组件定制化 @Autowired MyHandlerInterceptor myHandlerInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myHandlerInterceptor); } }
④ 将注解标注再接口上
@PostMapping("/{ran}/seckillorder") @CurrentAnnotation(second = 100,maxcount = 5) @ResponseBody public ResposeBeanCode seckillorder(@RequestParam("goodsid") int goodsid,@PathVariable("ran") String ran,HttpSession httpSession){ String user = (String) httpSession.getAttribute("user"); Boolean checkoutinterfaceaddr = interfacehidden.checkoutinterfaceaddr(user, goodsid, ran); if(!checkoutinterfaceaddr){ System.out.println("checkoutinterfaceaddr:"+checkoutinterfaceaddr); return ResposeBeanCode.error(); } boolean seckillserver = seckillOrderService.seckillserver(goodsid, user); if(!seckillserver){ return ResposeBeanCode.error(); } return ResposeBeanCode.success(); }
Comments | NOTHING
Warning: Undefined variable $return_smiles in /www/wwwroot/wql_luoqin_ltd/wp-content/themes/Sakura/functions.php on line 1109