【SpringBootStarter】

目的

  1. 了解SpringBoot Starter相关概念以及开发流程
  2. 实现自定义SpringBoot Starter(全局加解密)
  3. 了解测试流程
  4. 优化

最终引用的效果:

<dependency>
<groupId>com.xbhog</groupId>
<artifactId>globalValidation-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>

了解SpringBoot Starter相关概念以及开发流程

SpringBoot Starter

SpringBoot Starter作用将一组相关的依赖打包,简化项目的配置和初始化过程,通过特定的Starter开发者可以快速的实现特定功能模块的开发和扩展。

自定义Starter能够促进团队内部资源的复用,保持项目间的一致性,提升协作效率并且有助于构建稳定、高效的大型系统。

开发流程

注入SpringBoot的方式

在刚开始开发Starter的时候,首先考虑的是怎么能注入到SpringBoot中

这部分涉及到部分SpringBoot的自动装配原理,不太清楚的朋友可以补习下;

注入SpringBoot需要配置文件,在项目中的resources资源目录中创建该目录和文件。

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("已经触发了配置类,正在初始化DemoBean...");
return new DemoBean();
}
}
@Slf4j
public class DemoBean {
public void getDemo(){
log.info("方法调用成功");
}
}

这样就可以将设置的包扫描路径下的相关操作打包到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开发完成了,后续可以围绕工程,根据需求和业务,对通用功能(接口操作日志、异常、加解密、白名单等)进行封装,最后打到Maven仓库中进行使用。

自定义SpringBoot Starter(全局加解密)

来源

在之前金融系统开发中,需要对接多个第三方的服务且数据安全性要求比较高;在接口评审阶段需要双方在数据传输的时候进行接口加解密;起初在第一个服务对接的时候,将相关的加解密操作写到工具类中;随着后续服务的增多,代码的侵入越来越严重。

封装

选择通过Starter进行功能的封装;好处:引用方便,开发迭代方便,团队复用度高且对业务没有侵入。

开发

