【问题标题】:Loading a custom ApplicationContextInitializer in AWS Lambda Spring boot在 AWS Lambda Spring boot 中加载自定义 ApplicationContextInitializer
【发布时间】:2019-06-20 17:47:12
【问题描述】:

如何在 Spring Boot AWS Lambda 中加载自定义 ApplicationContextInitializer? 我有一个使用 spring boot 的 aws lambda 应用程序,我想编写一个 ApplicationContextInitializer 来解密数据库密码。我有以下代码在本地作为 Spring Boot 应用程序运行时可以工作,但是当我将它作为 lambda 部署到 AWS 控制台时它不起作用。

这是我的代码 1.applications.properties

spring.datasource.url=url
spring.datasource.username=testuser
CIPHER.spring.datasource.password=encryptedpassword

下面的代码是ApplicationContextInitializer,假设密码是Base64编码的,仅供测试使用(实际情况下会被AWM KMS加密)。这里的想法是密钥是否以“CIPHER”开头。 (如在 CIPHER.spring.datasource.password 中)我假设它的值需要被解密,并且另一个具有实际密钥的键值对(这里是 spring.datasource.password)及其解密值将在上下文初始化时添加。

就像spring.datasource.password=decrypted password

@Component
public class DecryptedPropertyContextInitializer 
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {

  private static final String CIPHER = "CIPHER.";

  @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 && key.startsWith(CIPHER)) {
                String cipherRemovedKey =  key.substring(CIPHER.length());
                String decodedValue = decode((String) rawValue);
                propertyOverrides.put(cipherRemovedKey, decodedValue);
            }
        }
    }
}

  public String decode(String encodedString) {
    byte[] valueDecoded = org.apache.commons.codec.binary.Base64.decodeBase64(encodedString);
    return new String(valueDecoded);
  }

这是 Spring 启动初始化程序

@SpringBootApplication
@ComponentScan(basePackages = "com.amazonaws.serverless.sample.springboot.controller")
public class Application extends SpringBootServletInitializer {

    @Bean
    public HandlerMapping handlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    @Bean
    public HandlerAdapter handlerAdapter() {
        return new RequestMappingHandlerAdapter();
    }

    @Bean
    public HandlerExceptionResolver handlerExceptionResolver() {
        return new HandlerExceptionResolver() {
            @Override
            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                return null;
            }
        };
    }

   //loading the initializer here
   public static void main(String[] args) {
     SpringApplication application=new SpringApplication(Application.class);
     application.addInitializers(new DecryptedPropertyContextInitializer());
     application.run(args);
    }

这在作为 Spring Boot 应用程序运行时有效,但是当它作为 lambda 部署到 AWS 中时,我的 SpringBootServletInitializer 中的 main() 方法将永远不会被 lambda 调用。这是我的 Lambda 处理程序。

public class StreamLambdaHandler implements RequestStreamHandler {
  private static Logger LOGGER = LoggerFactory.getLogger(StreamLambdaHandler.class); 

    private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
    static {
        try {
            handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
            handler.onStartup(servletContext -> {
                FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class);
                registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
            });
        } catch (ContainerInitializationException e) {
            e.printStackTrace();
            throw new RuntimeException("Could not initialize Spring Boot application", e);
        }
    }

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
        handler.proxyStream(inputStream, outputStream, context);
        outputStream.close();
    }
}

要在代码中进行哪些更改以通过 Lambda 加载 ApplicationContextInitializer?任何帮助将不胜感激。

【问题讨论】:

    标签: spring amazon-web-services spring-boot aws-lambda aws-kms


    【解决方案1】:

    我能够通过以下方式将其钉牢。

    先用带前缀的占位符更改属性值,其中前缀表示需要解密的值,例如

    spring.datasource.password=${MY_PREFIX_placeHolder}

    aws lambda 环境变量名称应与占位符匹配

    ('MY_PREFIX_placeHolder') 并且它的值使用 AWS KMS 加密(此示例为 base64 解码)。

    创建一个将解密属性值的 ApplicationContextInitializer

    public class DecryptedPropertyContextInitializer 
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
      private static final String CIPHER = "MY_PREFIX_";
    
      @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 && key.startsWith(CIPHER)) {
                    String decodedValue = decode((String) rawValue);
                    propertyOverrides.put(key, decodedValue);
                }
            }
        }
    }
    
      public String decode(String encodedString) {
        byte[] valueDecoded = org.apache.commons.codec.binary.Base64.decodeBase64(encodedString);
        return new String(valueDecoded);
      }
    }
    

    上述代码将解密所有前缀为 MY_PREFIX_ 的值,并将它们添加到属性源的顶部。

    由于 spring boot 部署到 aws lambda 中,lambda 将不会调用 main() 函数,因此如果 ApplicationContextInitializer 在 main() 中初始化,它将无法工作。为了让它工作需要重写 SpringBootServletInitializer 的 createSpringApplicationBuilder() 方法,所以 SpringBootServletInitializer 会像

    @SpringBootApplication
    @ComponentScan(basePackages = "com.amazonaws.serverless.sample.springboot.controller")
    public class Application extends SpringBootServletInitializer {
    
        @Bean
        public HandlerMapping handlerMapping() {
            return new RequestMappingHandlerMapping();
        }
    
        @Bean
        public HandlerAdapter handlerAdapter() {
            return new RequestMappingHandlerAdapter();
        }
    
        @Bean
        public HandlerExceptionResolver handlerExceptionResolver() {
            return new HandlerExceptionResolver() {
                @Override
                public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                    return null;
                }
            };
        }
    
    
    @Override
    protected SpringApplicationBuilder createSpringApplicationBuilder() {
      SpringApplicationBuilder builder = new SpringApplicationBuilder();
      builder.initializers(new DecryptedPropertyContextInitializer());
      return builder;
    }
    
       public static void main(String[] args) {
          SpringApplication.run(Application.class, args);
        }
    }
    

    无需对 lambdahandler 进行任何更改。

    【讨论】:

      猜你喜欢
      • 2016-05-15
      • 1970-01-01
      • 2020-08-15
      • 2021-07-30
      • 1970-01-01
      • 1970-01-01
      • 2017-08-18
      • 2021-11-22
      • 2018-10-22
      相关资源
      最近更新 更多