SpringBoot?Starter自定義全局加解密組件的詳細流程
目的
- 了解
SpringBoot Starter相關(guān)概念以及開發(fā)流程 - 實現(xiàn)自定義
SpringBoot Starter(全局加解密) - 了解測試流程
- 優(yōu)化
最終引用的效果:
<dependency>
<groupId>com.xbhog</groupId>
<artifactId>globalValidation-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
了解SpringBoot Starter相關(guān)概念以及開發(fā)流程
SpringBoot Starter
SpringBoot Starter作用將一組相關(guān)的依賴打包,簡化項目的配置和初始化過程,通過特定的Starter開發(fā)者可以快速的實現(xiàn)特定功能模塊的開發(fā)和擴展。
自定義Starter能夠促進團隊內(nèi)部資源的復(fù)用,保持項目間的一致性,提升協(xié)作效率并且有助于構(gòu)建穩(wěn)定、高效的大型系統(tǒng)。
開發(fā)流程
注入SpringBoot的方式
在剛開始開發(fā)Starter的時候,首先考慮的是怎么能注入到SpringBoot中?
這部分涉及到部分SpringBoot的自動裝配原理,不太清楚的朋友可以補習下;
注入SpringBoot需要配置文件,在項目中的resources資源目錄中創(chuàng)建該目錄和文件。
demo-spring-boot-starter
└── src
└── main
└── java
└── com.xbhog
├── DemoBean.java
└── DemoBeanConfig.java
└── resources
└── META-INF
└── spring.factories
在spring.factories中我們指定一下自動裝配的配置類,格式如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xbhog.DemoBeanConfig
/**
* @author xbhog
* @describe:
*/
@Slf4j
@Configuration
public class DemoBeanConfig {
?
@Bean
public DemoBean getDemo() {
log.info("已經(jīng)觸發(fā)了配置類,正在初始化DemoBean...");
return new DemoBean();
}
}
@Slf4j
public class DemoBean {
public void getDemo(){
log.info("方法調(diào)用成功");
}
}
這樣就可以將設(shè)置的包掃描路徑下的相關(guān)操作打包到SpringBoot 中。
SpringBoot主類啟動器:初始化的操作,感興趣的朋友可以研究下


