适用于服务器到服务器集成的 OAuth 2.0 JWT 不记名流
有时,您希望授权服务器访问数据,而不必每次服务器交换信息时都交互登录。对于一些情况,您可以使用 OAuth 2.0 JSON Web 令牌 (JWT) 不记名流。此流会使用证书签名 JWT 请求,且无需显式用户集成。但是,此流确实需要客户端应用程序的事先批准。
所需的 Edition
| 适用于 Salesforce Classic 和 Lightning Experience |
| 适用于:所有版本 |
通过 OAuth 2.0 JWT 不记名令牌流,客户端会将 JWT 发布到 Salesforce OAuth 令牌端点。Salesforce 处理包含数字签名的 JWT,并根据应用程序的事先批准发布访问令牌。
此示例显示了流中采取的步骤。
- 报表服务开始其夜间批处理报表。
- 连接的应用程序向 Salesforce 标记端点发送 JWT。JWT 启用在安全域之间共享的身份和安全信息。
- Salesforce 使用先前配置的证书和其他参数,基于签名验证 JWT。
- 假设 JWT 有效,并且连接的应用程序已事先获得批准,Salesforce 会发出访问令牌。通过以下其中一种方式进行事先批准:
- 如果连接的应用程序策略设置为管理员批准的用户已预授权,您可以使用简档和权限集。
- 如果连接的应用程序策略设置为所有用户可以自我授权,您可以使用最终用户批准并发布刷新令牌。但是,客户端不需要有当前或存储的刷新令牌。客户端也不需要将客户端密钥发送到令牌端点。
备注 对于这两个选项,Salesforce 仅在原始访问令牌包含至少一个标准范围(而非refresh_token范围)时发布新访问令牌。 - 连接的应用程序使用访问令牌访问 Salesforce 服务器上受保护的数据。
- 报表服务将授权数据拉入其夜间报表中。
让我们回顾一下该授权流的每一步。
创建 JWT
Salesforce 要求使用 RSA SHA256 对 JWT 进行签名,RSA sha 256 使用上传的证书作为签名密码。在使用此授权流之前,请确保您已完成这些步骤。
- 将 X509 证书上传到 Java 密钥存储 (JKS)。证书大小不能超过 4 KB。如果超过,请尝试使用 DER 编码的文件来减小大小。
- 为连接的应用程序注册 X509 证书。该证书对应于其应用的私钥。保存连接的应用程序时,将生成
client_id和client_secret并分配给应用程序。 - 构建可生成 JWT 的应用程序,该应用程序使用 X509 证书的私钥进行签名。关联的已连接应用程序使用证书来验证签名。JWT 必须符合 https://tools.ietf.org/html/rfc7519 指定的一般格式规则。
备注 Salesforce 在您的 JWT 不记名令牌中不要求 JWT ID (JTI) 声明。但是,如果您通过了 JWT 不记名令牌中的 JTI 声明,Salesforce 会验证之前没有发送过 JTI 声明。这种验证可以防止 JWT 重放攻击。
要创建有效的 JWT,请执行以下步骤。
- 以此格式构建 JWT 标题:
{"alg":"RS256"}. - 按照 http://tools.ietf.org/html/rfc4648#page-7 中的定义,Base64url 解码 JWT 标题。结果类似于
eyJhbGciOiJSUzI1NiJ9。 - 使用这些参数为 JWT 构建 JSON 请求集。
以下是 JWT 的 JSON 请求集示例。参数 描述 iss颁发者必须包含您为其注册证书的连接的应用程序的 OAuth client_id。aud受众会将授权服务器识别为目标受众。授权服务器必须验证它是令牌的目标受众。
使用授权服务器的 URL 作为受众值:https://login.salesforce.com、https://test.salesforce.com 或 https://site.force.com/customers(如果为 Experience Cloud 站点实施)。
sub如果您正在为 Experience Cloud 站点实施该流,主题必须包含用户的用户名。
为了向后兼容,您可以使用主体 (
prn) 而不是主题 (sub)。如果两者都指定,则使用prn。exp令牌过期的日期和时间,以从协调世界时 1970-01-01T0:0:0Z 开始测量的秒数表示。Salesforce 允许 3 分钟的时钟偏差缓冲时间。例如,如果过期时间设置为 1,735,743,600 秒或世界协调时 2025 年 1 月 1 日 15:00:00C,则令牌在世界协调时此日期的 15:03:00 之前仍然有效。 {"iss": "3MVG99OxTyEMCQ3gNp2PjkqeZKxnmAiG1xV4oHh9AKL_rSK.BoSVPGZHQ ukXnVjzRgSuQqGn75NL7yfkQcyy7", "sub": "my@email.com", "aud": "https://login.salesforce.com", "exp": "1333685628"} - Base64url 编码 JWT“请求设置”,无任何换行符。例如:
eyJpc3MiOiAiM01WRzk5T3hUeUVNQ1EzZ05wMlBqa3FlWkt4bm1BaUcxeFY0b0hoOUFLTF9yU0su Qm9TVlBHWkhRdWtYblZqelJnU3VRcUduNzVOTDd5ZmtRY3l5NyIsICJwcm4iOiAibXlAZW1haWwu Y29tIiwgImF1ZCI6ICJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwgImV4cCI6ICIxMzMz Njg1NjI4In0= - 在此格式,创建字符串用于编码 JWT 标题和编码 JWT “请求设置”:
在此示例中,编码 JWT 标题会突出显示。encoded_JWT_Header + "." + encoded_JWT_Claims_SeteyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiAiM01WRzk5T3hUeUVNQ1EzZ05wMlBqa3FlWkt4bm1BaUcxeFY0b0hoOUFLTF9yU0su Qm9TVlBHWkhRdWtYblZqelJnU3VRcUduNzVOTDd5ZmtRY3l5NyIsICJwcm4iOiAibXlAZW1haWwu Y29tIiwgImF1ZCI6ICJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwgImV4cCI6ICIxMzMz Njg1NjI4In0= - 从 JKS 下载 X509 证书。
- 使用 RSA SHA256 对结果字符串进行签名。
- 以此格式创建此步骤中的字符串。
在此示例中,base64 编码签名会突出显示。existing_string + "." + base64_encoded_signatureeyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiAiM01WRzk5T3hUeUVNQ1EzZ05wMlBqa3FlWkt4bm1BaUcxeFY0b0hoOUFLTF9yU0su Qm9TVlBHWkhRdWtYblZqelJnU3VRcUduNzVOTDd5ZmtRY3l5NyIsICJwcm4iOiAibXlAZW1haWwu Y29tIiwgImF1ZCI6ICJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwgImV4cCI6ICIxMzMz Njg1NjI4In0=.iYCthqWCQucwi35yFs-nWNgpF5NA_a46fXDTNIY8ACko6BaEtQ9E6h4Hn1l_pcwcK I_GlmfUO2dJDg1A610t09TeoPagJsZDm_H83bsoZUoI8LpAA1s-2aj_Wbysqb1j4uDToz 480WtEbkwIv09sIeS_-QuWak2RXOl1Krnf72mpVGS4WWSULodgNzlKHHyjAMAHiBHIDNt 36y2L2Bh7M8TNWiKa_BNM6s1FNKDAwHEWQrNtAeReXgRy0MZgQY2rZtqT2FcDyjY3JVQb En_CSjH2WV7ZlUwsKHqGfI7hzeEvVdfOjH9NuaJozxvhPF489IgW6cntPuT2V647JWi7ng
对于构建 JWT 不记名令牌,此 Java 代码是一个简单示例。
import org.apache.commons.codec.binary.Base64;
import java.io.*;
import java.security.*;
import java.text.MessageFormat;
public class JWTExample {
public static void main(String[] args) {
String header = "{\"alg\":\"RS256\"}";
String claimTemplate = "'{'\"iss\": \"{0}\", \"sub\": \"{1}\", \"aud\": \"{2}\", \"exp\": \"{3}\", \"jti\": \"{4}\"'}'";
try {
StringBuffer token = new StringBuffer();
//Encode the JWT Header and add it to our string to sign
token.append(Base64.encodeBase64URLSafeString(header.getBytes("UTF-8")));
//Separate with a period
token.append(".");
//Create the JWT Claims Object
String[] claimArray = new String[5];
claimArray[0] = "3MVG99OxTyEMCQ3gNp2PjkqeZKxnmAiG1xV4oHh9AKL_rSK.BoSVPGZHQukXnVjzRgSuQqGn75NL7yfkQcyy7";
claimArray[1] = "my@email.com";
claimArray[2] = "https://login.salesforce.com";
claimArray[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
claimArray[4]=<JTI>
MessageFormat claims;
claims = new MessageFormat(claimTemplate);
String payload = claims.format(claimArray);
//Add the encoded claims object
token.append(Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8")));
//Load the private key from a keystore
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(new FileInputStream("./path/to/keystore.jks"), "keystorepassword".toCharArray());
PrivateKey privateKey = (PrivateKey) keystore.getKey("certalias", "privatekeypassword".toCharArray());
//Sign the JWT Header + "." + JWT Claims Object
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(token.toString().getBytes("UTF-8"));
String signedPayload = Base64.encodeBase64URLSafeString(signature.sign());
//Separate with a period
token.append(".");
//Add the encoded signature
token.append(signedPayload);
System.out.println(token.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}请求访问令牌
要请求访问令牌,连接的应用程序会向 Salesforce 实例的标记端点发布标记请求。它在帖子中包括 JWT。
此示例显示示例标记请求。
POST /services/oauth2/token HTTP/1.1
Host: login.example.com
Content-Type: application/x-www-form-urlencoded
grant_type= urn:ietf:params:oauth:grant-type:jwt-bearer&
assertion=eyJpc3MiOiAiM01WRz...[omitted for brevity]...ZT在帖子中包括这些参数。
| 参数 | 描述 |
|---|---|
grant_type
|
将这些值用于资助类型:urn:ietf:params:oauth:grant-type:jwt-bearer. |
assertion
|
声明是整个 JWT 值。 |
format
|
(可选)用于指定预期返回格式。此参数会覆盖请求的标题。支持这些格式。
|
范围参数
您无法在 JWT 不记名令牌流中指定范围。范围根据连接的应用程序的允许用户策略或贵组织的 API 访问控制设置而颁发,如本表所示。
| 设置 | 结果 |
|---|---|
| 允许的用户策略:所有用户可以自我授权 | 通过成功授权,使用访问令牌返回的范围将从先前批准的范围派生。 |
| 允许的用户策略:管理员批准的用户是预先授权的 | 分配给连接的应用程序的标准和自定义范围将随访问令牌一起返回。 |
| API 访问权限控制将组织中连接的应用程序加入允许列表 | 分配给连接的应用程序的标准和自定义范围将随访问令牌一起返回。如果您将组织中连接的应用程序加入允许列表,但未收到预期范围,请采取以下步骤。
|
Salesforce 授予访问令牌
OAuth 2.0 JWT 不记名和 SAML 声明不记名流请求查看包含刷新标记的用户的所有先前批准。如果 Salesforce 查找匹配批准,将合并批准范围的值。然后,Salesforce 发行访问令牌。如果 Salesforce 未找到包含刷新令牌或任何可用批准范围的先前批准,请求会因为未授权而失败。
验证成功后,Salesforce 实例会向连接的应用程序发送响应。虽然从未发行刷新令牌,但 OAuth 2.0 JWT 不记名令牌流的令牌响应使用与授权码流相同的格式。
此示例显示来自 Salesforce 的响应。
{"access_token":"00Dxx0000001gPL!AR8AQJXg5oj8jXSgxJfA0lBog.
39AsX.LVpxezPwuX5VAIrrbbHMuol7GQxnMeYMN7cj8EoWr78nt1u44zU31
IbYNNJguseu",
"scope":"web openid api id","instance_url":"
https://yourInstance.salesforce.com","id":"
https://yourInstance.salesforce.com
/id/00Dxx0000001gPLEAY/005xx000001SwiUAAS","token_type":"Bearer"}这些参数在响应的正文中。
| 参数 | 描述 |
|---|---|
access_token |
连接的应用程序用来代表客户端应用程序请求访问受保护资源的 OAuth 标记。访问标记可以附带范围形式的附加权限。 |
token_type |
Bearer 令牌类型,用于包含访问令牌的所有响应。 |
scope |
范围根据连接的应用程序的允许用户策略或贵组织的 API 访问控制设置而颁发。请查看范围参数。 |
instance_url |
指示用户的组织实例的 URL。例如:https://yourInstance.salesforce.com/. |
id |
可用于标识用户以及查询有关用户的更多信息的身份 URL。请参阅身份 URL。 |
sfdc_site_url |
如果用户是 Experience Cloud 站点的成员,则提供站点 URL。 |
sfdc_site_id |
如果用户是 Experience Cloud 站点的成员,则提供用户的站点 ID。对于 Experience Cloud 站点,此流在令牌端点中包含"sfdc_site_id"值。在连接 REST API 请求中可能需要该站点 ID。 |

