2015-08-13 11 views
7

Ho il compito di nascondere le password nei nostri file di configurazione. Mentre non penso che questo sia l'approccio giusto, i manager non sono d'accordo ...Valori di proprietà estese avvio processo Spring

Quindi il progetto su cui sto lavorando si basa su Spring Boot e stiamo usando i file di configurazione YAML. Attualmente le password sono in testo normale:

spring: 
    datasource: 
     url: jdbc:sqlserver://DatabaseServer 
     driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver 
     username: ele 
     password: NotTheRealPassword 

L'idea è quella di avere un po 'speciale sintassi che supporta una password offuscato o crittografati:

spring: 
    datasource: 
     url: jdbc:sqlserver://DatabaseServer 
     driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver 
     username: ele 
     password: password(Tm90VGhlUmVhbFBhc3N3b3Jk) 

Al fine di far funzionare tutto questo voglio analizzare la proprietà valori che utilizzano un'espressione regolare e se corrispondono sostituiscono il valore con il valore deobfuscated/decrypted.

Ma come si intercetta il valore della proprietà?

+0

Non penso che tu possa, se stai solo usando qualcosa di costruito in Primavera. Tuttavia, se stai caricando manualmente questo YAML, quindi lo inserisci in Spring, fallo lì. –

+0

Suppongo di poter scrivere il mio PropertySource. Speravo di trovare un meccanismo che si applica a tutte le fonti di proprietà. –

+2

Potresti essere interessato a [questo problema Spring Boot] (https://github.com/spring-projects/spring-boot/issues/1312) –

risposta

9

Se finalmente ha funzionato. (Principalmente grazie a stephane-deraco su github)

La chiave per la soluzione è una classe che implementa ApplicationContextInitializer<ConfigurableApplicationContext>. L'ho chiamato PropertyPasswordDecodingContextInitializer.

Il problema principale era ottenere la primavera per usare questo ApplicationContextInitializer. Informazioni importanti sono disponibili nello reference. Ho scelto l'approccio utilizzando un META-INF/spring.factories con seguente contenuto:

org.springframework.context.ApplicationContextInitializer=ch.mycompany.myproject.PropertyPasswordDecodingContextInitializer 

Il PropertyPasswordDecodingContextInitializer utilizza un PropertyPasswordDecoder e una classe di attuazione, attualmente per semplicità un Base64PropertyPasswordDecoder.

PropertyPasswordDecodingContextInitializer.java

package ch.mycompany.myproject; 

import java.util.LinkedHashMap; 
import java.util.Map; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 

import org.springframework.context.ApplicationContextInitializer; 
import org.springframework.context.ConfigurableApplicationContext; 
import org.springframework.core.env.CompositePropertySource; 
import org.springframework.core.env.ConfigurableEnvironment; 
import org.springframework.core.env.EnumerablePropertySource; 
import org.springframework.core.env.MapPropertySource; 
import org.springframework.core.env.PropertySource; 
import org.springframework.stereotype.Component; 

@Component 
public class PropertyPasswordDecodingContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { 

    private static final Pattern decodePasswordPattern = Pattern.compile("password\\((.*?)\\)"); 

    private PropertyPasswordDecoder passwordDecoder = new Base64PropertyPasswordDecoder(); 

    @Override 
    public void initialize(ConfigurableApplicationContext applicationContext) { 
     ConfigurableEnvironment environment = applicationContext.getEnvironment(); 
     for (PropertySource<?> propertySource : environment.getPropertySources()) { 
      Map<String, Object> propertyOverrides = new LinkedHashMap<>(); 
      decodePasswords(propertySource, propertyOverrides); 
      if (!propertyOverrides.isEmpty()) { 
       PropertySource<?> decodedProperties = new MapPropertySource("decoded "+ propertySource.getName(), propertyOverrides); 
       environment.getPropertySources().addBefore(propertySource.getName(), decodedProperties); 
      } 
     } 
    } 

    private void decodePasswords(PropertySource<?> source, Map<String, Object> propertyOverrides) { 
     if (source instanceof EnumerablePropertySource) { 
      EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>) source; 
      for (String key : enumerablePropertySource.getPropertyNames()) { 
       Object rawValue = source.getProperty(key); 
       if (rawValue instanceof String) { 
        String decodedValue = decodePasswordsInString((String) rawValue); 
        propertyOverrides.put(key, decodedValue); 
       } 
      } 
     } 
    } 

    private String decodePasswordsInString(String input) { 
     if (input == null) return null; 
     StringBuffer output = new StringBuffer(); 
     Matcher matcher = decodePasswordPattern.matcher(input); 
     while (matcher.find()) { 
      String replacement = passwordDecoder.decodePassword(matcher.group(1)); 
      matcher.appendReplacement(output, replacement); 
     } 
     matcher.appendTail(output); 
     return output.toString(); 
    } 

} 

PropertyPasswordDecoder.java

package ch.mycompany.myproject; 

public interface PropertyPasswordDecoder { 

    public String decodePassword(String encodedPassword); 

} 

Base64PropertyPasswordDecoder.java

package ch.mycompany.myproject; 

import java.io.UnsupportedEncodingException; 

import org.apache.commons.codec.binary.Base64; 

public class Base64PropertyPasswordDecoder implements PropertyPasswordDecoder { 

