Salesforce Integration - OAuth2.0 JWT Bearer Flow

应用场景

  1. Salesforce作为Service Provider (SP),外部系统需要访问Salesforce数据;
  2. 在授权过程中无需通过UI Login页面输入账密;
  3. 不希望外部系统储存账密等可直接用于UI Login的信息;

前置条件

  1. 使用证书对JWT请求进行签名;
  2. 需要事先获得SP的批准;

案例设计

目标

通过JWT授权方式获取access_token,然后使用access_token call Salesforce API访问数据。
在这里插入图片描述

前提假设

  1. 使用openSSL形式本地获取server.crt (public key)和server.key (private key),然后上传到Connected App与Certificate and Key Management提供安全保障;
  2. 使用JWTBearerTokenExchange class来获取access_token;
  3. 为方便Demo,假设使用同一个Org进行自己调自己API;

关键步骤

#1. 通过openSSL获取下面4个文件:
在这里插入图片描述
#2 将server.crt上传到Connected App:
在这里插入图片描述
#3. 将本地的server.crtserver.key通过terminal转化成JKS File,然后修改keystore的Alias name,最后在Certificate and Key Management通过Import from Keystore将JKS File保存到Certificates相关列表。
在这里插入图片描述
可通过下面命令转JKS File:

# 根据公钥和私钥生成server.p12
openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12
# 根据server.p12生产jwt_apex.jks
keytool -importkeystore -srckeystore server.p12
        -srcstoretype PKCS12
        -destkeystore jwt_apex.jks
        -deststoretype JKS
# 修改keystore的默认alias name (默认是1)
keytool -keystore jwt-apex.jks -changealias -alias 1 -destalias jwt_apex
# 检查alias name是否修改正确
keytool -list -v -keystore jwt_apex.jks

# update on 2022-10-18 一行命令解决上面多行命令
keytool -importkeystore \
        -srckeystore ./gcp-logging-api-service-accounts.p12 \
        -srcstoretype PKCS12 \
        -srcalias privatekey \
        -srcstorepass notasecret \
        -destkeystore gcp-logging-api-service-accounts.jks \
        -deststoretype JKS \
        -destalias gcp_logging_api_cert \
        -deststorepass notasecret

为什么要修改alias name,以及alias name上传为Certificate时,有什么格式要求?

In order to successfully use “Import from Keystore” feature available at “Certificate and Key Management”, the ‘alias’ of the certificates within .JKS file must meet following criteria:
“The name must begin with a letter and use only alphanumeric characters and underscores. The name cannot end with an underscore or have two consecutive underscores.”

#4. 编写JWTApex.cls获取access_token:

public class JWTApex{
    /*
     * JWTApex.getAccessToken('https://login.salesforce.com/services/oauth2/token', 
      						  'test@sample.com', 
      						  '3MVG9pRzaMkjMb6nq5_7vWB7bzj1AvzgpqUcBl0jDx6HcCZKgL5Ck.8WO2aexmvmyF2QrpGG7YHVYw2mEQqqF', 
     						  'jwt_apex');
	* Setup->Security->Remote site settings. endpoint = https://login.salesforce.com/services/oauth2/token
	*/
    public static String getAccessToken(String login_url, String username, String client_id, String certname) {      
        Auth.JWT jwt = new Auth.JWT();
        jwt.setSub(username); 
        jwt.setAud(login_url); 
        jwt.setIss(client_id);
        
        //Additional claims to set scope
        Map<String, Object> claims = new Map<String, Object>();
        claims.put('scope', 'scope name');
        
        jwt.setAdditionalClaims(claims);
        
        //Create the object that signs the JWT bearer token
        Auth.JWS jws = new Auth.JWS(jwt, certname);
        
        //Get the resulting JWS in case debugging is required
        String token = jws.getCompactSerialization();
        
        //POST the JWT bearer token
        Auth.JWTBearerTokenExchange bearer = new Auth.JWTBearerTokenExchange(jwt.getAud(), jws);
        
        //Return access token
        return bearer.getAccessToken();
    }
}

此处需做2点说明:

  1. 通过System.debug无法获取明文access_token;
  2. 需要将tokenEndpoint添加到Remote site settings;

#5. 验证JWT获取的access_token是否可以自call SP的API:

public class JWTVerify {
    /*
     * JWTVerify.getInfoByServiceAPI('/appMenu/AppSwitcher/');
	*/
    public static void getInfoByServiceAPI(String service) {
        String token = JWTApex.getAccessToken('https://login.salesforce.com/services/oauth2/token', 
      						  				  'test@sample.com', 
      						  				  '3MVG9pRzaMkjMb6nq5_7vWB7bzj1AvzgpqUcBl0jDx6HcCZKgL5Ck.8WO2aexmvmyF2QrpGG7YHVYw2mEQqqF', 
     						  				  'jwt_apex');
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        // without Auth: [{"message":"Session expired or invalid","errorCode":"INVALID_SESSION_ID"}]
        request.setHeader('Authorization', 'Bearer ' + token);
        request.setEndpoint('https://pkg-instance1-dev-ed.my.salesforce.com/services/data/v55.0' + service);
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        
        System.debug(response.getBody());
    }
}

心得体会

  1. 如果我们使用两个Salesforce Org来验证Salesforce JWT授权方式会更加make sense;
  2. 关键步骤#4中使用的是官方的sample code,这种方式🉑️明文打印access_token,🉑️通过临时access_token在postman中做API调试;
  3. 这里强哥提供了另一种思路sample code
  4. 如果是Salesforce Org之间想通过JWT授权,不妨使用Named Credentials方式;

小工具

JWT.IO allows you to decode, verify and generate JWT
https://jwt.io/

参考文档

  1. OAuth 2.0 JWT Bearer Flow for Server-to-Server Integration
  2. JWTBearerTokenExchange Class
  3. Oauth Authorization flows in Salesforce
  4. How to convert crt and key to jks file
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值