Maison  >  Article  >  Java  >  Comment Springboot implémente le chiffrement des mots de passe en clair dans les fichiers de configuration

Comment Springboot implémente le chiffrement des mots de passe en clair dans les fichiers de configuration

WBOY
WBOYavant
2023-05-10 22:25:111452parcourir

Exemple d'affichage

Jetons un coup d'œil à cette configuration :

spring:
  # 数据库链接配置
  datasource:
    url: jdbc:mysql://xx.xx.xx.xx:3306/database
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: "123456"

La valeur correspondante de notre configuration ci-dessus spring.datasource.password est 123456. placé directement C'est très inapproprié dans le fichier de configuration. Ce que nous devons faire est de changer la valeur correspondante en un texte chiffré, comme suit : spring.datasource.password对应的值为123456,这么敏感的信息直接放在配置文件中很不合适,我们要做的就是对应的值改成一个加密的密文,如下:

spring:
  # 数据库链接配置
  datasource:
    url: jdbc:mysql://xx.xx.xx.xx:3306/database
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: "AES(DzANBAhBWXxZqAOsagIBCoaw8FV4gYRbid7G70UEM24=)"

这样的话,即使该配置文件被有心之人拿去,也不知道真正的数据库密码是啥,也就无法构成对项目的侵害风险;

原理解析

我们为了实现这个功能,需要了解Spring的相关扩展点以及对应的数据加解密知识,我们先来看看我们应该通过Spring的哪个扩展点进行切入;

我们想要拦截配置数据的话,可以通过实现自定义的BeanFactoryPostProcessor来处理:

public class PropertySourcePostProcessor implements BeanFactoryPostProcessor {

  private ConfigurableEnvironment environment;

  public PropertySourcePostProcessor(ConfigurableEnvironment environment) {
    this.environment = environment;
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // 从ConfigurableEnvironment中取出所有的配置数据
    MutablePropertySources propertySources = this.environment.getPropertySources();
    propertySources.stream()
        // 过滤不需要包装的对象
        .filter(s -> !noWrapPropertySource(s))
        // 包装所有的PropertySource
        .map(s -> new EncryPropertySource(s))
        .collect(Collectors.toList())
        // 替换掉propertySources中的PropertySource
        .forEach(wrap -> propertySources.replace(wrap.getName(), wrap));
  }

  private boolean noWrapPropertySource(PropertySource propertySource) {
    return propertySource instanceof EncryPropertySource || StringUtils.equalsAny(propertySource.getClass().getName(), "org.springframework.core.env.PropertySource$StubPropertySource", "org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource");
  }
}

基本原理解析如下:

1.通过ConfigurableEnvironment取出所有的PropertySource并依次遍历;

2.过滤掉不符合我们要求的PropertySource,因为PropertySource有很多子类,并不是所有的PropertySource实例都符合我们包装的要求;

3.对符合要求的PropertySource做一层包装,其实就是静态代理;

4.用包装好的PropertySource替换掉之前的PropertySource实例;

通过上述一系列的操作,我们就可以在PropertySource取值的时候做一些自定义的操作了,比如针对密文密码进行解密;

剩下的另一个问题就是加解密的问题,密码学里面有对称加密和非对称加密,这两种加密方式的区别就是对称加密的加密解密都需要同一个密钥,而非对称加密加密的时候需要公钥,解密的时候需要私钥;

了解了对称加密与非对称加密的区别,如果我们使用的是对称加密,那么一定要避免密文和密钥放在同一个地方;非对称加密一定要避免密文和私钥放在同一个地方;

工具介绍

接下来我们要介绍一款专门针对这个需求的jar工具,它就是jasypt,我们可以去maven仓库找到相关的包:

     <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>

它的实现原理其实就是我们上面所讲述的,通过自定义BeanFactoryPostProcessorConfigurableEnvironment中的PropertySource实例进行拦截包装,在包装类的实现上做一层解密操作,这样就实现了对密文密码的解密;

导入上述依赖后,该工具就已经自动生效了,我们就可以修改对应的配置了,首先我们先针对该工具做一些配置:

jasypt:
  encryptor:
    # 密钥
    password: ""
    property:
      # 密文前缀
      prefix: ""
      # 密文后缀
      suffix: ""

在上述配置中,jasypt.encryptor.password是一定要配置的,这就是加解密的密钥,默认的加密算法是PBEWITHHMACSHA512ANDAES_256;另外jasypt.encryptor.property.prefixjasypt.encryptor.property.suffix分别是密文前缀和密文后缀,是用来标注需要解密的密文的,如果不配置,默认的密文前缀是ENC(,密文后缀是);默认情况下,我们的密文如下所示:

spring:
  datasource:
    password: "ENC(DzANBAhBWXxZqAOsagIBCoaw8FV4gYRbid7G70UEM24=)"

还有一个需要注意的点就是jasypt.encryptor.password不能与密文放在一起,我们可以在项目当中通过系统属性、命令行参数或环境变量传递;

实现自定义加解密

如果jasypt提供的加解密方式不能满足咱们的项目需求,我们还可以自己实现加解密:

@Bean("jasyptStringEncryptor")
  public StringEncryptor jasyptStringEncryptor(){
    return new StringEncryptor() {
      @Override
      public String encrypt(String s) {
        // TODO 加密
        return null;
      }

      @Override
      public String decrypt(String s) {
        // TODO 解密
        return null;
      }
    };
  }

