kun's blog

vuePress-theme-reco kun    2020 - 2021
kun's blog kun's blog

Choose mode

  • dark
  • auto
  • light
Home
Category
  • Linux
  • Java
  • Spring
  • DevOps
  • Blog
  • Java 并发
  • Cache
  • API加密
  • Redis
  • Spring Cloud
  • Summer
tag
TimeLine
GitHub
author-avatar

kun

42

Article

39

Tag

Home
Category
  • Linux
  • Java
  • Spring
  • DevOps
  • Blog
  • Java 并发
  • Cache
  • API加密
  • Redis
  • Spring Cloud
  • Summer
tag
TimeLine
GitHub

造个轮子,开箱即用的API安全组件

vuePress-theme-reco kun    2020 - 2021

造个轮子,开箱即用的API安全组件


kun 2021-02-22 API加密

关于 API 加解密前面已经写了四篇日志::

  1. 一个前后分离的 API 签名和请求参数加解密构思
  2. RAS+AES混合加密Java端实现
  3. RAS+AES混合加密Vue端实现
  4. RAS+AES混合加密小程序端实现

这里是番外篇,造个轮子:开箱即用的 API 安全组件

前面第二篇:RAS+AES混合加密Java端实现 已经把 API 加解密的 Java 端功能基本都实现了。

但是在其他项目用的时候还得 Copy 代码过去,很不方便。所以我就封装了一个 Jar 包,项目需要对 API 进行安全管理的话只需要添加个依赖,application.yml 中加几行配置就 ok 了。下面讲下这个 Jar 包的用法。

组件仓库地址:api-security

示例代码已上传:test-api-security

# 引入组件

# pom 文件中引入 Jar 包

<dependency>
     <groupId>com.fxg</groupId>
     <artifactId>api-security</artifactId>
     <version>1.0-SNAPSHOT</version>
</dependency>

# 在 application.yml 中添加如下配置

api:
  security:
    open: true            ##总开关
    check-sign: true      ##是否验证签名和重放请求
    timeout: 300000     ##请求过期时间
    show-log: true        ##是否打印日志
    public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMbFUgBEsev1lURtNFgfr0jtz4IDJ6MEyIkA2WMG57bPfSsT4Pei7bxsXUCyMTXQbaxV0SThX802gxrpTEBAbJsCAwEAAQ==
    private-key: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxsVSAESx6/WVRG00WB+vSO3PggMnowTIiQDZYwbnts99KxPg96LtvGxdQLIxNdBtrFXRJOFfzTaDGulMQEBsmwIDAQABAkEAqG6gM9YCJn5txBP9nQcMU3IgunzN45e0DlQH4aACTac6JHPTZAA1STxdgTosdDBhrC1HA2pPlRzCuCAh3MpvgQIhAOxTENdAAiQPspaFWAvGJZhN767g9LFGUVdabvf0mCC7AiEA11HZRiSpICXO2U1MrYsLrTJMHrQQvCM/mOhW4UullaECIDs/7DX7T04ZPW4tilCRYjWYPKJ8tfyII7ah7rZt9YInAiBSdJSY6OcfWXsp+hEYEDxLegxuYZRbB8COBMNoiXiCoQIgMls9U5YPlGQ3ajDUhFACFIUNpGQl8l2faxPy/yRoV6o=

public-key 和 private-key 没有默认配置,可以通过网站在线生成,也可以直接使用上面的这个示例配置。

# 在启动类上增加 @EnableSecurity 注解

@EnableSecurity 注解是启动组件的开关注解,必须添加这个注解,相关安全组件才会被注册到系统中。它的主要作用是向spring容器注册下面三个类:

  • ApiSignFilter:验证签名的过滤器,主要用来解密 AES 秘钥和验证签名
  • DecryptRequestBodyAdvice:用来解密 RequestBody 的 @ControllerAdvice 类
  • EncryptResponseBodyAdvice:用来加密 ResponseBody 的 @ControllerAdvice 类

# 测试签名、加密、解密功能

# 测试签名功能

在 Controller 中创建一个 Post 方法:

	@PostMapping("/sign")
	public String sign(@RequestParam Integer id, @RequestBody User user) {
		logger.info("enter sign method,id:{},user:{}", id, user);
		return "ok";
	}

在 PostMan 中创建一个 Post 请求:

Params:
id:99

Body:
{"id":"99"}

Headers:
##时间戳
X_TIMESTAMP:1613815735447
##随机字符串
X_NONCE:1613815735447
##encryptAesKey,Rsa加密后的Aes秘钥(下面这个的原文是:VuL0fSCfWeQzl7yUcYasqhOLlO80M365)
X_EAK:Row54E6DJctz4V3OhK3JWj4bmeiOIpZLkq0K8DaHORL7TuflHxwK4Npa6gypcSGH7vh5Zi4mEEor3cR9HlGcgg==
##签名结果
X_SIGN:6448020f50ecfbf135a34e9f8b3fa800

注意事项:
1. 签名结果可以写个 main 方法获取,示例如下:

	public static void main(String[] args) {
		TreeMap<String, String> params = new TreeMap<>();
		params.put("timestamp", "1613815735447");
		params.put("nonce", "1613815735447");
		params.put("aesKey", "VuL0fSCfWeQzl7yUcYasqhOLlO80M365");
		params.put("id", "99");
		params.put("body", "{\"id\":\"99\"}");
		String sign = SignUtil.sign(params);
		System.out.println(sign);
	}

发起请求发现返回了463的状态码,后台也打印出了警告信息:Timestamp validation failed! requestTime:1613815735447, currentTime:1613963122150,timeOut:30000

说明请求已经过期了,示例中的时间戳 1613815735447 对应的日期是:2021-02-20 18:08:55,距离现在肯定已经超过 5 分钟了。按照后台提示的 currentTime 时间戳,修改请求头中的 X_TIMESTAMP 值,重新计算签名后发起请求即可。

# 测试加密功能

为方便测试可以先将配置项 api.security.check-sign 配置为 false。

在 Controller 中创建以下方法:

	@Encrypt
	@GetMapping("/encrypt")
	private User encrypt() {
		User user = new User();
		user.setNickName("encrypt");
		logger.info("enter encrypt method,return user:{}", user);
		return user;
	}

在 PostMan 创建一个 Get 请求,直接发起请求即可。

# 测试解密功能

为方便测试可以先将配置项 api.security.check-sign 配置为 false。

在 Controller 中创建以下方法:

	@Decrypt
	@PostMapping("/decrypt")
	private User decrypt(@RequestBody User user) {
		logger.info("enter decrypt method,param user:{}", user);
		user.setId(1);
		logger.info("enter decrypt method,return user:{}", user);
		return user;
	}

在 PostMan 创建一个 Post 请求,将测试加密功能时返回的加密结果复制到请求的 body 中,直接发起请求即可。

# 总结

其实在生产中, API 的安全校验也好、数据加解密也好,一般在网关层,基本都是放在一处完成。请求进内部服务的 Controller 前就把这些都做完了,内部服务也不应该关心这些检验和加解密。所以这个组件把解密和加密分别放在 RequestBodyAdvice 和 ResponseBodyAdvice 中,然后用注解来控制的方式有点花架子的意思,,,实际作用不大,但是在个人的单体小项目中和是很好的,很灵活,目前 小链家 和小程序郑房曲线中都采用了这个组件。

  • 引入组件
  • pom 文件中引入 Jar 包
  • 在 application.yml 中添加如下配置
  • 在启动类上增加 @EnableSecurity 注解
  • 测试签名、加密、解密功能
  • 测试签名功能
  • 测试加密功能
  • 测试解密功能
  • 总结