接口的安全优化注意包括三个方面:
- 接口地址的隐藏
- 接口验证码机制
- 接口限流
一,接口地址的隐藏
接口地址隐藏的作用:防止恶意的脚本请求,如果直接暴露接口地址,脚本的请求破坏即损坏了用户体验又增加了服务器压力
隐藏接口地址的实现过程:
例:代码演示
① 前端
//暴露接口
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