SpringBoot Starter自定义全局加解密组件的详细流程

 更新时间:2024年02月06日 11:14:23   作者:xbhog  
SpringBoot Starter作用将一组相关的依赖打包,简化项目的配置和初始化过程,通过特定的Starter开发者可以快速的实现特定功能模块的开发和扩展,本文给大家介绍了SpringBoot Starter自定义全局加解密组件的详细流程,需要的朋友可以参考下

目的

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

最终引用的效果:

<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中进行数据的前置处理,加密或解密后映射到接口接收的字段或对象。

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

  • 注解+AOP实现
  • 注解+RequestBodyAdvice/ResponseBodyAdvice

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

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

项目结构:

核心代码:

@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="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 "";
​
}

测试

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

以上就是SpringBoot Starter自定义全局加解密组件的详细流程的详细内容,更多关于SpringBoot Starter加解密组件的资料请关注脚本之家其它相关文章!

相关文章

  • Spring+SpringMVC+MyBatis整合详细教程(SSM)

    Spring+SpringMVC+MyBatis整合详细教程(SSM)

    Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架。这篇文章主要介绍了Spring+SpringMVC+MyBatis整合详细教程(SSM),需要的朋友可以参考下
    2017-10-10
  • Java SpringTask定时自动化处理方法

    Java SpringTask定时自动化处理方法

    这篇文章主要介绍了Java SpringTask定时自动化处理,通过自动化,不仅可以提高工作效率和准确性,还可以释放人力资源以专注于更高价值的工作,需要的朋友可以参考下
    2024-08-08
  • Java在PDF中添加表格过程详解

    Java在PDF中添加表格过程详解

    这篇文章主要介绍了Java在PDF中添加表格过程详解,本文将介绍通过Java编程在PDF文档中添加表格的方法。添加表格时,可设置表格边框、单元格对齐方式、单元格背景色、单元格合并、插入图片、设置行高、列宽、字体、字号等,需要的朋友可以参考下
    2019-07-07
  • Spring Boot 安全 API 构建之加密解密功能的实践记录

    Spring Boot 安全 API 构建之加密解密功能的实践记录

    本文详述了如何在SpringBoot3.3环境中实施API加密的最佳实践,包括选择合适的加密算法,密钥管理,数据加密,防止加密漏洞,安全日志记录,测试和监控等方面,同时,文章也对RSA非对称加密和AES对称加密的实现步骤进行了详细的解析
    2024-10-10
  • spring声明式事务管理解析

    spring声明式事务管理解析

    这篇文章主要为大家详细介绍了spring声明式事务管理,对spring事务管理进行深入了解,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • Java使用文本块(Text Blocks)处理多行字符串的操作方法

    Java使用文本块(Text Blocks)处理多行字符串的操作方法

    本文介绍了Java文本块(TextBlocks)语法,它简化多行字符串的处理,保留文本格式、减少错误、提升代码可读性,本文详细解释了文本块的语法、缩进规则、常用转义符及常见使用场景,同时注意事项,需要的朋友可以参考下
    2026-05-05
  • Java 方法递归的思路详解

    Java 方法递归的思路详解

    程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。但是如果没终止条件会造成死循环,所以递归代码里要有结束自调自的条件,接下来讲解一下学习递归的思路
    2022-04-04
  • JAVA IO和NIO的主要区别及使用说明

    JAVA IO和NIO的主要区别及使用说明

    Java IO(BIO)阻塞式,逐字节流处理;NIO非阻塞,基于缓冲区和通道实现多路复用,单线程管理多连接,适合高并发网络编程,IO代码简单但性能低,NIO性能高但复杂度增加,选择需权衡开发效率与性能需求
    2025-09-09
  • 每日六道java新手入门面试题,通往自由的道路--多线程

    每日六道java新手入门面试题,通往自由的道路--多线程

    这篇文章主要为大家分享了最有价值的6道多线程面试题,涵盖内容全面,包括数据结构和算法相关的题目、经典面试编程题等,对hashCode方法的设计、垃圾收集的堆和代进行剖析,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • 详解Spring-Boot中如何使用多线程处理任务

    详解Spring-Boot中如何使用多线程处理任务

    本篇文章主要介绍了详解Spring-Boot中如何使用多线程处理任务,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03

最新评论