首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

SpringBoot Starter优雅封神!自定义全局加解密组件

说实话,我一开始真没把“自定义 SpringBoot Starter”当回事,以为不过是整一套自动配置嘛,Spring 本身就挺善于封装的。但真到实际落地的时候,才发现其中的门道远比想象中多。尤其当你要做一个全局的加解密组件,不光是“能用”,还得“好用”、“能复用”、“能扩展”,这就开始考验功底了。

为啥要搞个 Starter?

场景很典型,早年我在对接一个金融系统的项目,接口评审的时候人家一句话把我们打回来了:“数据传输必须加密,而且要支持 SM2。”

行,那就加密呗,咱们写个工具类搞定。第一个接口搞完,领导夸了;第二个接口上线,PM也很满意;第三个……等等,怎么每次都要复制那一堆工具代码?改一处出 bug 的概率高得惊人不说,团队协作的时候每个人写法还不一样,审代码都快审瞎了。

说实话,那个时候我脑子里冒出的第一个想法就是:要是能一行配置,像整合 Redis、JWT 那样,直接用就好了。

然后,我就开始了“自定义 Starter”这条路,一发不可收拾。

一步一步做个 Starter 出来

Starter 的底层原理其实不复杂,说白了就是自动装配 + 组件注册 + 配置解耦,SpringBoot 自己的核心机制就支持这一套。那我就按这个节奏来搞:

首先,项目结构要清晰,遵循 SpringBoot 的习惯:

encryAdecry-spring-boot-starter

└── java

  └── com.xbhog

      ├── advice

      ├── annotation

      ├── handler

      ├── holder

      ├── GlobalConfig.java

      ├── GlobalProperties.java

└── resources

  └── META-INF

      └── spring.factories

这里面的关键点是spring.factories,这玩意是 SpringBoot Starter 的“身份证”,告诉 SpringBoot:“我这儿有个自动配置类,记得加载我。”

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xbhog.GlobalConfig

然后我们来看看这个配置类里干了啥:

@Configuration

@ComponentScan("com.xbhog")

@EnableConfigurationProperties(GlobalProperties.class)

public class GlobalConfig {

  @Bean

  public SecurityHandler encryAdecryImpl(GlobalProperties properties) {

      return new EncryAdecryImpl(properties);

  }

}

这么一来,整个包下的组件就能被自动扫描和注入了,同时配合@ConfigurationProperties把外部配置文件参数也读进来了,比如:

encryption:

type: SM2

key: 123456

有了这些,组件的灵活性就大了。

拦截点在哪儿?加解密是怎么接入进来的?

这部分我当时也卡了挺久,因为加解密不能只靠控制器显式调用工具类,太烦了,而且不优雅。最优解是什么?就是你写接口像平时那样写,Starter 来负责“偷天换日”

我选的是RequestBodyAdvice和ResponseBodyAdvice,这两个接口可以在 Spring 处理请求和响应体之前“插个手”进去,把数据“拦”下来做一波操作。

拦截请求体进行解密:

@Override

public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

  SecuritySupport securitySupport = parameter.getMethodAnnotation(SecuritySupport.class);

  ContextHolder.setCryptHolder(securitySupport.securityHandler());

  String original = IOUtils.toString(inputMessage.getBody(), Charset.defaultCharset());

  String handler = securitySupport.securityHandler();

  SecurityHandler securityHandler = SpringContextHolder.getBean(handler, SecurityHandler.class);

  String 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) {

  String cryptHandler = ContextHolder.getCryptHandler();

  SecurityHandler securityHandler = SpringContextHolder.getBean(cryptHandler, SecurityHandler.class);

  return securityHandler.encrypt(body.toString());

}

你看,这逻辑是不是就很清楚了:你只管写@PostMapping的业务代码,数据交给它加解密,不改动 Controller,业务零侵入。

不过,有个前提:你必须是 POST 请求,并且参数在 body 中,GET 请求是不管用的——这是这套机制的一个小缺点,但一般传敏感数据本来就不应该用 GET,对吧。

如何实现“扩展能力”?加密方式不想被写死咋办?

一开始我用的是 Hutool 提供的SM2加密工具,确实方便,但后来看到了它的 bug(每次初始化生成新的密钥对),我就意识到这个组件必须支持自定义扩展。

于是,我定义了一个接口:

public interface SecurityHandler {

  String encrypt(String original);

  String decrypt(String original);

  default void init() {}

}

然后,默认实现类是:

@Component("encryAdecryImpl")

publicclass EncryAdecryImpl implements SecurityHandler {

  privatestaticvolatile SM2 sm2;

  @Resource

  private GlobalProperties globalProperties;

  @PostConstruct

  public void init() {

      KeyPair pair = SecureUtil.generateKeyPair(globalProperties.getAlgorithmType());

      byte[] privateKey = pair.getPrivate().getEncoded();

      byte[] publicKey = pair.getPublic().getEncoded();

      sm2= SmUtil.sm2(privateKey, publicKey);

  }

  @Override

  public String encrypt(String original) {

      return sm2.encryptBase64(original, KeyType.PublicKey);

  }

  @Override

  public String decrypt(String original) {

      return StrUtil.utf8Str(sm2.decryptStr(original, KeyType.PrivateKey));

  }

}

如果你不想用 SM2,可以自己实现SecurityHandler接口,然后在注解里这么写:

@SecuritySupport(securityHandler = "yourCustomHandler")

一行搞定,是不是很丝滑?

怎么测试这个玩意到底好不好使?

整合测试的时候,我是这么搞的:

@Slf4j

@RestController

publicclass BasicController {

  @SecuritySupport

  @PostMapping("/hello")

  public String hello(@RequestBody String name) {

      return"Hello " + name;

  }

  @GetMapping("/configTest")

  public String configTest(@RequestParam("name") String name) {

      return encryAdecry.encrypt(name);

  }

}

启动项目以后,在@PostConstruct里生成一次密钥对,这个时候控制台就能看到类似这样的输出:

生成的公钥:[B@4f3f5b24

生成的私钥:[B@62f59913

然后你直接调用/hello接口,POST 一个加密字符串进去,Starter 会帮你解密;你返回的响应,它也会自动帮你加密。

对业务代码来说,丝毫无感;但对安全传输来说,这一层加密就好比自动上锁上保险,还是军工级别的。

为什么这个 Starter 值得一试?

总结几点我的真实感受吧:

少写重复代码:真正做到“封装一次,到处用”,不再 copy 工具类;

对业务零侵入:不影响业务逻辑,所有处理都在外围进行;

高度可扩展:想换加密算法?自己写个实现就行;

集成超丝滑:一个注解,一个依赖,就能上线。

这个东西说复杂不复杂,说简单也不简单。要做到“让别人用了觉得舒服”,这才是技术的高级玩法。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O-jZsmag1o5YQJiOTfohTlXA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券