您当前的位置: 首页 >  安全

互联网并发与安全系列教程(08) - API接口幂等设计与实现

杨林伟 发布时间:2019-11-19 11:11:08 ,浏览量:3

实现思路:

  • 客户端每次在调用接口的时候,需要在请求头中,传递令牌参数,每次令牌只能用一次,有超时时间限定。
  • 一旦使用之后,就会被删除,这样可以有效防止重复提交。

本文目录结构: l____1.BaseRedisService封装Redis l____2. RedisTokenUtils工具类 l____3.自定义Api幂等注解和切面 l____4.幂等注解使用 l____5.封装生成token注解 l____6.改造ExtApiAopIdempotent l____7.API接口保证幂等性 l____8. 页面防止重复提交

下面直接上代码

1.BaseRedisService封装Redis
@Component
public class BaseRedisService {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	public void setString(String key, Object data, Long timeout) {
		if (data instanceof String) {
			String value = (String) data;
			stringRedisTemplate.opsForValue().set(key, value);
		}
		if (timeout != null) {
			stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
		}
	}

	public Object getString(String key) {
		return stringRedisTemplate.opsForValue().get(key);
	}

	public void delKey(String key) {
		stringRedisTemplate.delete(key);
	}

}
2. RedisTokenUtils工具类
@Component
public class RedisTokenUtils {
	private long timeout = 60 * 60;
	@Autowired
	private BaseRedisService baseRedisService;

	// 将token存入在redis
	public String getToken() {
		String token = "token" + System.currentTimeMillis();
		baseRedisService.setString(token, token, timeout);
		return token;
	}

	public boolean findToken(String tokenKey) {
		String token = (String) baseRedisService.getString(tokenKey);
		if (StringUtils.isEmpty(token)) {
			return false;
		}
		// token 获取成功后 删除对应tokenMapstoken
		baseRedisService.delKey(token);
		return true;
	}

}
3.自定义Api幂等注解和切面

1. 注解:

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiIdempotent {
	String value();
}

2. 切面:

@Aspect
@Component
public class ExtApiAopIdempotent {
	@Autowired
	private RedisTokenUtils redisTokenUtils;

	@Pointcut("execution(public * com.itmayiedu.controller.*.*(..))")
	public void rlAop() {
	}

	@Around("rlAop()")
	public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
		ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
		if (extApiIdempotent == null) {
			// 直接执行程序
			Object proceed = proceedingJoinPoint.proceed();
			return proceed;
		}
		// 代码步骤:
		// 1.获取令牌 存放在请求头中
		HttpServletRequest request = getRequest();
		String token = request.getHeader("token");
		if (StringUtils.isEmpty(token)) {
			response("参数错误!");
			return null;
		}
		// 2.判断令牌是否在缓存中有对应的令牌
		// 3.如何缓存没有该令牌的话,直接报错(请勿重复提交)
		// 4.如何缓存有该令牌的话,直接执行该业务逻辑
		// 5.执行完业务逻辑之后,直接删除该令牌。
		if (!redisTokenUtils.findToken(token)) {
			response("请勿重复提交!");
			return null;
		}
		Object proceed = proceedingJoinPoint.proceed();
		return proceed;
	}

	public HttpServletRequest getRequest() {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		return request;
	}

	public 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();
		}

	}

}
4.幂等注解使用
// 从redis中获取Token
@RequestMapping("/redisToken")
public String RedisToken() {
	return redisTokenUtils.getToken();
}

// 验证Token
@RequestMapping(value = "/addOrderExtApiIdempotent", produces = "application/json; charset=utf-8")
@ExtApiIdempotent
public String addOrderExtApiIdempotent(@RequestBody OrderEntity orderEntity, HttpServletRequest request) {
	int result = orderMapper.addOrder(orderEntity);
	return result > 0 ? "添加成功" : "添加失败" + "";
}
5.封装生成token注解
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiToken {

}
6.改造ExtApiAopIdempotent
@Aspect
@Component
public class ExtApiAopIdempotent {
	@Autowired
	private RedisTokenUtils redisTokenUtils;

	@Pointcut("execution(public * com.itmayiedu.controller.*.*(..))")
	public void rlAop() {
	}

	// 前置通知转发Token参数
	@Before("rlAop()")
	public void before(JoinPoint point) {
		MethodSignature signature = (MethodSignature) point.getSignature();
		ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class);
		if (extApiToken != null) {
			extApiToken();
		}
	}

	// 环绕通知验证参数
	@Around("rlAop()")
	public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
		ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
		if (extApiIdempotent != null) {
			return extApiIdempotent(proceedingJoinPoint, signature);
		}
		// 放行
		Object proceed = proceedingJoinPoint.proceed();
		return proceed;
	}

	// 验证Token
	public Object extApiIdempotent(ProceedingJoinPoint proceedingJoinPoint, MethodSignature signature)
			throws Throwable {
		ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
		if (extApiIdempotent == null) {
			// 直接执行程序
			Object proceed = proceedingJoinPoint.proceed();
			return proceed;
		}
		// 代码步骤:
		// 1.获取令牌 存放在请求头中
		HttpServletRequest request = getRequest();
		String valueType = extApiIdempotent.value();
		if (StringUtils.isEmpty(valueType)) {
			response("参数错误!");
			return null;
		}
		String token = null;
		if (valueType.equals(ConstantUtils.EXTAPIHEAD)) {
			token = request.getHeader("token");
		} else {
			token = request.getParameter("token");
		}
		if (StringUtils.isEmpty(token)) {
			response("参数错误!");
			return null;
		}
		if (!redisTokenUtils.findToken(token)) {
			response("请勿重复提交!");
			return null;
		}
		Object proceed = proceedingJoinPoint.proceed();
		return proceed;
	}

	public void extApiToken() {
		String token = redisTokenUtils.getToken();
		getRequest().setAttribute("token", token);

	}

	public HttpServletRequest getRequest() {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		return request;
	}

	public 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();
		}

	}

}
7.API接口保证幂等性
@RestController
public class OrderController {

	@Autowired
	private OrderMapper orderMapper;
	@Autowired
	private RedisTokenUtils redisTokenUtils;

	// 从redis中获取Token
	@RequestMapping("/redisToken")
	public String RedisToken() {
		return redisTokenUtils.getToken();
	}

	// 验证Token
	@RequestMapping(value = "/addOrderExtApiIdempotent", produces = "application/json; charset=utf-8")
	@ExtApiIdempotent(value = ConstantUtils.EXTAPIHEAD)
	public String addOrderExtApiIdempotent(@RequestBody OrderEntity orderEntity, HttpServletRequest request) {
		int result = orderMapper.addOrder(orderEntity);
		return result > 0 ? "添加成功" : "添加失败" + "";
	}
}
8. 页面防止重复提交
@Controller
public class OrderPageController {
	@Autowired
	private OrderMapper orderMapper;

	@RequestMapping("/indexPage")
	@ExtApiToken
	public String indexPage(HttpServletRequest req) {
		return "indexPage";
	}

	@RequestMapping("/addOrderPage")
	@ExtApiIdempotent(value = ConstantUtils.EXTAPIFROM)
	public String addOrder(OrderEntity orderEntity) {
		int addOrder = orderMapper.addOrder(orderEntity);
		return addOrder > 0 ? "success" : "fail";
	}

}
关注
打赏
1688896170
查看更多评论

杨林伟

暂无认证

  • 3浏览

    0关注

    3183博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.3366s