|
| 1 | +# Token Manager for Salesforce |
| 2 | + |
| 3 | +This project makes Salesforce API calls with Spring a breeze. It exposes either a RestTemplate or WebClient that handles generating, refreshing and attaching an authorization token header for every API call. Just pass in your instance and user credentials via `application.yml`, autowire your desired bean and it takes care of the rest. |
| 4 | + |
| 5 | +## Usage |
| 6 | + |
| 7 | +To use this library, the following repository declaration and one of the dependencies to your project's `build.gradle` file. This will include the core module as well automatically. |
| 8 | + |
| 9 | +```groovy |
| 10 | +repositories { |
| 11 | + maven { |
| 12 | + name = "GitHubPackages" |
| 13 | + url = uri("https://maven.pkg.github.com/target/token-manager-for-salesforce") |
| 14 | + credentials { |
| 15 | + username = project.findProperty("gpr.user") ?: System.getenv("USERNAME") |
| 16 | + password = project.findProperty("gpr.token") ?: System.getenv("TOKEN") |
| 17 | + } |
| 18 | + } |
| 19 | +} |
| 20 | +dependencies { |
| 21 | + // for reactive applications |
| 22 | + implementation "com.tgt.crm:token-manager-for-salesforce-webflux:${libraryVersion}" |
| 23 | + // for non-reactive applications |
| 24 | + implementation "com.tgt.crm:token-manager-for-salesforce-webmvc:${libraryVersion}" |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +Find the latest version in the "packages" section of this repo. You will need access to the Github repository and will need to make your user and a personal access token available as a project or system property to authorize with Github and download the package. More details can be found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry). |
| 29 | + |
| 30 | +Add the following to your `application.yml` file to pass in Salesforce properties from TAP secrets/environment variables. You can set explicit values for these properties instead of environment variables if you prefer. Ensure you don't check any secrets into Git. |
| 31 | + |
| 32 | +```yaml |
| 33 | +salesforce: |
| 34 | + host: ${SALESFORCE_HOST} |
| 35 | + username: ${SALESFORCE_USERNAME} |
| 36 | + password: ${SALESFORCE_PASSWORD} |
| 37 | + client-id: ${SALESFORCE_CLIENT_ID} |
| 38 | + client-secret: ${SALESFORCE_CLIENT_SECRET} |
| 39 | + auth-uri: /services/oauth2/token # optional |
| 40 | + retry-backoff-delay: 1000 # optional, configures retry for auth token requests only |
| 41 | + max-auth-token-retries: 3 # optional, configures retry for auth token requests only |
| 42 | + retry-backoff-multiplier: 2 # optional, configures retry for auth token requests only, only used by MVC, see SalesforceConfig for more info |
| 43 | + httpclient: |
| 44 | + max-conn-per-route: 20 # optional |
| 45 | + read-timeout: 30000 # optional, in milliseconds |
| 46 | + connection-timeout: 60000 # optional, in milliseconds |
| 47 | + mvc: # configs in this block only available for webmvc |
| 48 | + max-pools: 50 # optional |
| 49 | + connection-request-timeout: 30000 # optional, in milliseconds |
| 50 | + retries: 3 # optional, MVC only, configures default # of retries for all requests except auth token |
| 51 | + retry-interval: 2000 # optional, in milliseconds, configures default retry interval for all requests except auth token |
| 52 | +``` |
| 53 | +
|
| 54 | +You should then be able to autowire the RestTemplate or WebClient bean in any component in your project and use it to make API calls to Salesforce. |
| 55 | +
|
| 56 | +```java |
| 57 | +@Service |
| 58 | +public class SalesforceClient { |
| 59 | + |
| 60 | + private final RestTemplate salesforceRestTemplate; |
| 61 | + |
| 62 | + public SalesforceClient(@Qualifier("sfRestTemplate") final RestTemplate salesForceRestTemplate) { |
| 63 | + this.salesForceRestTemplate = salesForceRestTemplate; |
| 64 | + } |
| 65 | + |
| 66 | + public String querySalesforce() { |
| 67 | + ResponseEntity<String> sfResponse = |
| 68 | + salesForceRestTemplate.exchange( |
| 69 | + "/services/data/v50.0/query&q={query}", |
| 70 | + HttpMethod.GET, |
| 71 | + HttpEntity.EMPTY, |
| 72 | + String.class, |
| 73 | + "SELECT Id FROM Case"); |
| 74 | + |
| 75 | + return sfResponse.getBody(); |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +```java |
| 81 | +@Service |
| 82 | +public class SalesforceClient { |
| 83 | + |
| 84 | + private final WebClient salesforceWebClient; |
| 85 | + |
| 86 | + public SalesforceClient(@Qualifier("sfWebClient") final WebClient salesforceWebClient) { |
| 87 | + this.salesforceWebClient = salesforceWebClient; |
| 88 | + } |
| 89 | + |
| 90 | + public Mono<String> querySalesforce() { |
| 91 | + return webClient |
| 92 | + .get() |
| 93 | + .uri("/services/data/v50.0/query&q={query}", "SELECT Id FROM Case") |
| 94 | + .retrieve() |
| 95 | + .toEntity(String.class) |
| 96 | + .map(HttpEntity::getBody); |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +### Minimum Requirements |
| 102 | + |
| 103 | +In your project, the following minimum versions of Spring Boot are required to use this library: |
| 104 | + |
| 105 | +* token-manager-for-salesforce-webflux: Spring Boot > 2.2.6.RELEASE |
| 106 | + * Spring Boot > 2.4.0 is recommended to support Wiretap for WebClient debug logging |
| 107 | +* token-manager-for-salesforce-webmvc: Spring Boot > 2.2.0.RELEASE |
| 108 | + |
| 109 | +### Metrics |
| 110 | + |
| 111 | +The application also emits one micrometer metric. If a token refresh fails, a counter is incremented. The counter is called `exception_counter` and has one tag `exception_type` with value `token_refresh_exception`. This can be used to set up an alert in Grafana if a token refresh ever fails. |
| 112 | + |
| 113 | +### How does it work? |
| 114 | + |
| 115 | +This library follows the [OAuth 2.0 Username-Password Flow](https://help.salesforce.com/articleView?id=remoteaccess_oauth_username_password_flow.htm&type=5) and is intended to be used with first-party applications. |
| 116 | + |
| 117 | +When your application starts up it will not have a token. The first time it makes an API call, the library intercepts the request, makes the appropriate API call to `/services/oauth2/token` for your configured instance as documented [here](https://help.salesforce.com/articleView?id=remoteaccess_oauth_endpoints.htm&type=5). The generated token is attached to the request as an `Authorization` header and the request proceeds. It is also cached in memory. |
| 118 | + |
| 119 | +Subsequent requests use the cached token and do not try to request a new token unless a 401 response is received. When a 401 is received, it attempts to generate a new token and retries the request. We use this behavior because Salesforce auth tokens do not return an `expires_in` property and the length of time they are valid for can vary from instance to instance based on admin settings. |
| 120 | + |
| 121 | +### Debugging Requests |
| 122 | + |
| 123 | +It is possible and occasionally useful to log complete HTTP requests and responses including URLs, query params, headers and bodies. Be careful as this has the potential to expose sensitive data such as passwords, auth tokens or API keys. It is recommended to only use this when running the application locally. |
| 124 | + |
| 125 | +To enable request/response logging for either mvc or webflux library, add the following to your `application.yml`: |
| 126 | + |
| 127 | +```yaml |
| 128 | +logging: |
| 129 | + level: |
| 130 | + com.tgt.crm.token: TRACE |
| 131 | + org.springframework.web.client.RestTemplate: DEBUG # additional RestTemplate debug logs, only applies to MVC. WARNING: logs sensitive info |
| 132 | + org.apache.http: DEBUG # additional detailed logging for RestTemplate, only applies to MVC. WARNING: logs sensitive info |
| 133 | + reactor.netty: TRACE # additional detailed logging for WebClient, only applies to WebFlux |
| 134 | +``` |
| 135 | +
|
| 136 | +Note that setting log level to `DEBUG` will enable all debug logging except request/response logging. This is by design to help prevent unintentional sensitive data exposure. |
| 137 | + |
| 138 | +## Local Development |
| 139 | + |
| 140 | +If you make changes to this library locally and want to test those changes with another application, you can use [Gradle Composite Builds](https://docs.gradle.org/current/userguide/composite_builds.html). Essentially you just tell your other application to point to this project on your local file system instead of downloading the dependency from a repository. |
| 141 | + |
| 142 | +In the `settings.gradle` file in the root of the other project (you may have to create the file if it doesn't exist in that project), add the following line: |
| 143 | + |
| 144 | +``` |
| 145 | +includeBuild '../token-manager-for-salesforce' |
| 146 | +``` |
| 147 | + |
| 148 | +Where `../token-manager-for-salesforce` is the relative path to this project on your local file system. Now when you build the other project, it should use your local copy of `token-manager-for-salesforce`. Be sure to remove this change before committing. |
| 149 | + |
| 150 | +## Publish a new version |
| 151 | + |
| 152 | +Make the desired changes locally. Open a PR against the master branch, get it reviewed and merge. Tag this commit with a tag following [semantic versioning](https://semver.org/). This will trigger a new deployment of the library to [Github Packages](https://github.com/orgs/target/packages?repo_name=token-manager-for-salesforce). |
| 153 | + |
| 154 | +## Troubleshooting |
| 155 | + |
| 156 | +Problem: When I try to start my application I am getting a `NoClassDefFoundError` or a `NoSuchMethodError`. |
| 157 | + |
| 158 | +Solution: Check the version of Spring Boot you are using in your app and that it meets the minimum requirement listed above in the README. |
| 159 | + |
| 160 | +## License |
| 161 | + |
| 162 | +token-manager-for-salesforce is licensed under the [MIT License](LICENSE.md) |
| 163 | + |
| 164 | +Salesforce is a trademark of Salesforce.com, inc., and is used here with permission. |
0 commit comments