Issue
I'm using azure keyvault to pull my application properties. I'm using spring @value
annotation to set the property value from the keyvault by placing the placeholder in the application.properties
file. In my main application context I was able to pull the properties and test the application flow. Were as in test context its throwing some issuing saying vault properties aren't injected. Here is my properties bean class looks like, and the stack trace of the issue. I tried to mock the KeyVaultProperties in the ControllerTest class still having same issue.
KeyVault.java
@Data
@Component
public class KeyVaultProperties {
@Value("${by-pass-token}")
private String token;
@Value("${backend-clients}")
private String clients;
}
ControllerTest.java
@SpringBootTest
@SpringBootConfiguration
@AutoConfigureMockMvc
public class ControllerTest {
@Autowired
Controller controller;
@Autowired
private MockMvc mockMvc;
@Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
}
Controller.java
@RestController
@Slf4j
@RequestMapping("/api/test")
public class Controller {
@GetMapping(value = "/hello")
public String getString() {
return "Hello";
}
}
AuthConfiguration.java
@Slf4j
@Component
public class AuthConfiguration extends HandlerInterceptorAdapter {
@Autowired
private KeyVaultProperties keyVaultProperties;
private static final String CORRELATION_ID_LOG_VAR_NAME = "correlationId";
private static final String CORRELATION_ID_HEADER_NAME = "Correlation-Id";
@PostConstruct
public void setup() {
System.out.println("-------@PostConstruct------setup----------------");
sub = keyVaultProperties.getClients();
ByPass = keyVaultProperties.getAuthByPassToken();
}
@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws Exception {
System.out.println("-------preHandle----------------------");
final Boolean isValidToken;
final String correlationId = getCorrelationIdFromHeader(request);
log.info("correlationId:{}",correlationId);
MDC.put(CORRELATION_ID_LOG_VAR_NAME, correlationId);
return true;
}
@Override
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
System.out.println("-------afterCompletion----------------------");
MDC.remove(CORRELATION_ID_LOG_VAR_NAME);
}
private String getCorrelationIdFromHeader(final HttpServletRequest request) {
String correlationId = request.getHeader(CORRELATION_ID_HEADER_NAME);
if (correlationId == null) {
correlationId = generateUniqueCorrelationId();
}
return correlationId;
}
}
app/src/main/resources/application.properties
by-pass-token = ${BY-PASS-TOKEN}
backend-clients = ${CLIENTS}
azure.keyvault.enabled=true
Stack Trace:
2021-04-04 13:28:03.640 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AuthConfiguration': Unsatisfied dependency expressed through field 'KeyVaultProperties'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'KeyVaultProperties': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'by-pass-token' in value "${by-pass-token}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject
Solution
You could set the value of properties to Azure Key Vault by authenticating via Azure AD.
Note: In order for your application to have access to the Key Vault contents, you must set the appropriate permissions for your application in the Key Vault. Navigate to Azure Key Vault > Access Policies > Add access policy > select your application in select principal.
Dependencies:
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-keyvault</artifactId>
<version>1.0.0</version>
</dependency>
Connect to key vault via AzureAD based on client credentials flow:
public class ClientSecretKeyVaultCredential extends KeyVaultCredentials
{
private String clientId;
private String clientKey;
public ClientSecretKeyVaultCredential( String clientId, String clientKey ) {
this.clientId = clientId;
this.clientKey = clientKey;
}
@Override
public String doAuthenticate(String authorization, String resource, String scope) {
AuthenticationResult token = getAccessTokenFromClientCredentials(
authorization, resource, clientId, clientKey);
return token.getAccessToken();
}
private static AuthenticationResult getAccessTokenFromClientCredentials(
String authorization, String resource, String clientId, String clientKey) {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authorization, false, service);
ClientCredential credentials = new ClientCredential(clientId, clientKey);
Future<AuthenticationResult> future = context.acquireToken(
resource, credentials, null);
result = future.get();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
service.shutdown();
}
if (result == null) {
throw new RuntimeException("authentication result was null");
}
return result;
}
}
Access Key vault:
You could use client.setSecret("Secret-Name", "value")
to set your properties.
// ClientSecretKeyVaultCredential is the implementation of KeyVaultCredentials
KeyVaultClient client = new KeyVaultClient(
new ClientSecretKeyVaultCredential(clientId, clientKey));
// KEYVAULT_URL is the location of the keyvault to use: https://<yourkeyvault>.vault.azure.net
SecretBundle secret = client.getSecret( KEYVAULT_URL, "Secret-name" );
log( secret.value() );
Answered By - Pamela Peng
Answer Checked By - Candace Johnson (JavaFixing Volunteer)