完成后,我們可以打包該項目,然后在測試工程紅進行Maven的引入、測試。
測試
新建Spring 測試工程,引入依賴:
<dependency>
<groupId>com.xbhog</groupId>
<artifactId>demo-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
?
@RestController
public class BasicController implements ApplicationContextAware {
private ApplicationContext applicationContext;
/**兩種引入方式都可以
@Autowired
private DemoBean demoBean;*/
?
@GetMapping("/configTest")
public void configTest() {
DemoBean demoBean = applicationContext.getBean(DemoBean.class);
demoBean.getDemo();
}
?
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
?
請求地址后,可以觀察控制臺,如下日志表示SpringBoot Starter可以使用了。

到此,一個簡單的Starter開發(fā)完成了,后續(xù)可以圍繞工程,根據(jù)需求和業(yè)務(wù),對通用功能(接口操作日志、異常、加解密、白名單等)進行封裝,最后打到Maven倉庫中進行使用。
自定義SpringBoot Starter(全局加解密)
來源
在之前金融系統(tǒng)開發(fā)中,需要對接多個第三方的服務(wù)且數(shù)據(jù)安全性要求比較高;在接口評審階段需要雙方在數(shù)據(jù)傳輸?shù)臅r候進行接口加解密;起初在第一個服務(wù)對接的時候,將相關(guān)的加解密操作寫到工具類中;隨著后續(xù)服務(wù)的增多,代碼的侵入越來越嚴重。
封裝
選擇通過Starter進行功能的封裝;好處:引用方便,開發(fā)迭代方便,團隊復(fù)用度高且對業(yè)務(wù)沒有侵入。
開發(fā)
思路:通過配置文件初始化,讓配置類注解@ComponentScan掃描到的Bean等注入到SpringBoot中,通過自定義注解和`RequestBodyAdvice/ResponseBodyAdvice組合攔截請求,在BeforBodyRead/beforeBodyWrite中進行數(shù)據(jù)的前置處理,加密或解密后映射到接口接收的字段或?qū)ο蟆?/p>
接口上的操作有兩種方式:
- 注解+
AOP實現(xiàn) - 注解+
RequestBodyAdvice/ResponseBodyAdvice
這里我選擇的第二種的RequestBodyAdvice/ResponseBodyAdvice,拋磚引玉一下。
【注】 第二種存在的局限性是:只能針對POST請求中的Body數(shù)據(jù)處理,無法針對GET請求進行處理。
項目結(jié)構(gòu):

核心代碼:
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
log.info("進入【RequestBodyDecryptAdvice】beforeBodyRead的操作,方法:{}",parameter.getMethod());
SecuritySupport securitySupport = parameter.getMethodAnnotation(SecuritySupport.class);
assert securitySupport != null;
ContextHolder.setCryptHolder(securitySupport.securityHandler());
String original = IOUtils.toString(inputMessage.getBody(), Charset.defaultCharset());
//todo
log.info("該流水已插入當前請求流水表");
String handler = securitySupport.securityHandler();
String plainText = original;
if(StringUtils.isNotBlank(handler)){
SecurityHandler securityHandler = SpringContextHolder.getBean(handler, SecurityHandler.class);
plainText = securityHandler.decrypt(original);
}
return new MappingJacksonInputMessage(IOUtils.toInputStream(plainText, Charset.defaultCharset()), inputMessage.getHeaders());
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
log.info("進入【ResponseBodyEncryptAdvice】beforeBodyWrite的操作,方法:{}",returnType.getMethod());
String cryptHandler = ContextHolder.getCryptHandler();
SecurityHandler securityHandler = SpringContextHolder.getBean(cryptHandler, SecurityHandler.class);
assert body != null;
return securityHandler.encrypt(body.toString());
}
該Starter中的全局加解密默認采用的國密非對稱加密SM2,在開發(fā)過程中遇到了該問題InvalidCipherTextException: invalid cipher text
【原因】 私鑰和公鑰值不是成對存在的,每次調(diào)用SmUtil.sm2()會生成不同的隨機密鑰對。
【解決】在該Starter中采用@PostConstruct修飾方法,在項目運行中只會初始化運行一次該方法,保證了SmUtil.sm2()只會調(diào)用一次,不會生成不同的隨機秘鑰對。
【ISSUES#1890】詳細請看該地址:https://hub.fgit.cf/dromara/hutool/issues/1890
/**
* @author xbhog
* @date 2024/02/01 13:23
**/
@Slf4j
@Component
public class EncryAdecryHolder {
public static SM2 sm2 = null;
@PostConstruct
public void encryHolder(){
KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
log.info("生成的公鑰:{}",publicKey);
log.info("生成的私鑰:{}",privateKey);
sm2= SmUtil.sm2(privateKey, publicKey);
}
}
除了默認的加密方式,還可以通過SecurityHandler接口進行擴展,擴展出來的impl可以在@SecuritySupport(securityHandler="xxxxx")中指定。
/**
* @author xbhog
* @describe: 全局加解密注解
* @date 2023/6/8
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecuritySupport {
/*securityHandlerImpl*/
String securityHandler() default "securityHandlerImpl";
?
String exceptionResponse() default "";
?
}
測試
復(fù)用之前的測試項目,引用打包的mavne依賴:
<dependency>
<groupId>com.xbhog</groupId>
<artifactId>encryAdecry-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
啟動項目,初始化公私鑰。


測試接口代碼如下:
@Slf4j
@RestController
public class BasicController implements ApplicationContextAware {
@Resource(name = "demoSecurityHandlerImpl")
private SecurityHandler encryAdecry;
private ApplicationContext applicationContext;
?
// http://127.0.0.1:8080/hello?name=lisi
//@SecuritySupport(securityHandler = "demoSecurityHandlerImpl")
@SecuritySupport
@PostMapping("/hello")
public String hello(@RequestBody String name) {
return "Hello " + name;
}
?
@GetMapping("/configTest")
public String configTest(@RequestParam("name") String name) {
/*DemoBean demoBean = applicationContext.getBean(DemoBean.class);
demoBean.getDemo();*/
return encryAdecry.encrypt(name);
//return MD5.create().digestHex16(name);
}
}

優(yōu)化
優(yōu)化后的項目結(jié)構(gòu):
encryAdecry-spring-boot-starter
└── src
└── main
└── java
└── com.xbhog
├── advice
│ ├──ResponseBodyEncryptAdvice.java
│ └──RequestBodyDecryptAdvice.java
├── annotation
│ └──SecuritySupport
├── handler
│ ├──impl
│ │ └──EncryAdecryImpl.java
│ └──SecurityHandler
└── holder
│ ├──ContextHolder.java
│ └──SpringContextHolder.java
├──GlobalProperties.java
└──GlobalConfig.java
└── resources
└── META-INF
└── spring.factories
增加配置類,用于綁定外部配置(properties和YAML)到Java對象的的一種機制;
@Data
@ConfigurationProperties(GlobalProperties.PREFIX)
public class GlobalProperties {
/**
* 默認前綴
*/
public static final String PREFIX = "encryption.type";
/**
* 加解密算法
*/
private String algorithmType;
?
/**
* 加解密key值
*/
private String key;
}
注解修改:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecuritySupport {
/**
* 項目默認加解密實現(xiàn)類encryAdecryImpl
* */
String securityHandler() default "encryAdecryImpl";
?
}
重寫Starter默認的加解密方式:
?
@Slf4j
@Component
public class EncryAdecryImpl implements SecurityHandler {
?
@Resource
private GlobalProperties globalProperties;
private static volatile SM2 sm2;
?
@Override
public String encrypt(String original) {
log.info("【starter】具體加密的數(shù)據(jù){}",original);
return sm2.encryptBase64(original, KeyType.PublicKey);
}
?
@Override
public String decrypt(String original) {
String decryptData = StrUtil.utf8Str(sm2.decryptStr(original, KeyType.PrivateKey));
log.info("【starter】具體解密的數(shù)據(jù):{}",decryptData);
return decryptData;
}
?
@PostConstruct
@Override
public void init() {
log.info("======>獲取映射的加密算法類型:{}",globalProperties.getAlgorithmType());
//傳的是加密算法
KeyPair pair = SecureUtil.generateKeyPair(globalProperties.getAlgorithmType());
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
sm2= SmUtil.sm2(privateKey, publicKey);
}
}
以上就是SpringBoot Starter自定義全局加解密組件的詳細流程的詳細內(nèi)容,更多關(guān)于SpringBoot Starter加解密組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 中的字符串替換方法之replace, replaceAll 和 rep
Springboot 實現(xiàn)數(shù)據(jù)庫備份還原的方法
Spring Boot兩種配置文件properties和yml區(qū)別
Spring MVC環(huán)境中文件上傳功能的實現(xiàn)方法詳解
Spring Cloud之服務(wù)監(jiān)控turbine的示例

