SpringBoot Starter,自定义全局加解密组件

Java精选面试题 (微信小程序):5000+道面试题和选择题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计、大厂真题等,在线随时刷题!

目的

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

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

  • 了解测试流程

  • 优化

最终引用的效果:

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

开源地址

https://gitee.com/xbhog/encry-adecry-spring-boot-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主类启动器:初始化的操作,感兴趣的朋友可以研究下

43179e1db9330660987289b58339a434.png

9235aaca5588006edf4f46921f061edb.png

完成后,我们可以打包该项目,然后在测试工程红进行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可以使用了。

29af2e2e80f588daec6f3d9da98d6751.png

到此,一个简单的Starter开发完成了,后续可以围绕工程,根据需求和业务,对通用功能(接口操作日志、异常、加解密、白名单等)进行封装,最后打到Maven仓库中进行使用。

自定义SpringBoot Starter

来源

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

封装

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

开发

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

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

  • 注解+AOP实现

  • 注解+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

项目处理流程图:

6967327c4ff3d929614df78596b270d6.png

核心代码:

@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()只会调用一次,不会生成不同的随机秘钥对。

/**
 * @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>

启动项目,初始化公私钥。

推荐程序员摸鱼地址:

https://www.yoodb.com/slack-off/home.html

d75524da6921fa1015faadff4e2be756.png

fab11e6e2ea88dbea347fc6eeeeca60d.png

测试接口代码如下:

@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);
    }
}

d183483d0532b0a13af9825e4deae74a.png

优化

优化后的项目结构:

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 {
    /**
     * 项目默认加解密实现类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);
    }
}
 
 

作者:Xbhog

https://www.cnblogs.com/xbhog/p/18012435

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!
最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!

Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!
------ 特别推荐 ------
特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。

点击“阅读原文”,了解更多精彩内容!文章有帮助的话,点在看,转发吧!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个快速开发微服务的框架,它提供了大量的自动化配置和快速开发的工具。在Spring Boot中,我们可以使用Starter来快速集成一些常用的功能,如数据库、缓存、web等。而自定义Starter则可以让我们将自己的功能快速集成到Spring Boot中,下面是自定义Starter的流程: 1. 创建Maven项目 首先,我们需要创建一个Maven项目作为我们自定义Starter的项目。在项目的pom.xml中添加Spring Boot的依赖,以及其他需要集成的依赖。 2. 编写自动配置类 自动配置类是自定义Starter的核心,它负责将我们自定义的功能集成到Spring Boot中。在自动配置类中,我们可以使用@Conditional注解来判断是否需要进行配置。 3. 编写Starter类 Starter类是我们自定义Starter的入口,它负责将自动配置类注入到Spring容器中。在Starter类中,我们需要使用@EnableAutoConfiguration注解来启用自动配置。 4. 打包和发布Starter 当我们完成了自动配置类和Starter类的编写后,我们需要将自定义Starter打包成jar包,并发布到Maven仓库中,以便其他项目可以通过Maven依赖的方式使用我们的Starter。 5. 在项目中使用自定义Starter 在其他项目中使用自定义Starter非常简单,只需要在项目中的pom.xml中添加我们自定义Starter的依赖即可。Spring Boot会自动将我们的自定义Starter集成到项目中,并进行自动配置。 以上就是自定义Starter的流程,通过自定义Starter,我们可以将自己的功能快速集成到Spring Boot中,提高开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值