思路:通过配置文件初始化,让配置类注解@ComponentScan扫描到的Bean等注入到SpringBoot中,通过自定义注解和``RequestBodyAdvice/ResponseBodyAdvice组合拦截请求,在BeforBodyRead/beforeBodyWrite`中进行数据的前置处理,解密后映射到接口接收的字段或对象。

接口上的操作有两种方式:

  1. 注解+AOP实现
  2. 注解+RequestBodyAdvice/ResponseBodyAdvice

这里我选择的第二种的RequestBodyAdvice/ResponseBodyAdvice,抛砖引玉一下。

【注】第二种存在的局限性是:只能针对POST请求中的Body数据处理,无法针对GET请求进行处理。

项目结构:

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);
}
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,在开发过程中遇到了该问题InvalidCipherTextException: invalid cipher text

【原因】 私钥和公钥值不是成对存在的,每次调用SmUtil.sm2()会生成不同的随机密钥对。

【解决】在该Starter中采用@PostConstruct修饰方法,在项目运行中只会初始化运行一次该方法,保证了SmUtil.sm2()只会调用一次,不会生成不同的随机秘钥对。

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="xxxxxx")中指定。

/**
* @author xbhog
* @describe: 全局加解密注解
* @date 2023/6/8
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecuritySupport {
/*securityHandlerImpl*/
String securityHandler() default "securityHandlerImpl"; String exceptionResponse() default ""; }

测试

复用之前的测试项目,引用打包的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);
}
}

优化

优化后的项目结构:

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

增加配置类,用于绑定外部配置(propertiesYAML)到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 {
/**
* 项目默认加解密实现类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】具体加密的数据{}",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
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);
}
}

【SpringBootStarter】自定义全局加解密组件的更多相关文章

  1. Vue 自定义全局消息框组件

    消息弹框组件,默认3秒后自动关闭,可设置info/success/warning/error类型 效果图: 文件目录: Message.vue <template> <transit ...

  2. 基于SpringCloud的Microservices架构实战案例-配置文件属性内容加解密

    使用过SpringBoot配置文件的朋友都知道,资源文件中的内容通常情况下是明文显示,安全性就比较低一些.打开application.properties或application.yml,比如mysq ...

  3. vue 自定义全局方法

    import {myfun} from '../static/js/test.js' //se6的正确写法export default {methods:{ diyfun:function () { ...

  4. SpringBoot+ShardingSphere彻底解决生产环境数据库字段加解密问题

    前言   互联网行业公司,对于数据库的敏感字段是一定要进行加密的,方案有很多,最直接的比如写个加解密的工具类,然后在每个业务逻辑中手动处理,在稍微有点规模的项目中这种方式显然是不现实的,不仅工作量大而 ...

  5. vue2 自定义全局组件(Loading加载效果)

    vue2 自定义全局组件(Loading加载效果) github地址: https://github.com/ccyinghua/custom-global-component 一.构建项目 vue ...

  6. 07vue 自定义全局组件 通用流程

    1.全局组件的目录 2.loading/index.js import LoadingComp from './Loaiding' const compName=LoadingComp.name // ...

  7. POCO库——Foundation组件之加解密Crypt

    加解密Crypt:内部提供多种加解密方式.信息摘要提取.随机数产生等,具体的算法内部实现不做研究学习: DigestEngine.h :DigestEngine类作为各种摘要提取的基类,提供必要的接口 ...

  8. php利用自定义key,对数据加解密的方法

    客户端和服务端通信时,有个场景很常见,通过一个id作为url参数来回传递.假设现在业务上只有这个id标识,那么需要稍微安全一点的通信,对这个id进行加密传输,到服务端再进行解密.这里需要一个服务端进行 ...

  9. Asp.Net Core 2.0 项目实战(7)MD5加密、AES&DES对称加解密

    本文目录 1. 摘要 2. MD5加密封装 3. AES的加密.解密 4. DES加密/解密 5. 总结 1.  摘要 C#中常用的一些加密和解密方案,如:md5加密.RSA加密与解密和DES加密等, ...

  10. vue自定义全局和局部指令

    一.介绍 1.除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 2.自定义指令的分类       1.全局指令 2.局部指令 3.自定义全局指令格式 V ...

随机推荐

  1. C++跨DLL内存所有权问题探幽(一)DLL提供的全局单例模式

    最近在开发的时候,特别是遇到关于跨DLL申请对象.指针.内存等问题的时候遇到了这么一个问题. 问题 跨DLL能不能调用到DLL中提供的单例? 问题比较简单,就是我现在有一个进程A,有DLL B DLL ...

  2. CS2打开可以听到声音,但黑屏问题?

    1.问题 我这里原先是可以启动CS2的,但是后来在CS2中重新调整了分辨率等等,之后由于某种原因又调整了屏幕分辨率,导致后面一进入CS2登录界面,橙色登陆界面就会缩在左上角一小块,并且之后就会陷入黑屏 ...

  3. 浏览器兼容 : IE 5 到 IE 9

    <!--[if IE]> <link href="ie.css" rel="stylesheet"> <![endif]--> ...

  4. Docker-01基本命令

    1.Docker安装 系统镜像为Centos7.x yum包更新到最新 sudo yum update 安装需要的软件包,yum-util提供yum-config-manager功能.另外两个是dev ...

  5. [转帖]解Bug之路-NAT引发的性能瓶颈

    https://zhuanlan.zhihu.com/p/286532997 解Bug之路-NAT引发的性能瓶颈 笔者最近解决了一个非常曲折的问题,从抓包开始一路排查到不同内核版本间的细微差异,最后才 ...

  6. [转帖]是什么让 Redis“气急败坏”回击:13 年来,总有人想替 Redis 换套新架构

    https://www.infoq.cn/article/AlF5NIhHdskayl0MTyQG 回击就代表输了?! 今年年中,一位前谷歌.前亚马逊的工程师推出了他创作的开源内存数据缓存系统 Dra ...

  7. [转帖]create table INITRANS参数分析

    https://www.modb.pro/db/44701 1. 内容介绍 Oracle数据库create table时使用INITRANS参数设置数据块ITL事务槽的数量,确保该数据块上 并发事务数 ...

  8. [转帖]Jmeter常用配置元件(二):“HTTP Cookie管理器”登录状态保持

    在API接口测试过程中,我们需要传递cookie值作为连接状态的保持,例如登录后状态信息.通过使用Jmeter提供的"HTTP Cookie管理器"来实现. 一般情况下不需要输入什 ...

  9. express学会CRUD

    使用express 搭建项目 1==> express 项目名 -e 2==> 然后按照提示就可以了 cd 项目名 3==>进入项目 下载依赖 cnpm i 4==>启动项目 ...

  10. 【JS 逆向百例】网洛者反爬练习平台第四题:JSFuck 加密

    关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...