Search code examples
javaspringspring-bootcloud-foundryconfiguration-files

Read CF environment nested JSON with @ConfigurationProperties


I have an application on SpringBoot that is deployes to Cloud Foundry. It has a bound service that attaches some information to VCAP_SERVICES that I can use in my application.properties file to receive security related data.

This services variables are in the user-provided sub-json of whole VCAP_SERVICE environment variable and looks like this:

  "user-provided": [
   {
    "credentials": {
     "auth-scopes": {
      "group-1": "superadmin",
      "group-2": "admin"
     },
     "base64ClientCredential": "SSBBTSBOT1QgVEhBVCBTSUxMWSA6LUQ=",
     "client-id": "my-app",
     "client-secret": "g1234567890",
     "token-info-uri": "https://XYZ.predix.io/check_token",
     "user-info-uri": "https://XYZ.predix.io/userinfo"
    },
    "label": "user-provided",
    "name": "my-app-creds",
    "syslog_drain_url": "",
    "tags": [],
    "volume_mounts": []
   }
  ]

In my application.properties I have

security.properties.client-id=${vcap.services.my-app-creds.credentials.client-id}
security.properties.client-secret=${vcap.services.my-app-creds.credentials.client-secret}
security.properties.token-info-uri=${vcap.services.my-app-creds.credentials.token-info-uri}
security.properties.user-info-uri=${vcap.services.my-app-creds.credentials.user-info-uri}
security.properties.auth-scopes=${vcap.services.my-app-creds.credentials.auth-scopes}

I also created class SecurityProperties that reads those properties and stores in JAVA:

@Component
@PropertySource("classpath:application.properties")
@ConfigurationProperties(prefix = "security.properties")
public class SecurityProperties {

    String clientId;
    String clientSecret;
    String tokenInfoUri;
    String userInfoUri;

    //Required getters and setters
}

And this class successfully binds those fields to information received from application.properties.

The problem occurs when I want to receive Map:

 "auth-scopes": {
  "group-1": "superadmin",
  "group-2": "admin"
 }

I tried adding to SecurityProperties class field:

HashMap<String, String> authScopes;

But it fails

I also tried with inner class

AuthScopes authScopes;

public static class AuthScopes {
    //various different implementations
    //to read this but no success at all
}

I ran out of other ideas. I just can't figure out how to get this. I will also accept that auth-scopes json from VCAPS will be read as a string, and then I will parse it - no problem. The issue is that even if I add to SecurityProperties field String authScopes it is bound with exactly this string: "${vcap.services.my-app-creds.credentials.auth-scopes}". Nothing useful.

If you have any idea - please share.


Solution

  • Spring Boot provides the vcap. properties via the CloudFoundryVcapEnvironmentPostProcessor. That class reads the VCAP_SERVICES JSON and flattens it into a set of discrete properties. If you add Spring Boot actuators to your application and access the /env endpoint, you will see the set of vcap. properties that get created.

    In your example, the properties will contain these values:

    "vcap": {
      ...
      "vcap.services.my-app-creds.credentials.auth-scopes.group-1": "******",
      "vcap.services.my-app-creds.credentials.auth-scopes.group-2": "******",
    }
    

    There is no vcap.services.my-service.credentials.auth-scopes property in that list, because the JSON get completely flattened. The property names use the dot notation to reflect the JSON structure, but the hierarchy is not preserved in the set of properties. In short, what you are trying to do won't work with Boot properties.

    One alternative would be to set those values as a string instead of a hash in the JSON, as in "auth-scopes": "group-1:superadmin,group-2:admin" or "auth-scopes": "group-1=superadmin,group-2=admin". Your SecurityProperties could then get the value of vcap.services.my-service.credentials.auth-scopes and parse the String into a Map.