-
《專案實戰(影片)》:從書中學,往事上“練” -
《網際網路高頻面試題》:面朝簡歷學習,春暖花開 -
《架構 x 系統設計》:摧枯拉朽,掌控面試高頻場景題 -
《精進 Java 學習指南》:系統學習,網際網路主流技術棧 -
《必讀 Java 原始碼專欄》:知其然,知其所以然

-
Boot 倉庫:https://gitee.com/zhijiantianya/ruoyi-vue-pro -
Cloud 倉庫:https://gitee.com/zhijiantianya/yudao-cloud -
影片教程:https://doc.iocoder.cn

目的
-
瞭解SpringBoot Starter相關概念以及開發流程 -
實現自定義SpringBoot Starter(全域性加解密) -
瞭解測試流程 -
最佳化
<dependency>
<groupId>com.xbhog</groupId>
<artifactId>globalValidation-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
-
專案地址:https://github.com/YunaiV/ruoyi-vue-pro -
影片教程:https://doc.iocoder.cn/video/
瞭解SpringBoot Starter相關概念以及開發流程
SpringBoot Starter
開發流程
demo-spring-boot-starter
└── src
└── main
└── java
└── com.xbhog
├── DemoBean.java
└── DemoBeanConfig.java
└── resources
└── META-INF
└── spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xbhog.DemoBeanConfig
@Slf4j
@Configuration
publicclassDemoBeanConfig{
@Bean
public DemoBean getDemo(){
log.info("已經觸發了配置類,正在初始化DemoBean...");
returnnew DemoBean();
}
}
@Slf4j
publicclassDemoBean{
publicvoidgetDemo(){
log.info("方法呼叫成功");
}
}


<dependency>
<groupId>com.xbhog</groupId>
<artifactId>demo-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
@RestController
publicclassBasicControllerimplementsApplicationContextAware{
private ApplicationContext applicationContext;
/**兩種引入方式都可以
@Autowired
private DemoBean demoBean;*/
@GetMapping("/configTest")
publicvoidconfigTest(){
DemoBean demoBean = applicationContext.getBean(DemoBean.class);
demoBean.getDemo();
}
@Override
publicvoidsetApplicationContext(ApplicationContext applicationContext)throws BeansException {
this.applicationContext = applicationContext;
}
}

-
專案地址:https://github.com/YunaiV/yudao-cloud -
影片教程:https://doc.iocoder.cn/video/
自定義SpringBoot Starter(全域性加解密)
來源
封裝
開發
@ComponentScan
掃描到的Bean等注入到SpringBoot中,透過自定義註解和RequestBodyAdvice/ResponseBodyAdvice
組合攔截請求,在BeforBodyRead/beforeBodyWrite
中進行資料的前置處理,解密後對映到介面接收的欄位或物件。-
註解+AOP實現 -
註解+ RequestBodyAdvice/ResponseBodyAdvice
RequestBodyAdvice/ResponseBodyAdvice
,拋磚引玉一下。encryAdecry-spring-boot-starter
└── src
└── main
└── java
└── com.xbhog
├── advice
│ ├──ResponseBodyEncryptAdvice.java
│ └──RequestBodyDecryptAdvice.java
├── annotation
│ └──SecuritySupport
├── handler
│ ├──impl
│ │ └──SecurityHandlerImpl.java
│ └──SecurityHandler
└── holder
│ ├──ContextHolder.java
│ ├──EncryAdecryHolder.java
│ └──SpringContextHolder.java
└──GlobalConfig.java
└── resources
└── META-INF
└── spring.factories

@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);
}
returnnew 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());
}
InvalidCipherTextException: invalid cipher text
SmUtil.sm2()
會生成不同的隨機金鑰對。@PostConstruct
修飾方法,在專案執行中只會初始化執行一次該方法,保證了SmUtil.sm2()
只會呼叫一次,不會生成不同的隨機秘鑰對。@Slf4j
@Component
publicclassEncryAdecryHolder{
publicstatic SM2 sm2 = null;
@PostConstruct
publicvoidencryHolder(){
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="xxxxxx")
中指定。@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public@interface SecuritySupport {
/*securityHandlerImpl*/
String securityHandler()default "securityHandlerImpl";
String exceptionResponse()default "";
}
測試
<dependency>
<groupId>com.xbhog</groupId>
<artifactId>encryAdecry-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>


@Slf4j
@RestController
publicclassBasicControllerimplementsApplicationContextAware{
@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);
}
}

最佳化
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
@Data
@ConfigurationProperties(GlobalProperties.PREFIX)
publicclassGlobalProperties{
/**
* 預設字首
*/
publicstaticfinal String PREFIX = "encryption.type";
/**
* 加解密演算法
*/
private String algorithmType;
/**
* 加解密key值
*/
private String key;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public@interface SecuritySupport {
/**
* 專案預設加解密實現類encryAdecryImpl
* */
String securityHandler()default "encryAdecryImpl";
}
@Slf4j
@Component
publicclassEncryAdecryImplimplementsSecurityHandler{
@Resource
private GlobalProperties globalProperties;
privatestaticvolatile SM2 sm2;
@Override
public String encrypt(String original){
log.info("【starter】具體加密的資料{}",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】具體解密的資料:{}",decryptData);
return decryptData;
}
@PostConstruct
@Override
publicvoidinit(){
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);
}
}





