专栏首页北漂的我开放平台的简单实现(access key & access secret)

开放平台的简单实现(access key & access secret)

1. 服务端需要维护一个表,保存客户端调用的 access key 和 access secret

2. 所有的客户端发起的请求都为 POST 请求,post 请求参数都放在 body 中

{
	"appId": "fdsafdsafdsaf",

	"timestamp": 1608190943132,

	"businessData": "{\"createTime\":1608195735340,\"id\":1,\"name\":\"tom\"}",

	"sign": "jhgfjhgfjhgfjhgjhgfjhgfjhgfjfghjhgfjhgfjhgfjfg"
}

appId: 客户端的唯一标识(access key)

timestamp: 时间戳毫秒数

businessData: 业务数据

sign: appSecret + timestamp + businessData 的 MD5 签名值

3. 服务端获取到请求后对请求进行验证

① 验证请求的类型

② 验证请求参数的合法性

③ 验证请求时间戳是否过期(比如: 与服务端时间差再±120秒之内)

④ 验证请求签名的有效性

4. 如果想要防止重放攻击, 让一个请求只能请求一次

可以在 body 中添加一个参数 nonce (一个随机字符串), 请求之后把 nonce 放到redis缓存中, 过期时间可以设置比请求时间戳过期时间略长(比如125秒), 请求发起时先验证请求时间戳是否过期, 如果没过期再验证nonce, 只要发现redis中有对应的nonce值就直接拒绝访问.

package com.bytedance.itmd.open.filter;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.util.DigestUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import com.alibaba.fastjson.JSON;
import com.bytedance.itmd.open.common.Constant;
import com.bytedance.itmd.open.common.CustomHttpServletRequestWrapper;
import com.bytedance.itmd.open.common.LockCallback;
import com.bytedance.itmd.open.common.RedissonLockTemplate;
import com.bytedance.itmd.open.common.ResultEnum;
import com.bytedance.itmd.open.model.dto.ParamDTO;
import com.bytedance.itmd.open.service.ExternalAppService;

public class ValidateRequestFilter extends OncePerRequestFilter {
	private final Logger logger = LoggerFactory.getLogger(getClass());
	
	private ExternalAppService externalAppService;
	private RedisTemplate<String, Object> redisTemplate;
	private RedissonLockTemplate redissonLockTemplate;
	
	private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
	
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
		// 验证请求类型
		if(request.getServletPath().startsWith("/open-api") && !HttpMethod.POST.matches(request.getMethod())) {
			request.setAttribute("resultEnum", ResultEnum.HTTP_METHOD_ERROR);
			request.getRequestDispatcher("/exception").forward(request, response);
			return;
		}
		
		// 验证参数
		CustomHttpServletRequestWrapper customHttpServletRequestWrapper = new CustomHttpServletRequestWrapper(request);
		String bodyJsonStr = new String(customHttpServletRequestWrapper.getBody());
		ParamDTO paramDTO = JSON.parseObject(bodyJsonStr, ParamDTO.class);
		if(paramDTO == null) {
			request.setAttribute("resultEnum", ResultEnum.PARAMETER_VALIDATE_FAIL);
			request.setAttribute("message", "请求参数为空");
			request.getRequestDispatcher("/exception").forward(request, response);
			return;
		}
		Set<ConstraintViolation<ParamDTO>> constraintViolationSet = validator.validate(paramDTO, Default.class);
		if(constraintViolationSet.size() != 0) {
			StringBuffer message = new StringBuffer("");
			for (ConstraintViolation<ParamDTO> constraintViolation : constraintViolationSet) {
				String prefix = message.toString().equals("") ? "" : ", ";
				message.append(prefix).append(constraintViolation.getPropertyPath().toString()).append(" ").append(constraintViolation.getMessage());
			}
			request.setAttribute("resultEnum", ResultEnum.PARAMETER_VALIDATE_FAIL);
			request.setAttribute("message", message.toString());
			request.getRequestDispatcher("/exception").forward(request, response);
			return;
		}
		
		// 验证请求时间戳, 在正负120秒之内
		long timestamp = paramDTO.getTimestamp();
		long nowTimestamp = System.currentTimeMillis();
		if(Math.abs(nowTimestamp - timestamp) > 120000) {
			request.setAttribute("resultEnum", ResultEnum.HTTP_REQUEST_EXPIRED);
			request.getRequestDispatcher("/exception").forward(request, response);
			return;
		}
		