    @Override 
    public String decodePassword(String encodedPassword) { 
     try { 
      byte[] decodedData = Base64.decodeBase64(encodedPassword); 
      String decodedString = new String(decodedData, "UTF-8"); 
      return decodedString; 
     } catch (UnsupportedEncodingException e) { 
      throw new RuntimeException(e); 
     } 
    } 


} 

Attenzione, ApplicationContext non è stato inizializzato in questa fase, quindi il meccanismo di autowiring o altri meccanismi relativi ai bean non funzionerà.


Aggiornamento: Incluso @jny 's suggerimenti.

+0

ho notato che hai usato la versione apache di base64 invece del tuo codice in https://github.com/spring-projects/spring-boot/search?utf8=%E2%9C%93&q=.base64 perché? – shareef

+0

Fondamentalmente non mi interessa davvero quale implementazione usare. Utilizziamo già le librerie di apache commons quindi non ho notato che ce n'è una anche in spring boot. –

3

Ho usato la risposta di @Daniele Torino e ho apportato diverse modifiche minori.

In primo luogo, grazie al suo legame con le opzioni su come rendere la primavera riconoscere Initializer, ho scelto di farlo nel Application: Seconda

public static void main(String[] args) throws Exception { 
    SpringApplication application=new SpringApplication(Application.class); 
    application.addInitializers(new PropertyPasswordDecodingContextInitializer()); 
    application.run(args); 
} 

, IDEA mi ha detto che che else if (source instanceof CompositePropertySource) { è ridondante ed è perché CompositePropertySource eredita da EnumerablePropertySource.

In terzo luogo, credo che ci sia un piccolo bug: sconvolge l'ordine di risoluzione della proprietà. Se si dispone di una proprietà codificata in ambiente e un'altra nel file application.properties, il valore dell'ambiente verrà sovrascritto con il valore application.properties. Ho cambiato la logica di inserire le decodedProperties destra prima codificati:

 for (PropertySource<?> propertySource : environment.getPropertySources()) { 
       Map<String, Object> propertyOverrides = new LinkedHashMap<>(); 
       decodePasswords(propertySource, propertyOverrides); 
       if (!propertyOverrides.isEmpty()) { 
         environment.getPropertySources().addBefore(propertySource.getName(), new MapPropertySource("decoded"+propertySource.getName(), propertyOverrides)); 
       } 
     } 
+1

Ho aggiornato la risposta per includere i vostri suggerimenti. La tua risposta sembra avere un piccolo bug, in cui crei due 'MapPropertySource' - il secondo prende' decodedProperties' come argomento. L'argomento dovrebbe essere un 'Map', ma è un' PropertySource '. –

+0

Grazie, ho aggiornato il codice – jny

1

Ispirata @gogstad. Qui è la mia azione importante nel progetto di avvio di primavera a cifrato il mio nome utente e la password e li decifrati nel progetto di lavorare con Tomcat:

1. Nel file di pom.xml

<dependency> 
     <groupId>com.github.ulisesbocchio</groupId> 
     <artifactId>jasypt-spring-boot</artifactId> 
     <version>1.12</version> 
    </dependency> 
    … 
    <build> 
     <resources> 
      <resource> 
       <directory>src/main/java</directory> 
       <includes> 
       <include>**/*.properties</include> 
       <include>**/*.xml</include> 
       </includes> 
       <targetPath>${project.build.directory}/classes</targetPath> 
      </resource> 
      <resource> 
       <directory>src/main/resources</directory> 
       <includes> 
        <include>**/*.properties</include> 
       </includes> 
       <targetPath>${project.build.directory}/classes</targetPath> 
     </resource> 
    </resources> 
    … 
    </build> 

2. In App.java (Nota: per distribuire lo springboot decryted su Tomcat, è necessario aggiungere l'annotazione @ServletComponentScan ed estende lo SpringBootServletInitializer)

@SpringBootApplication 
    @ServletComponentScan 
    @EnableEncryptableProperties 
    @PropertySource(name="EncryptedProperties", value = "classpath:config/encrypted.properties") 
    public class App extends SpringBootServletInitializer { 
    public static void main(String[] args) throws Exception { 
     SpringApplication.run(App.class, args); 
     } 

    } 

3. crittografati il ​​nome utente e la password e riempire il file application.properties con il risultato:

java -cp ~/.m2/repository/org/jasypt/jasypt/1.9.2/jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="mypassword" password=mykey algorithm=PBEWithMD5AndDES 

uscita è come la demo di seguito:

java -cp ~/.m2/repository/org/jasypt/jasypt/1.9.2/jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="mypassword" password=mykey algorithm=PBEWithMD5AndDES 

    ----ENVIRONMENT----------------- 

    Runtime: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 25.45-b02 



    ----ARGUMENTS------------------- 

    algorithm: PBEWithMD5AndDES 
    input: mypassword 
    password: mykey 



    ----OUTPUT---------------------- 

    5XNwZF4qoCKTO8M8KUjRprQbivTkmI8H 

4. sotto la directory src/main/resources/config aggiungi due proprietà file:

a. application.properties 
     spring.datasource.driver-class-name=com.mysql.jdbc.Driver 
     spring.datasource.url=jdbc:mysql://xxx 
     spring.datasource.username=ENC(xxx) 
     spring.datasource.password=ENC(xxx) 
     mybatis.mapper-locations=classpath:*/mapper/*.xml 
     mybatis.type-aliases-package=com.xx.xxx.model 
     logging.level.com.xx.xxx: DEBUG 

    b. encrypted.properties 
     jasypt.encryptor.password=mykey 
Problemi correlati