# api-secure-boot-starter **Repository Path**: gl613/api-secure-boot-starter ## Basic Information - **Project Name**: api-secure-boot-starter - **Description**: 一个简单的接口安全请求组件。支持请求验签、请求解密和响应加密,支持注解和配置文件两种声明方式。 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-08-24 - **Last Updated**: 2023-08-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # api-secure-boot-starter ## 简单介绍 一个简单的接口安全请求组件,提供请求验签、请求解密和响应加密,使用注解和配置文件两种声明方式,简单易用。 #### 请求加密、响应解密 内置了base64编码、aes对称加密算法和rsa非对称加密算法三种方式处理器,也可以自己实现`EncryptDecryptHandler`接口。 #### 请求验签 内置了默认的验签处理器,对前端请求参数以p1=v1&p2=v2方式拼接进行验签验证。也可以自己实现`SignatureHandler`接口。 ## 使用说明 #### 请求解密 激活请求解密需要在配置文件配置`api.secure.encdec.enable=true`。具体配置如下: ```yaml api: secure: #加密解密配置 encdec: enable: true #解密方式 type: aes #内置三种方式:base64,aes,rsa。不填则需要自己实现EncDecHandler #解密密钥 key: zNVSnd+RJ8OW+Fkm1AAfXA== ``` 解密需要使用`@Decrypt`注解标识需要接收密文的处理器方法入参,并且该入参必须是`String`字符串类型。注意`@Decrypt`注解的`fillParameter`属性是用于指定解密后的明文字符串填充到哪个处理器方法入参上,这里是使用Jackson的ObjectMapper把明文字符串转化到对应参数类型的。 ```java @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Decrypt { /** * 解密后填充的目标入参: * 1、填充本身入参,字符串类型 * 2、填充其他入参,json字符串必须能转换成对应入参类型 * * @return */ String fillParameter() default ""; } ``` 几种接收密文入参的方式: - Query Params ```java @RequestMapping(value = "/queryParam", method = {RequestMethod.GET, RequestMethod.POST}) public void queryParam(@Decrypt(fillTarget = "content") String content) { //解密后的明文字符直接填充给本身入参,覆盖密文 } ``` - Content-Type请求内容类型为application/form-data ```java @RequestMapping(value = "/formData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, method = {RequestMethod.GET, RequestMethod.POST}) public void formData(@Decrypt(fillTarget = "content") String content) { //解密后的明文字符直接填充给本身入参,覆盖密文 } ``` - Content-Type请求内容类型为application/json ```java @PostMapping(value = "/json", consumes = MediaType.APPLICATION_JSON_VALUE) public void json(@Decrypt(fillTarget = "map") @RequestBody String content, Map map) { //解密后的明文字符串转Map类型 } @PostMapping(value = "/json2", consumes = MediaType.APPLICATION_JSON_VALUE) public void json2(@Decrypt(fillTarget = "vo") @RequestBody String content, UserDetailVO vo) { //解密后的明文字符串转对象类型 } @PostMapping(value = "/json3", consumes = MediaType.APPLICATION_JSON_VALUE) public void json3(@Decrypt(fillTarget = "voList") @RequestBody String content, @RequestParam(required = false) List voList) { //解密后的明文字符串转对象类型 //集合入参必须加注解@RequestParam(required = false),不然会报错No primary or single unique constructor found for } @PostMapping(value = "/json4", consumes = MediaType.APPLICATION_JSON_VALUE) public void json4(@Decrypt(fillTarget = "vos") @RequestBody String content, @RequestParam(required = false) UserDetailVO[] vos) { //解密后的明文字符串转数组类型 //数组入参必须加注解@RequestParam(required = false),不然会报错No primary or single unique constructor found for } ``` - Content-Type请求内容类型为text/plain ```java @PostMapping(value = "/text", consumes = MediaType.TEXT_PLAIN_VALUE) public void text(@Decrypt(fillTarget = "plainContent") @RequestBody String content, String plainContent) { //解密后的明文字符填充给另一个字符串类型入参 } ``` - Content-Type请求内容类型为application/x-www-form-urlencoded ```java @PostMapping(value = "/xWwwFormUrlencoded", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) public void xWwwFormUrlencoded(@Decrypt(fillTarget = "content") String content) { //解密后的明文字符直接填充给本身入参,覆盖密文 } ``` #### 响应加密 激活响应加密需要在配置文件配置`api.secure.encdec.enable=true`。具体配置如下: ```yaml api: secure: #加密解密配置 encdec: enable: true #加密方式 type: aes #内置三种方式:base64,aes,rsa。不填则需要自己实现EncDecHandler #加密密钥 key: zNVSnd+RJ8OW+Fkm1AAfXA== #加密处理器方法响应路径 enc-handler-mapping-path-list: /test/** ``` 加密可以通过使用配置文件`api.secure.encdec.enc-handler-mapping-path-list`配置或者`@Encrypt`注解两种声明方式指定哪些处理器方法响应需要加密。其中,注解声明方式可以把加密注解放到处理器方法或者处理器类上,注解到处理器方法上只会加密本方法响应对象,注解到类上则该类下所有请求映射方法的响应都会被加密。配置文件声明方式见上面配置,注解声明方式见下面代码: ```java @Encrypt @PostMapping(value = "/encrypt", consumes = MediaType.APPLICATION_JSON_VALUE) public UserDetailVO encrypt() { //解密后的明文字符串转对象类型 return null; } ``` #### 请求验签 激活请求验签需要在配置文件配置`api.secure.signature.enable=true`。具体配置如下: ```yaml api: secure: #验签配置 signature: enable: true #验签处理器方法响应路径 handler-mapping-path-list: /test/** ``` 验签可以通过使用配置文件`api.secure.signature.handler-mapping-path-list`配置或者`@VerifySignature`注解两种声明方式指定哪些处理器方法请求需要验签。其中,注解声明方式可以把验签注解放到处理器方法或者处理器类上,注解到处理器方法上只会对本方法请求验签,注解到类上则该类下所有请求映射方法的请求都会验签。配置文件声明方式见上面配置,注解声明方式见下面代码: ```java @VerifySignature @RequestMapping(value = "/verifySignature", method = {RequestMethod.GET, RequestMethod.POST}) public void verifySignature(@RequestParam Integer id, @Decrypt(fillParameter = "name") String name) { } ``` ## 安全常识 #### 编码与加密 1. 编码 编码 解码 2. 非对称加密 加密和解密使用相同的密钥(Key)。 记明文为P,密文为C,密钥为K 加密:C = E(P,K),E为加密函数 解密:P = D(C,K),D为解密函数 3. 对称加密 公钥(publicKey)和私钥(privateKey)。 记明文为P,密文为C,公钥为publicKey,密钥为privateKey 如果用公钥对数据进行加密,只有用对应的私钥才能解密; 加密:C = E(P,publicKey),E为加密函数 解密:P = D(C,privateKey),D为解密函数 如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。 加密:C = E(P,privateKey),E为加密函数 解密:P = D(C,publicKey),D为解密函数 #### 签名与验签 加密:发送方利用接收方的公钥对要发送的明文进行加密。 解密:接收方利用自己的私钥对密文进行解密。 配对:公钥和私钥配对的,用公钥加密的文件,只有对应的私钥才能解密。当然也可以反过来,用私钥加密,用对应的公钥进行解密。 签名:签名是发送方为发送的文件写上一个自己的签名,所以需要使用的是自己(发送方)的私钥。 验证签名: 验证签名是接收方需要确认自己接收到的密文文件是否真的是发送方发送过来的,需要确认的是中间有没有被篡改(不同于解密),验签最终是根据报文摘要来进行对比。 具体详细描述可参考[《签名验签》](https://zhuanlan.zhihu.com/p/457435473) ## 计划 - [x] feign调用加密、解密和验签支持测试 - [ ] 加密解密分开? - [ ] 请求头参数名称抽出去,一般定义在业务系统common、core包 - [ ] 异常抛出错误处理,feign调用异常测试 - [ ] 优化query params传参方式传rsa加密文本,+加号丢失,导致解码失败问题。 - [ ] 加密和编码组合处理问题。