做Salesforce集成的朋友应该都遇到过这个场景:外部系统要读Salesforce的数据,或者你想写个脚本把订单批量推上去,然后网上查了一圈资料,得到的答案几乎都是同一句——"去新建一个Connected App,用OAuth调API就行了"。我刚开始听到这话的时候觉得这事儿挺简单的,直到真正上手才发现,光是配置页面里那七八个按钮和一堆OAuth选项,就足以让人绕半天。
这篇东西就围绕Connected App的设置展开,把我从创建到跑通、再到上线的完整过程捋一遍。这里面既包括每个配置项到底是干嘛的、不同OAuth Flow怎么选,也会把我在实测过程中踩过的坑、排查过的报错一并放出来,给正准备做Salesforce API集成的同学一份可以直接照着操作的参考。
1. Connected App在Salesforce生态里的定位:外部访问的第一道关卡
先说清楚Connected App到底解决什么问题。Salesforce本身是一个多租户的云平台,它对"谁能调用API""通过什么方式调用"做了非常严格的管控。早期那种直接在代码里写用户名密码调API的做法,现在基本行不通了,平台层面要求所有外部应用必须有一个明确的身份标识,并且要走OAuth协议来换取访问令牌。
Connected App本质上是Salesforce体系里的"应用登记卡"。你可以把它理解成小区门口的访客系统:你先把某个外部应用的身份(也就是Connected App)登记到平台里,平台给它发一对钥匙(Consumer Key和Consumer Secret),然后这个应用每次来访问的时候,都要用这对钥匙证明"我是谁",再配合用户授权,确定"我能进哪栋楼、能开哪扇门"。
这个机制带来的直接好处有三个:
- 身份可追溯:每个API调用都能关联到具体的Connected App和具体用户,出了问题能在Login History、Auth Session里查到来源。
- 权限可收敛:Connected App可以限定OAuth Scopes,也就是控制这个应用拿到的Token能调哪些接口、访问哪些数据范围,不用把全部权限暴露给外部系统。
- 审计可落地:平台对Connected App的创建、授权、Token发放都有审计事件,合规要求严格的企业(金融、医疗、零售)很依赖这部分记录。
从我接触过的实际项目看,Connected App最常用的场景集中在以下几类:
- 系统间数据集成:比如ERP、电商平台、数据中台通过REST API或Bulk API把数据同步到Salesforce。
- 移动端或PC端工具:公司内部开发的工具需要员工登录后读取其权限范围内的数据。
- 第三方BI报表工具:Tableau、Power BI或者自研的数据看板需要从Salesforce取数。
- 自动化脚本和运维任务:定期清洗数据、批量修改记录、同步主数据等场景。
在这些场景里,Connected App承担的角色都不太一样,有的是"服务器到服务器"的系统级身份,有的是"代替某个用户"的用户级身份。搞清楚这个区别,后面选OAuth Flow才不会选错。
2. 创建Connected App:设置页面里每个选项的用途和推荐取值
2.1 入口和基础信息
进入Setup,在顶部搜索框输入"App Manager",打开应用管理界面,然后点击右上角的"New Connected App"按钮。
在这里需要填三样基础信息:
- Connected App Name:显示名称,比如"ERP_Integration_Prod",建议按"用途+环境"的规则命名,方便后期维护。
- API Name:系统会自动生成,可以手动改,它是这个Connected App在Metadata层面的唯一标识。
- Contact Email:必填,用于接收平台的关于这个应用的通知(比如密钥即将过期、异常登录提醒等)。
这里有个小提醒:API Name一旦保存就不能改,所以在前期就要定好命名规范。我见过不少项目因为一开始没规划好,后面在Metadata管理、权限集引用时全是手工维护的旧名称,很痛苦。
2.2 启用OAuth Settings和Consumer Key/Secret的生成逻辑
接下来最关键的一步,勾选"Enable OAuth Settings"。这一步不做,后面的回调地址、Scopes这些配置项全都不会显示。勾选之后,你会看到Callback URL、Selected OAuth Scopes、Require Secret for Web Server Flow等一系列选项。
保存之后,平台会生成Consumer Key(这个类似公钥,到处可见)和Consumer Secret(这个类似私钥,相当于密码)。注意,Consumer Secret只在创建完成后的页面里完整显示一次,之后如果忘了,只能通过点击"Manage Consumer Details"重新获取,而且每次重取都可能使旧的Secret失效,所以在保存那一刻就要把它放到安全的密码管理器里。
2.3 Callback URL:大小写敏感,匹配粒度比你想的更严格
Callback URL,有的地方也叫Redirect URL,是OAuth授权完成后平台把授权码(Authorization Code)跳到哪个地址的配置。这是整个设置里最容易被忽略、也最容易踩坑的字段。
它的匹配规则是完全匹配(exact match),包括协议、域名、端口和路径,甚至大小写都敏感。比如你填了https://app.example.com/callback,授权完平台跳转到https://app.example.com/Callback都会失败。
我踩过一次很典型的坑:本地开发环境用http://localhost:3000/callback调试没问题,部署到生产环境时把回调改成了https://api.example.com/sf/callback,但由于当时的API网关对内网转发时去掉了末尾的斜杠,导致回调URL对不上,OAuth流程一直报redirect_uri不匹配。排查到最后才发现,不是代码问题,就是回调地址在网关上被改写了一个字符。
所以建议是:一个环境对应一个Callback URL,如果有多个回调地址(比如本地、测试、生产),要么用多个Connected App,要么在平台侧尽量把地址统一。Salesforce目前允许你在Token端点通过redirect_uri参数指定多个已注册地址之一,但前提是每个地址都提前注册且完全匹配。
2.4 OAuth Scopes:宁可少给,不要贪多
Scopes决定了这个Connected App换取到的Access Token能调用哪些接口、操作哪些数据。常见的选项有:
| Scope | 含义 |
|---|---|
| Manage user data via APIs (API) | 允许通过API读写当前用户可访问的数据 |
| Perform requests at any time (RefreshToken) | 允许签发Refresh Token,刷新过期Access Token |
| Access unique user identifiers (OpenID) | 返回OpenID标识信息 |
| Access Salesforce CMS API | 访问CMS内容 |
| Full access (full) | 几乎等同于当前用户的全部权限 |
我个人的建议是,绝大多数场景只勾选第一项"Manage user data via APIs"加"Perform requests at any time"就足够了。凡是勾了"Full access"的项目,后期基本都会面临安全合规的追问——为什么应用需要全部权限?是不是存在过度授权?
还有一个容易踩的点:Scopes的生效范围跟"当前授权用户有权限访问的数据"是取交集的。也就是说,即使Connected App勾了Full access,实际API返回的数据范围仍然不会超出登录用户(或集成用户)本身的Profile、Permission Set、Sharing规则所允许的范围。这一点在权限设计时必须想清楚,否则会出现"明明Connected App看着权限很大,但查不到数据"的情况。
2.5 两个容易被问到的开关:Require Secret和Refresh Token策略
在OAuth Settings里还有几个开关,初看容易不知道什么意思,实际影响不小:
- Require Secret for Web Server Flow:Web Server Flow(Authorization Code Flow)在换取Token的时候需要带Client Secret。建议开启,这是OAuth 2.0安全基线里的标准做法。
- Require Secret for Refresh Token Flow:Refresh Token刷新的时候是否要求带Client Secret。同样建议开启。
- Enable Client Credentials Flow:这个选项是Salesforce后来加入的,适合纯系统到系统、不涉及具体用户授权的场景。勾选后还需要设置"Run As"用户,表示API调用以哪个后台用户身份执行。
2.6 生命周期状态:尝鲜和生产的配置差异
Connected App保存后在界面上会有一个状态标识,默认是"In Development",也就是开发模式。在开发模式下,OAuth流程只能由创建者(或System Administrator)授权使用,其他用户登录授权会被拒绝。
要把它变成所有人可用,需要进入Connected App详情页,点击右上角"Edit",找到"Manage OAuth Policies"区域,把"Permitted Users"改成"Admin-approved users are pre-authorized"或"All users may self-authorize"。前者适合企业内部系统,把User ID或Profile加入允许列表后,用户不用逐个弹窗确认;后者适合面向员工自助集成的场景。
另外再提一个操作细节:如果你想把同一个Connected App配置复制到生产环境,除了在界面上手动填一遍,更靠谱的方式是用Metadata API拉取ConnectedApp对象,或者借助VS Code的Salesforce扩展包、DevOps Center之类的工具做元数据部署。手动配置很容易漏掉某个开关不一致,上线当天才发现生产环境的回调地址不对,那时候就真的手忙脚乱了。
3. 选对授权流程:服务器到服务器、Web应用、无头设备各该用哪种Flow
3.1 几种OAuth Flow的适用场景对比
Connected App设置完成后,紧接着要面对的就是选择OAuth授权流程。Salesforce支持的常见模式有五种,各自有明确的使用边界:
| OAuth Flow | 适用场景 | 关键特征 | 安全注意事项 |
|---|---|---|---|
| Web Server Flow(Authorization Code) | 有后端服务的Web应用 | 需要Client Secret换取Token,支持Refresh Token | 回调地址必须完全匹配,Secret绝不能暴露在前端 |
| User-Agent Flow | 纯前端/单页应用 | 隐式授权,不经过后端换取Token | Token暴露在浏览器历史,不建议新项目使用 |
| Username-Password Flow | 内部工具、测试脚本 | 用户名密码直接换Token | 无法撤销单个用户的授权,侵入性较强 |
| JWT Bearer Flow | 服务器到服务器集成 | 用证书签名换取Token,无需用户交互 | 需要配置证书并妥善保管私钥 |
| Device Flow | 无头设备、CLI工具 | 用户在其他设备上输入授权码完成授权 | 适合交互受限的设备场景 |
3.2 为什么我强烈建议系统集成优先考虑JWT Bearer Flow
如果你做的是ERP、数据中台、BI取数这类"服务器主动发起"的集成,我的首选是JWT Bearer Flow。理由很简单:它不需要用户名密码在外部系统里存着,也不用专门维护一个用户去授权,安全性比Username-Password高级很多,而且完全无用户交互,适合后台定时任务。
JWT Bearer Flow的核心思路是:你生成一个密钥对(RSA或者X.509证书),把公钥上传到Connected App里,然后用私钥签一个JWT发给Salesforce的Token端点,换取Access Token。JWT里要包含三项关键信息:
iss:Connected App的Client ID。sub:集成用户(比如一个专门的Integration User)的Username。aud:Salesforce实例的Token端点,通常是https://login.salesforce.com/services/oauth2/token(生产)或https://test.salesforce.com/services/oauth2/token(沙盒)。
JWT的过期时间(exp)一般设置几分钟内有效,不要设置得太长,建议5分钟以内,换到Access Token之后再由Access Token去调API,这样即使JWT泄露,影响窗口也非常有限。
3.3 JWT Bearer的实操链路
在Connected App端,需要做两件事:
- 在Connected App的OAuth Settings里勾选"Enable JWT Bearer Flow"。
- 上传证书公钥,也就是在OAuth Settings下方找到"Certificate"区域,上传你生成的证书或公钥文件。
然后服务端代码的流程大概是:加载私钥,构造JWT(包含iss、sub、aud、exp),用RS256算法签名,然后POST到Token端点,收到access_token和instance_url。后续所有API调用都以instance_url为Base URL,Header里带Authorization: Bearer <access_token>。
这里有个细节经常被忽略:JWT里定义的用户的Profile必须勾选"API Enabled"权限,否则Token虽然能拿到,但调用任何API都会返回INVALID_SESSION_ID。我排查过不下三次这个问题,每次都不是JWT写错,而是那个集成用户根本就没开API权限。
3.4 Username-Password和Authorization Code的取舍
有一些场景还是会用Authorization Code Flow,比如你做了一个内部Web工具,员工登录后查自己的数据。这种模式下用户在浏览器里跳转到Salesforce登录页,授权后平台会重定向回你的回调地址并带上授权码,再由后端用授权码加Client Secret换Token。
这个Flow要注意的是,Callback URL必须是你自己控制的地址,而且每次授权都要经过用户点击确认(除非你把Permitted Users设成了Admin预授权)。对于团队员工的内部工具,建议把权限策略设置为"Admin-approved users are pre-authorized",减少弹窗打扰。
至于Username-Password Flow,能用尽量别用。它最大的问题是一旦账号密码在外部系统里存了,任何拿到了这个外部系统权限的人就等于拿到了Salesforce的登录凭据。Salesforce本身对这个Flow的管控也越来越严,在某些Org配置下(比如启用MFA的组织)这个是直接走不通的。
4. 跑通验证:用Postman和脚本实测整个OAuth链路
4.1 用Postman快速验证Authorization Code Flow
配置完Connected App之后,第一件事就是先验证能不能正常换取Token。我一般的做法是用Postman快速跑一遍Authorization Code Flow。
Postman里的OAuth 2.0配置界面可以直接设置Authorization URL、Access Token URL、Client ID和Client Secret,然后在浏览器里完成授权。以生产环境为例:
- Authorization URL:
https://login.salesforce.com/services/oauth2/authorize - Access Token URL:
https://login.salesforce.com/services/oauth2/token - Callback URL:和Connected App配置的一致,比如
https://app.example.com/callback
在Postman里填好上面几项后,点"Get New Access Token",会跳出一个Salesforce登录窗口,登录授权完成后拿到Access Token和Refresh Token。这个验证的意义在于确认:
- Callback URL配置是否正确;
- Scopes是否满足所需接口;
- 用户是否有权访问目标对象的数据。
如果这一步顺利,说明Connected App本身没问题,后面代码报错基本可以定位到代码或权限层面。
4.2 用脚本验证JWT Bearer Flow
跑通Authorization Code之后,再验证JWT Bearer Flow。这里给一个Python示例的伪代码思路:
import jwt import requests import time client_id = "3MVG9..." # Consumer Key private_key = open("server.key", "rb").read() username = "integration@example.com" claims = { "iss": client_id, "sub": username, "aud": "https://login.salesforce.com/services/oauth2/token", "exp": int(time.time()) + 300 } token = jwt.encode(claims, private_key, algorithm="RS256") payload = { "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": token } r = requests.post("https://login.salesforce.com/services/oauth2/token", data=payload) auth_data = r.json() access_token = auth_data["access_token"] instance_url = auth_data["instance_url"]这段代码跑完之后,用instance_url加/services/data/v59.0/sobjects/Account/去调用一次API试试:
curl -X GET \ "https://your_instance.my.salesforce.com/services/data/v59.0/sobjects/Account/" \ -H "Authorization: Bearer <access_token>"返回200就是通了。如果返回401,看一下是不是sub对应的用户名拼写不对(大小写严格),或者是不是给的沙盒地址却用了生产环境的Token端点。
4.3 拿到Token之后的三类高频报错
跑通不等于万事大吉,实际调用API时会遇到几种情况:
INVALID_SESSION_ID:Token失效或格式不对。可能是过期时间、Bearer拼写、或者用户的Session超时策略太短。INSUFFICIENT_ACCESS_OR_READONLY:Connected App的Scope没问题,但当前授权用户对目标对象没有字段级或记录级权限。UNSUPPORTED_GRANT_TYPE:多半是grant_type值写错,或者在Username-Password Flow下填了不支持的参数。
我处理INSUFFICIENT_ACCESS_OR_READONLY的次数最多。之前有个项目,脚本用的集成用户是System Administrator,按理说权限够大了,但实际调用某个自定义对象还是报了权限错误。查到最后发现,那个对象的"Called by Apex Without Sharing"设置没开,而代码里的Apex类又用了WITHOUT SHARING关键字以外的模式调用,导致权限被Sharing规则拦了。这种问题跟Connected App本身没关系,但排查的时候得记住:Connected App只管"你是谁",管不了"你能看到什么"。
4.4 多环境配置保持一致的建议
还有一个非常实际的问题:同一套代码,在沙盒上跑得好好的,切到生产环境就报redirect_uri not registered。十有八九是生产环境的Connected App配置和沙盒环境不一致。
我的做法是,把Connected App的配置模板用一个Markdown文档管理起来,内容包括Callback URL、Scopes、Permitted Users、JWT证书、Token策略等。每次搭建新环境就照着模板核对一遍。有条件的话,用Salesforce的Metadata API直接把ConnectedApp的XML部署到目标环境,最大程度减少手工配置差异。
5. 上线前的安全加固与排查清单:这些细节决定集成能活多久
5.1 密钥管理没有"过度谨慎"这回事
Consumer Secret和JWT私钥是外部访问Salesforce的核心凭证。我见过有团队把Secret直接写在Git仓库的配置文件里,也见过把私钥和代码一起打包。这类做法一旦仓库权限泄露,等于把Salesforce的数据拱手送人。
正确的做法是:
- Secret和私钥放到专门的密钥管理系统里(比如Vault、KMS、云厂商的Secrets Manager),代码里通过环境变量引用,不硬编码。
- 定期轮换密钥,尤其是人员变动、项目交接、外部审计之后。
- Consumer Secret只在创建时显示一次,拿到后立刻归档。
5.2 IP限制和证书固定:给Connected App加上边界
Connected App支持两种安全限制:
- IP Relaxation / IP限制:限定哪些来源IP可以调用Token端点或API。如果你的集成服务器出口IP是固定的,强烈建议勾选并填上具体的IP段。
- 同意(Consent)策略:设置用户授权的有效期,可以要求用户定期重新授权。
如果用的是JWT Bearer Flow,控制面还在证书上:私钥在谁手上,谁就能代这个用户发起调用。所以证书的生命周期管理要做起来,临期前提前生成新证书,把旧证书在Connected App里删掉。
5.3 Refresh Token的生命周期比你想象的短
Salesforce的Refresh Token默认180天内有效,而且如果用户在Org里修改了密码、被激活了额外的安全性策略,已签发的Refresh Token可能直接失效。这意味着你的代码一定要处理好Token刷新失败的逻辑:
- 捕获
invalid_grant异常; - 主动清理失效的Refresh Token;
- 必要时提示用户重新走一次授权。
在实际生产中,我经常会遇到因为Refresh Token过期导致定时任务静默失败的情况,日志里全是invalid_grant,但又不影响其他功能,很容易被忽略。建议把Token刷新失败做成一个告警事件,纳入监控体系。
5.4 用审计视角复查整个链路
上线前,最好从审计视角走一遍这些问题:
| 核查项 | 检查点 |
|---|---|
| 最小权限原则 | Connected App的Scope是否收敛到业务所需的最小集合 |
| 集成用户权限 | 集成用户的Profile/Permission Set是否最小化,是否开了API权限 |
| Token生命周期 | Refresh Token有效期、会话Idle超时时间是否符合内控要求 |
| 审计事件 | 是否能看到Connected App的登录记录和OAuth授权历史 |
| 多环境一致 | 沙盒/生产环境的Connected App配置是否一致,差异是否有记录 |
5.5 一个被很多人忽略的细节:Connected App的Owner和后续维护
创建Connected App时的Owner(通常是创建者),在后期会收到平台的安全通知和密钥相关提醒。如果创建了这个App的人已经离职,或者Owner账号被冻结,那这个Connected App的维护就会变得很尴尬。
我建议在项目初期就把Connected App的Owner指定为一个服务账号(Service Account)或团队公共账号,而不是某个具体同事的个人账号。这样即使人员变动,钥匙和权限还在团队控制内,不会出现"前任离职了,Connected App没人能改"的窘境。
最后再分享一个实操里的小经验:在调试Connected App的时候,不要一上来就把所有权限拉到最大。最稳妥的方式是,先用一个专门的集成用户、只勾API Scope、Callback URL先用本地地址跑通,然后逐步收紧配置,一步一步验证。我见过太多项目是"配的时候全开,跑通了再一点点收缩权限",结果收缩的时候发现业务逻辑早就绕过权限依赖了,改起来比一开始做权限规划还费劲。Connected App这个"门禁卡"设计得越干净,后面集成的运维越轻松。