应用场景
- Salesforce作为Service Provider (SP),外部系统需要访问Salesforce数据;
- 在授权过程中无需通过UI Login页面输入账密;
- 不希望外部系统储存账密等可直接用于UI Login的信息;
前置条件
- 使用证书对JWT请求进行签名;
- 需要事先获得SP的批准;
案例设计
目标
通过JWT授权方式获取access_token,然后使用access_token call Salesforce API访问数据。
前提假设
- 使用openSSL形式本地获取
server.crt (public key)和server.key (private key),然后上传到Connected App与Certificate and Key Management提供安全保障; - 使用
JWTBearerTokenExchange class来获取access_token; - 为方便Demo,假设使用同一个Org进行自己调自己API;
关键步骤
#1. 通过openSSL获取下面4个文件: #2 将server.crt上传到Connected App: #3. 将本地的server.crt 和server.key 通过terminal转化成JKS File,然后修改keystore的Alias name ,最后在Certificate and Key Management通过Import from Keystore将JKS File保存到Certificates相关列表。 可通过下面命令转JKS File:
openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12
keytool -importkeystore -srckeystore server.p12
-srcstoretype PKCS12
-destkeystore jwt_apex.jks
-deststoretype JKS
keytool -keystore jwt-apex.jks -changealias -alias 1 -destalias jwt_apex
keytool -list -v -keystore jwt_apex.jks
为什么要修改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{
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);
Map<String, Object> claims = new Map<String, Object>();
claims.put('scope', 'scope name');
jwt.setAdditionalClaims(claims);
Auth.JWS jws = new Auth.JWS(jwt, certname);
String token = jws.getCompactSerialization();
String tokenEndpoint = 'https://login.salesforce.com/services/oauth2/token';
Auth.JWTBearerTokenExchange bearer = new Auth.JWTBearerTokenExchange(tokenEndpoint, jws);
String accessToken = bearer.getAccessToken();
return accessToken;
}
}
此处需做2点说明:
- 通过System.debug无法获取明文access_token;
- 需要将tokenEndpoint添加到Remote site settings;
#5. 验证JWT获取的access_token是否可以自call SP的API:
public class JWTVerify {
public static void getInfoByServiceAPI(String service) {
String token = JWTApex.getAccessToken('https://login.salesforce.com',
'test@sample.com',
'3MVG9pRzaMkjMb6nq5_7vWB7bzj1AvzgpqUcBl0jDx6HcCZKgL5Ck.8WO2aexmvmyF2QrpGG7YHVYw2mEQqqF',
'jwt_apex');
Http http = new Http();
HttpRequest request = new HttpRequest();
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());
}
}
心得体会
- 如果我们使用两个Salesforce Org来验证Salesforce JWT授权方式会更加make sense;
- 关键步骤#4中使用的是官方的sample code,这种方式无法明文打印access_token,想通过临时access_token在postman中做API调试行不通;
- 这里强哥提供了另一种思路sample code,但也无法明文打印access_token;
- 如果是Salesforce Org之间想通过JWT授权,不妨使用Named Credentials方式;
小工具
JWT.IO allows you to decode, verify and generate JWT https://jwt.io/
参考文档
- OAuth 2.0 JWT Bearer Flow for Server-to-Server Integration
- JWTBearerTokenExchange Class
- Oauth Authorization flows in Salesforce
- How to convert crt and key to jks file
|