		// 验证请求签名
		String appId = paramDTO.getAppId();
		String appIdKey = Constant.KEY_APP_ID_PREFIX + ":" + appId;
		String appSecret = (String) redisTemplate.opsForValue().get(appIdKey);
		if(appSecret == null) {
			appSecret = redissonLockTemplate.tryLock("lock:" + appIdKey, 5, 10, TimeUnit.SECONDS, new LockCallback<String>() {
				@Override
				public String doBusiness() {
					String appSecret = (String) redisTemplate.opsForValue().get(appIdKey);
					if(appSecret != null) {
						return appSecret;
					}
					appSecret =  externalAppService.getAppSecret(appId);
					redisTemplate.opsForValue().set(appIdKey, appSecret == null ? "" : appSecret, 1, TimeUnit.DAYS);
					return appSecret;
				}
			});
		}
		String businessData = paramDTO.getBusinessData();
		String md5Sign = DigestUtils.md5DigestAsHex((appSecret + timestamp + businessData).getBytes("UTF-8"));
		String sign = paramDTO.getSign();
		if(logger.isInfoEnabled()) {
			logger.info("客户端md5签名: {}", sign);
			logger.info("appSecret + timestamp + businessData: {}", appSecret + timestamp + businessData);
			logger.info("服务端md5签名: {}", md5Sign);
		}
		if(!sign.equals(md5Sign)) {
			request.setAttribute("resultEnum", ResultEnum.HTTP_SIGN_INVALID);
			request.getRequestDispatcher("/exception").forward(request, response);
			return;
		}
		
		filterChain.doFilter(customHttpServletRequestWrapper, response);
	}

	public void setExternalAppService(ExternalAppService externalAppService) {
		this.externalAppService = externalAppService;
	}

	public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	public void setRedissonLockTemplate(RedissonLockTemplate redissonLockTemplate) {
		this.redissonLockTemplate = redissonLockTemplate;
	}
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 手把手教你调用百度人脸识别API

    现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。

    互扯程序
  • AWS 命名提示需要指定 region

    在使用命令 aws codeartifact get-authorization-token 生成 token的时候得到提示:

    HoneyMoose
  • python实现图片文字提取,准确率高达99%,强无敌!!!

    上次我使用的百度AI开放平台的API接口实现图片的转化,后来有许多小伙伴都私信问我,怎么获取百度AI平台的AK和SK。为了统一回答大家的问题,今天我又使...

    菜鸟小白的学习分享
  • Amazon S3 工具及使用

    持续部署中需要将编译后的静态资源打包上传到S3服务中 ,就研究了下。 需要申请Amazon账号,得开通信用卡,可免费使用一年 然后开通S3服务,填写buck...

    mafeifan
  • 合理使用百度开放平台(一)---动物识别

    可以在浏览器输入 https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials...

    代码哈士奇
  • 如何从亚马逊下载aws-SpaceNet卫星遥感图片数据集

    亚马逊SpaceNet数据集是作用于机器学习人工智能方面比赛或者研究用的商用数据集。我们在利用深度学习进行卫星图像分割时,比如利用FCN、Deeplab算法进行...

    OLDPAN
  • 部署在SAP Cloud Platform CloudFoundry环境的应用如何消费

    Jerry的前一篇文章 如何在Web应用里消费SAP Leonardo的机器学习API 里介绍的例子是Neo测试环境的Web应用消费sandbox版本的机器学习...

    Jerry Wang
  • 用AWS部署一个无服务架构的个人网站

    在这篇文章里我想介绍下怎样利用AWS(hjlouyoujuqi360com)部署一个无服务架构的个人网站。这个个人网站将具备以下特点:

    用户4199102
  • Java程序员不缺对象,缺的是对象存储

    文件服务器是一个应用必要的组件之一。最早我搞过FTP,然后又用过FastDFS,接私活的时候我用MongoDB也凑合凑合。现如今时代不同了,开始流行起了OSS。

    码农小胖哥

扫码关注云+社区

领取腾讯云代金券