接口幂等性处理(接口重复调用 || 表单重复提交 || 防止csrf 攻击)

幂等性: 接口重复调用(http延时机制) 及 表单重复提交
防止csrf 攻击 (模拟请求攻击)

优缺点说明

本篇突出特点:AOP 拦截,自定义注解思想
自定义注解AOP 拦截思想
1、拦截通知,处理重复调用
2、rpc 接口安全加密可以统一拦截验证加密串是否有效

缺点:
1、rpc 验证太麻烦,api 接口需先掉生成token 接口
2、因为是 rpc 验证,不适应于前后端分离项目
3、表单验证太繁琐,可以使用session保存 / 可以看我的另外一篇文章接口幂等性处理(表单重复提交–简单处理)https://blog.csdn.net/qq_41463655/article/details/100176081

正题–接口重复调用

项目demo:
链接:https://pan.baidu.com/s/1f6RvqX5mnGDKj4Zp9wcdNw
提取码:nc6k

效果展示:

  • 访问 /test 每次调用生成唯一token
    接口幂等性处理(接口重复调用 || 表单重复提交 || 防止csrf 攻击)_第1张图片
  • 提交form删除唯一token,接口幂等性处理(接口重复调用 || 表单重复提交 || 防止csrf 攻击)_第2张图片
  • 点击刷新按钮
    接口幂等性处理(接口重复调用 || 表单重复提交 || 防止csrf 攻击)_第3张图片
  • 防止了重复提交问题接口幂等性处理(接口重复调用 || 表单重复提交 || 防止csrf 攻击)_第4张图片
    远程rpc接口同理,token 放入 http 请求头
    在此处判断是form 还是 远程rpc调用即可
    接口幂等性处理(接口重复调用 || 表单重复提交 || 防止csrf 攻击)_第5张图片

一、搭建缓存

使用memcached缓存,详情看下方地址,推荐使用redis, 也可以使用 Map集合直接缓存到Jvm
https://www.cnblogs.com/wujuntian/p/4791220.html

本篇所有 maven 相关依赖

  
		 
			org.springframework.boot
			spring-boot-starter-web
		 


		
		
			org.springframework
			spring-aop
		
		
			org.springframework
			spring-aspects
		
		
			org.aspectj
			aspectjrt
		
		
			org.aspectj
			aspectjweaver
		
		
			cglib
			cglib
			2.1_3
		
		

		
		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		


		
		
			org.springframework.boot
			spring-boot-devtools
			true
		
		
       
		
			org.springframework.boot
			spring-boot-starter
		

        
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		
        
		
			net.spy
			spymemcached
			2.12.2
		

缓存memcached

配置文件

################################################
###   memcache缓存 
################################################
memcache.ip=127.0.0.1
memcache.port=11211

配置类MemcachedRunner


package com.example.demo.config;

import net.spy.memcached.MemcachedClient;
import net.spy.memcached.compat.log.Logger;
import net.spy.memcached.compat.log.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.InetSocketAddress;

/**
 * Memcached缓存配置  MemcachedClient.getClient(),获取缓存
 * @author wangsong
 * @date 2019年4月27日 下午5:17:22
 */
@Component
public class MemcachedRunner implements CommandLineRunner {
    protected Logger logger =  LoggerFactory.getLogger(this.getClass());

    private MemcachedClient client = null;

    @Value("${memcache.ip}")
    private String ip;
    
    @Value("${memcache.port}")
    private int port;
    
    
    @Override
    public void run(String... args) throws Exception {
        try {
            client = new MemcachedClient(new InetSocketAddress(ip,port));
        } catch (IOException e) {
            logger.error("inint MemcachedClient failed ",e);
        }
    }

    /**
     * 获取 MemcachedClient 连接
     * @return
     */
    public MemcachedClient getClient() {
        return client;
    }
}


创建注解

注解一(token验证)

package com.example.demo.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解(token验证注解),需要验证的接口添加
 * @author wangsong
 * @date:  2019年4月27日 下午9:37:02
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiIdempotent {
	String value();
}

注解二(token生成)

package com.example.demo.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 生成token 的注解
 * @author wangsong
 * @date:  2019年4月27日 下午10:09:44
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiToken {

}


token生成与判断工具类MemcachedTokenUtils


package com.example.demo.utlis;

import com.example.demo.config.MemcachedRunner;
import net.spy.memcached.MemcachedClient;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;

/**
 * Token 生成与判断
 * @author wangsong
 * @date:  2019年4月27日 下午10:04:39
 */
@Service
public class MemcachedTokenUtils {
	

	/**
	 * toket有效期
	 */
	private Integer TOKET_TIMEOUT = 60 * 60;

	/**
	 * Memcached对象
	 */
	@Resource
	private MemcachedRunner memcachedRunner;