注意我们的BeanName,默认情况下一定要设置成jasyptStringEncryptor,否则不会生效,如果想要改变这个BeanName,也可以通过修改这个配置参数来自定义StringEncryptor实例所对应的BeanName

jasypt:
  encryptor:
    # 自定义StringEncryptor的BeanName
    bean: ""

如何生成密文

生成密文的这个操作还是要自个儿通过调用StringEncryptor

@Component
public class StringEncryptorUtil{
  @Autowired
  private StringEncryptor encryptor;
  
  public void encrypt(){
    String result = encryptor.encrypt("123456");
    System.out.println(result);
  }
}

Dans ce cas, même si le fichier de configuration est pris par quelqu'un avec des intentions, le Le mot de passe réel de la base de données ne sera pas connu. De quoi s'agit-il, il ne peut pas présenter de risque de violation du projet

Analyse des principes🎜🎜Afin d'implémenter cette fonction, nous devons comprendre les points d'extension pertinents de Spring et les connaissances correspondantes en matière de cryptage et de décryptage des données. Voyons d'abord par quel point d'extension de <code>Spring nous devons couper 🎜🎜Si nous voulons intercepter les données de configuration, nous pouvons le gérer en implémentant un ; BeanFactoryPostProcessor personnalisé : 🎜rrreee🎜 Le principe de base est analysé comme suit : 🎜🎜1 Supprimez tous les PropertySource via ConfigurableEnvironment et parcourez-les dans l'ordre. 🎜🎜2. Filtrez PropertySource qui ne répond pas à nos exigences code>, car PropertySource a de nombreuses sous-classes, toutes les instances de PropertySource ne répondent pas à nos exigences. exigences d'emballage ; 🎜🎜3. Pour PropertySource Créez une couche d'emballage, qui est en fait un proxy statique 🎜🎜4. Utilisez le <code>PropertySource empaqueté pour le remplacer ; l'instance PropertySource précédente ; 🎜🎜Grâce à la série d'opérations ci-dessus, nous pouvons effectuer certaines opérations personnalisées lorsque PropertySource prend la valeur, comme le déchiffrement du mot de passe chiffré 🎜🎜Le ; L'autre problème restant est la question du cryptage et du déchiffrement. Il existe un cryptage symétrique et un cryptage asymétrique. La différence entre ces deux méthodes de cryptage est que le cryptage symétrique nécessite la même clé pour le cryptage et le déchiffrement, tandis que le cryptage asymétrique nécessite une clé publique pour le cryptage et le déchiffrement. une clé privée pour le décryptage ; 🎜🎜Comprendre Comprendre la différence entre le chiffrement symétrique et le chiffrement asymétrique. Si nous utilisons le chiffrement symétrique, nous devons éviter de placer le texte chiffré et la clé au même endroit ; évitez de placer le texte chiffré et la clé privée au même endroit. La clé est placée au même endroit 🎜🎜Introduction à l'outil🎜🎜Ensuite, nous présenterons un outil jar spécifiquement pour ce besoin, il s'agit de jasypt, on peut se rendre dans l'entrepôt maven pour trouver le package concerné : 🎜rrreee🎜 Son principe de mise en œuvre est en fait celui que nous avons décrit ci-dessus, en personnalisant BeanFactoryPostProcessor à ConfigurableEnvironment L'instance PropertySource est interceptée et empaquetée, et une couche d'opération de décryptage est effectuée sur l'implémentation de la classe d'empaquetage, réalisant ainsi le déchiffrement du mot de passe chiffré 🎜 ; 🎜Après avoir importé les dépendances ci-dessus, l'outil prendra automatiquement effet, nous pouvons modifier la configuration correspondante. Tout d'abord, nous effectuons quelques configurations pour l'outil : 🎜rrreee🎜Dans la configuration ci-dessus, jasypt.encryptor.password doit être configuré, qui est le cryptage et le déchiffrement La clé, l'algorithme de cryptage par défaut est <code>PBEWITHMACSHA512ANDAES_256 en plus, jasypt.encryptor.property.prefix et jasypt. encryptor.property.suffix sont respectivement Le préfixe et le suffixe du texte chiffré sont utilisés pour marquer le texte chiffré qui doit être déchiffré. S'il n'est pas configuré, le préfixe du texte chiffré par défaut est ENC(, et le suffixe du texte chiffré est ) ; Par défaut, notre texte chiffré ressemble à ceci : 🎜rrreee🎜 Un autre point à noter est que jasypt.encryptor.password ne peut pas être placé ensemble. avec le texte chiffré. Nous pouvons transmettre les propriétés du système, les paramètres de ligne de commande ou les variables d'environnement ; notre projet a besoin, nous pouvons également implémenter le cryptage et le déchiffrement par vous-même : 🎜rrreee🎜Faites attention à notre BeanName Il doit être défini sur jasyptStringEncryptor par défaut, sinon il ne le sera pas. Si vous souhaitez modifier ce BeanName , vous pouvez également personnaliser le BeanName correspondant à l'instance StringEncryptor en modifiant ce paramètre de configuration : 🎜 rrreee

Comment générer du texte chiffré

🎜Génération Cette opération de texte chiffré doit encore être chiffrée et générée en appelant l'instance StringEncryptor. Vous pouvez vous référer au code suivant : 🎜rrreee🎜After. Dans l'ensemble, l'opération qui nécessite un chiffrement ne doit être effectuée qu'une seule fois dans le cycle de vie du projet, il suffit donc d'écrire une classe d'outils et de l'appeler. 🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer