Facebook
From Bruno, 2 Years ago, written in Java.
Embed
Download Paste or View Raw
Hits: 69
  1. package com.benu.anubis.salesforce.service.impl;
  2.  
  3. import com.benu.anubis.core.exception.ExceptionReporterService;
  4. import com.benu.anubis.salesforce.service.api.HttpRequestHandler;
  5. import com.benu.anubis.salesforce.service.exception.AuthenticationException;
  6. import com.benu.anubis.salesforce.service.api.OAuthService;
  7. import com.benu.anubis.salesforce.entity.OauthResponse;
  8. import com.benu.anubis.salesforce.repository.AccessTokenRepository;
  9. import org.apache.commons.codec.binary.Base64;
  10. import org.jetbrains.annotations.NotNull;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.beans.factory.annotation.Value;
  15. import org.springframework.core.ParameterizedTypeReference;
  16. import org.springframework.http.HttpHeaders;
  17. import org.springframework.http.ResponseEntity;
  18. import org.springframework.stereotype.Service;
  19. import org.springframework.web.client.RestTemplate;
  20. import org.springframework.web.util.UriComponentsBuilder;
  21.  
  22. import java.io.FileInputStream;
  23. import java.nio.charset.StandardCharsets;
  24. import java.security.KeyStore;
  25. import java.security.PrivateKey;
  26. import java.security.Signature;
  27. import java.text.MessageFormat;
  28. import java.time.LocalDateTime;
  29. import java.util.Optional;
  30.  
  31. import static org.springframework.http.HttpMethod.POST;
  32.  
  33. /**
  34.  * SalesForce uses the Oauth strategy to implement authentication. First you will need to register the client,
  35.  * then you will need to create a self-client token. With that self-client token you can make a first token generation
  36.  * request to get a refresh_token. With that refresh_token, you can then generate access_tokens as you make
  37.  * requests to the platform. All these steps were done and the refresh_token is saved as a secret and inject in the
  38.  * SalesForce.token.refresh_token property in the catalog-service.yml configuration file for each environment.
  39.  * Check out the official documentation here: https://www.SalesForce.com/crm/help/developer/api/oauth-overview.html
  40.  */
  41. @Service
  42. public class OAuthServiceImpl implements OAuthService {
  43.  
  44.   private final Logger logger = LoggerFactory.getLogger(OAuthServiceImpl.class);
  45.  
  46.  
  47.   String header = "{\"alg\":\"RS256\"}";
  48.   String claimTemplate = "'{'\"iss\": \"{0}\", \"sub\": \"{1}\", \"aud\": \"{2}\", \"exp\": \"{3}\"'}'";
  49.  
  50.   @Value("${oauth2.jwt.iss}")
  51.   private String iss;
  52.   @Value("${oauth2.jwt.sub}")
  53.   private String sub;
  54.   @Value("${oauth2.jwt.aud}")
  55.   private String aud;
  56.   @Value("${oauth2.jwt.jks.location}")
  57.   private String jksLocation;
  58.   @Value("${oauth2.jwt.jks.key}")
  59.   private String jksKey;
  60.   @Value("${oauth2.jwt.jks.alias}")
  61.   private String jksAlias;
  62.   @Value("${oauth2.token}")
  63.   private String tokenUrl;
  64.   @Value("${oauth2.grantType}")
  65.   private String grantType;
  66.  
  67.   private final RestTemplate restTemplate;
  68.   private final AccessTokenRepository accessTokenRepository;
  69.   private final ExceptionReporterService exceptionReporterService;
  70.  
  71.   @Autowired
  72.   public OAuthServiceImpl(RestTemplate restTemplate, AccessTokenRepository accessTokenRepository, ExceptionReporterService exceptionReporterService) {
  73.     this.restTemplate = restTemplate;
  74.     this.accessTokenRepository = accessTokenRepository;
  75.     this.exceptionReporterService = exceptionReporterService;
  76.   }
  77.  
  78.   /**
  79.    * Creates the HTTP header with the Authorization entry (cached access token/new access token)
  80.    * @return http headers with the authorization attribute
  81.    * @throws AuthenticationException In case there was an issue generating the access token
  82.    */
  83.   @NotNull
  84.   public HttpHeaders getAuthHeader() throws AuthenticationException {
  85.     HttpHeaders headers = new HttpHeaders();
  86.     headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken());
  87.     return headers;
  88.   }
  89.  
  90.   /**
  91.    * Creates the HTTP header with the Authorization entry (new access token)
  92.    * @return http headers with the authorization attribute
  93.    * @throws AuthenticationException In case there was an issue generating the access token
  94.    */
  95.   @NotNull
  96.   public HttpHeaders getAuthHeaderWithNewToken() throws AuthenticationException {
  97.     HttpHeaders headers = new HttpHeaders();
  98.     headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + generateNewAccessToken().getAccessToken());
  99.     return headers;
  100.   }
  101.  
  102.   private String getAccessToken() throws AuthenticationException {
  103.     try {
  104.       Optional<OauthResponse> tokenFromDatabase = findAvailableAccessToken();
  105.       return tokenFromDatabase.isPresent() ?
  106.               tokenFromDatabase.get().getAccessToken()
  107.               : generateNewAccessToken().getAccessToken();
  108.     } catch (Exception exception) {
  109.       logger.error(String.format("[Salesforce OAuth Service] Failed to Authenticate: %s", exception.getMessage()));
  110.       exceptionReporterService.report(exception, "generateAccessToken");
  111.       throw new AuthenticationException(exception.getMessage());
  112.     }
  113.   }
  114.  
  115.   private Optional<OauthResponse> findAvailableAccessToken() {
  116.     return accessTokenRepository.findTopByExpirationDateGreaterThan(LocalDateTime.now());
  117.   }
  118.  
  119.   /**
  120.    * Generate the token and save it to be reused in the future
  121.    * @return  OOauthResponse the generated token
  122.    */
  123.   private OauthResponse generateNewAccessToken() throws AuthenticationException {
  124.     UriComponentsBuilder builder =
  125.             UriComponentsBuilder.fromHttpUrl(tokenUrl)
  126.                     .queryParam("grant_type", grantType)
  127.                     .queryParam("assertion", generateJWT());
  128.  
  129.     ParameterizedTypeReference<OauthResponse> ptr = new ParameterizedTypeReference<>() {};
  130.     ResponseEntity<OauthResponse> response = restTemplate.exchange(builder.toUriString(), POST, null, ptr);
  131.     OauthResponse responseBody = Optional.ofNullable(response.getBody()).orElseThrow(RuntimeException::new);
  132.     saveAccessToken(responseBody);
  133.     return responseBody;
  134.   }
  135.  
  136.   /**
  137.    * Saves the token to the database, setting the expiration date for 2 hour later so that it can be easily retrieved
  138.    * and used up until it expires.
  139.    * @param accessToken the generated token
  140.    */
  141.   private void saveAccessToken(OauthResponse accessToken) {
  142.     LocalDateTime localDateTime = LocalDateTime.now().plusHours(2);
  143.     accessToken.setExpirationDate(localDateTime);
  144.     accessTokenRepository.save(accessToken);
  145.   }
  146.  
  147.   /**
  148.    * Generate JWT token
  149.    * Check out documentation here: https://help.salesforce.com/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm&type=5
  150.    * @return JWT generated token
  151.    */
  152.   public String generateJWT() throws AuthenticationException {
  153.     try {
  154.       StringBuilder token = new StringBuilder();
  155.  
  156.       //Encode the JWT Header and add it to our string to sign
  157.       token.append(Base64.encodeBase64URLSafeString(header.getBytes(StandardCharsets.UTF_8)));
  158.  
  159.       //Separate with a period
  160.       token.append(".");
  161.  
  162.       //Create the JWT Claims Object
  163.       String[] claimArray = new String[4];
  164.       claimArray[0] = iss;
  165.       claimArray[1] = sub;
  166.       claimArray[2] = aud;
  167.       claimArray[3] = Long.toString((System.currentTimeMillis() / 1000) + 300);
  168.  
  169.       MessageFormat claims;
  170.       claims = new MessageFormat(claimTemplate);
  171.       String payload = claims.format(claimArray);
  172.  
  173.       //Add the encoded claims object
  174.       token.append(Base64.encodeBase64URLSafeString(payload.getBytes(StandardCharsets.UTF_8)));
  175.  
  176.       //Load the private key from a keystore
  177.       KeyStore keystore = KeyStore.getInstance("JKS");
  178.       keystore.load(new FileInputStream(jksLocation), jksKey.toCharArray());
  179.       PrivateKey privateKey = (PrivateKey) keystore.getKey(jksAlias, jksKey.toCharArray());
  180.  
  181.       //Sign the JWT Header + "." + JWT Claims Object
  182.       Signature signature = Signature.getInstance("SHA256withRSA");
  183.       signature.initSign(privateKey);
  184.       signature.update(token.toString().getBytes(StandardCharsets.UTF_8));
  185.       String signedPayload = Base64.encodeBase64URLSafeString(signature.sign());
  186.  
  187.       //Separate with a period
  188.       token.append(".");
  189.  
  190.       //Add the encoded signature
  191.       token.append(signedPayload);
  192.       return token.toString();
  193.     } catch (Exception e) {
  194.       throw new AuthenticationException("Error While Generating JWT Token");
  195.     }
  196.   }
  197. }