SpringBoot?Starter自定義全局加解密組件的詳細(xì)流程
目的
- 了解
SpringBoot Starter
相關(guān)概念以及開發(fā)流程 - 實(shí)現(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)的依賴打包,簡化項(xiàng)目的配置和初始化過程,通過特定的Starter
開發(fā)者可以快速的實(shí)現(xiàn)特定功能模塊的開發(fā)和擴(kuò)展。
自定義Starter
能夠促進(jìn)團(tuán)隊(duì)內(nèi)部資源的復(fù)用,保持項(xiàng)目間的一致性,提升協(xié)作效率并且有助于構(gòu)建穩(wěn)定、高效的大型系統(tǒng)。
開發(fā)流程
注入SpringBoot的方式
在剛開始開發(fā)Starter
的時(shí)候,首先考慮的是怎么能注入到SpringBoot中
?
這部分涉及到部分SpringBoot
的自動(dòng)裝配原理,不太清楚的朋友可以補(bǔ)習(xí)下;
注入SpringBoot
需要配置文件,在項(xiàng)目中的resources
資源目錄中創(chuàng)建該目錄和文件。
demo-spring-boot-starter └── src └── main └── java └── com.xbhog ├── DemoBean.java └── DemoBeanConfig.java └── resources └── META-INF └── spring.factories
在spring.factories
中我們指定一下自動(dòng)裝配的配置類,格式如下:
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
主類啟動(dòng)器:初始化的操作,感興趣的朋友可以研究下
完成后,我們可以打包該項(xiàng)目,然后在測試工程紅進(jìn)行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
可以使用了。
到此,一個(gè)簡單的Starter
開發(fā)完成了,后續(xù)可以圍繞工程,根據(jù)需求和業(yè)務(wù),對通用功能(接口操作日志、異常、加解密、白名單等)進(jìn)行封裝,最后打到Maven
倉庫中進(jìn)行使用。
自定義SpringBoot Starter(全局加解密)
來源
在之前金融系統(tǒng)開發(fā)中,需要對接多個(gè)第三方的服務(wù)且數(shù)據(jù)安全性要求比較高;在接口評審階段需要雙方在數(shù)據(jù)傳輸?shù)臅r(shí)候進(jìn)行接口加解密;起初在第一個(gè)服務(wù)對接的時(shí)候,將相關(guān)的加解密操作寫到工具類中;隨著后續(xù)服務(wù)的增多,代碼的侵入越來越嚴(yán)重。
封裝
選擇通過Starter
進(jìn)行功能的封裝;好處:引用方便,開發(fā)迭代方便,團(tuán)隊(duì)復(fù)用度高且對業(yè)務(wù)沒有侵入。
開發(fā)
思路:通過配置文件初始化,讓配置類注解@ComponentScan
掃描到的Bean等
注入到SpringBoot
中,通過自定義注解和`RequestBodyAdvice/ResponseBodyAdvice
組合攔截請求,在BeforBodyRead/beforeBodyWrite
中進(jìn)行數(shù)據(jù)的前置處理,加密或解密后映射到接口接收的字段或?qū)ο蟆?/p>
接口上的操作有兩種方式:
- 注解+
AOP
實(shí)現(xiàn) - 注解+
RequestBodyAdvice/ResponseBodyAdvice
這里我選擇的第二種的RequestBodyAdvice/ResponseBodyAdvice
,拋磚引玉一下。
【注】 第二種存在的局限性是:只能針對POST
請求中的Body
數(shù)據(jù)處理,無法針對GET
請求進(jìn)行處理。
項(xiàng)目結(jié)構(gòu):
核心代碼:
@Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { log.info("進(jìn)入【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("該流水已插入當(dāng)前請求流水表"); 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("進(jìn)入【ResponseBodyEncryptAdvice】beforeBodyWrite的操作,方法:{}",returnType.getMethod()); String cryptHandler = ContextHolder.getCryptHandler(); SecurityHandler securityHandler = SpringContextHolder.getBean(cryptHandler, SecurityHandler.class); assert body != null; return securityHandler.encrypt(body.toString()); }
該Starter
中的全局加解密默認(rèn)采用的國密非對稱加密SM2
,在開發(fā)過程中遇到了該問題InvalidCipherTextException: invalid cipher text
【原因】 私鑰和公鑰值不是成對存在的,每次調(diào)用SmUtil.sm2()
會(huì)生成不同的隨機(jī)密鑰對。
【解決】在該Starter
中采用@PostConstruct
修飾方法,在項(xiàng)目運(yùn)行中只會(huì)初始化運(yùn)行一次該方法,保證了SmUtil.sm2()
只會(huì)調(diào)用一次,不會(huì)生成不同的隨機(jī)秘鑰對。
【ISSUES#1890
】詳細(xì)請看該地址: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); } }
除了默認(rèn)的加密方式,還可以通過SecurityHandler
接口進(jìn)行擴(kuò)展,擴(kuò)展出來的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ù)用之前的測試項(xiàng)目,引用打包的mavne
依賴:
<dependency> <groupId>com.xbhog</groupId> <artifactId>encryAdecry-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>
啟動(dòng)項(xiàng)目,初始化公私鑰。
測試接口代碼如下:
@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)化后的項(xiàng)目結(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
對象的的一種機(jī)制;
@Data @ConfigurationProperties(GlobalProperties.PREFIX) public class GlobalProperties { /** * 默認(rèn)前綴 */ public static final String PREFIX = "encryption.type"; /** * 加解密算法 */ private String algorithmType; ? /** * 加解密key值 */ private String key; }
注解修改:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SecuritySupport { /** * 項(xiàng)目默認(rèn)加解密實(shí)現(xiàn)類encryAdecryImpl * */ String securityHandler() default "encryAdecryImpl"; ? }
重寫Starter
默認(rèn)的加解密方式:
? @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自定義全局加解密組件的詳細(xì)流程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Starter加解密組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章

基于Java編寫第一個(gè)區(qū)塊鏈項(xiàng)目

Java 中的字符串替換方法之replace, replaceAll 和 rep

Springboot 實(shí)現(xiàn)數(shù)據(jù)庫備份還原的方法

Spring Boot兩種配置文件properties和yml區(qū)別

Spring MVC環(huán)境中文件上傳功能的實(shí)現(xiàn)方法詳解

Spring Cloud之服務(wù)監(jiān)控turbine的示例