	/**
	 *  将token存入在Memcached || redis
	 * @return
	 */
	public String getToken() {
		//token = token+时间戳
		String token = "token" + System.currentTimeMillis();
		MemcachedClient memcachedClient = memcachedRunner.getClient();
		memcachedClient.set(token,TOKET_TIMEOUT, token);
		return token;
	}

	/**
	 *  判断toket是否有效 / 是否为重复调用
	 * @param tokenKey
	 * @return
	 */
	public boolean findToken(String tokenKey) {
		MemcachedClient memcachedClient = memcachedRunner.getClient();
		String token = (String) memcachedClient.get(tokenKey);
		//判断token是否为空
		if (StringUtils.isEmpty(token)) {
			return false;
		}
		// token 获取成功后 删除对应tokenMapstoken
		memcachedClient.delete(tokenKey);
		return true;
	}
}

AOP 拦截通知实现注解功能

package com.example.demo.config;

import com.example.demo.utlis.MemcachedTokenUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * spring 通知验证 || 防止接口重复掉用|| 表单重复提交
 */
@Aspect
@Component
public class ExtApiAopIdempotent {

    @Autowired
    private MemcachedTokenUtils memcachedTokenUtils;


    /**
     * 拦截范围
     */
    @Pointcut("execution(public * com.example.demo.controller.*.*(..))")
    public void rlAop() {
    }


    /**
     * 前置通知
* 转发Token参数() * * @param point */ @Before("rlAop()") public void before(JoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); //查询接口是否携带 @ExtApiToken 注解 ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class); //携带注解 生成toket 放到请求头 if (extApiToken != null) { extApiToken(); } } /** * 获取token(生成toket 放到请求头) */ public void extApiToken() { String token = memcachedTokenUtils.getToken(); getRequest().setAttribute("token", token); } /** * 环绕通知验证参数 * * @param proceedingJoinPoint * @return * @throws Throwable */ @Around("rlAop()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); //查询接口是否携带 @ExtApiIdempotent 注解 ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class); //携带注解验证toket,是否为重复请求 Object proceed; if (extApiIdempotent != null) { //验证Token proceed = extApiIdempotent(proceedingJoinPoint, signature); return proceed; } // 未携带直接放行 proceed = proceedingJoinPoint.proceed(); return proceed; } /** * 验证Token * * @param proceedingJoinPoint * @param signature * @return * @throws Throwable */ private Object extApiIdempotent(ProceedingJoinPoint proceedingJoinPoint, MethodSignature signature) throws Throwable { //获取注解 ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class); // 1.获取令牌,存放在请求头中|valueType = 请求类型,rpc接口/表单 HttpServletRequest request = getRequest(); String valueType = extApiIdempotent.value(); //判断某字符串是否为空,为空的标准是 str==null或 str.length()==0 if (StringUtils.isEmpty(valueType)) { response("参数错误!"); return null; } //from(表单) 或者 head(请求头) String token = null; if (valueType.equals("head")) { token = request.getHeader("token"); } else { token = request.getParameter("token"); } //判断某字符串是否为空,为空的标准是 str==null或 str.length()==0 if (StringUtils.isEmpty(token)) { response("参数错误!"); return null; } //查看缓存是否存在token|| 不存在重复提交 if (!memcachedTokenUtils.findToken(token)) { response("请勿重复提交!"); return null; } //删除缓存token,防止重复提交 Object proceed = proceedingJoinPoint.proceed(); return proceed; } /** * 获取 request * * @return */ private HttpServletRequest getRequest() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); return request; } /** * 获取 response * * @param msg * @throws IOException */ private void response(String msg) throws IOException { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletResponse response = attributes.getResponse(); response.setHeader("Content-type", "text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); try { writer.println(msg); } catch (Exception e) { } finally { writer.close(); } } }

conteoller

package com.example.demo.controller;

import com.example.demo.config.ExtApiIdempotent;
import com.example.demo.config.ExtApiToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;


@Controller
public class TokenController {


    /**
     * 跳转表单页
     * 自定义注解 @ExtApiToken 生成token
     *
     * @param req
     * @return
     */
    @RequestMapping("/test")
    @ExtApiToken
    public String indexPage(HttpServletRequest req) {
        return "test";
    }




    /**
     * 提交表单
     * 自定义注解验证是否重复提交 @ExtApiIdempotent(value = "form")   form||head
     *
     * @param request
     * @return
     */
    @RequestMapping(value = "/tokenTest", produces = "application/json; charset=utf-8")
    @ExtApiIdempotent(value = "form")
    @ResponseBody
    public String addOrderExtApiIdempotent(HttpServletRequest request) {
        System.out.println("正常执行");
        return "正常执行逻辑代码";
    }
}




html





Insert title here


  

你可能感兴趣的:(API,设计/安全/架构)