Salesforce Experience Cloud 安全架构:构建可扩展的共享模型深度解析

背景与应用场景

我是一名 Salesforce 架构师。在我的职业生涯中,设计和实施 Experience Cloud(前身为 Community Cloud)解决方案是最具挑战性也最有价值的任务之一。Experience Cloud 的核心价值在于将 Salesforce 的强大功能从内部员工延伸至外部用户,如客户、合作伙伴或分销商。然而,这种延伸也带来了最严峻的挑战:如何在开放协作与数据安全之间取得完美的平衡

一个精心设计的合作伙伴门户网站,需要让合作伙伴能够访问他们自己的商机(Opportunity)和潜在客户(Lead),但绝对不能让他们看到竞争对手的数据。一个客户自助服务站点,需要让客户能查看和编辑自己的个案(Case)和订单(Order),但必须将这些数据严格隔离在他们自己的客户(Account)范围内。这些场景都指向一个核心问题:如何为成千上万,甚至数百万的外部用户设计一个既安全、可控,又具备高性能和可扩展性的数据共享模型。

一个拙劣的共享模型设计,轻则导致用户无法访问所需数据,影响业务流程;重则可能引发严重的数据泄露,造成不可估量的商业损失和声誉损害。因此,作为架构师,我们必须从项目伊始就将安全和共享模型置于设计的核心位置,而不是事后弥补。本文将从架构师的视角,深入探讨 Experience Cloud 的安全层级和共享机制,帮助您构建一个坚如磐石且能随业务增长而扩展的解决方案。


原理说明

Salesforce 平台提供了一个多层次的安全模型,Experience Cloud 在此基础上针对外部用户进行了一些特殊的调整和扩展。理解这些层次及其相互作用是设计成功方案的关键。

第一层:用户许可证 (User Licenses) - 一切的基石

在设计共享模型之前,我们必须做的第一件事就是选择正确的用户许可证。许可证类型直接决定了用户能拥有的权限和可用的共享机制,这是一个不可逆的基础性决策。

  • Customer Community: 这是为大量 B2C 场景设计的许可证,用户数量可以非常庞大。拥有此许可证的用户没有角色 (Role),因此无法利用基于角色的共享规则。他们的数据访问权限主要通过 Sharing SetsShare Groups 来实现。
  • Customer Community Plus: 为 B2B 场景设计,相比前者功能更强。此许可证的用户拥有角色,可以访问报表和仪表盘,并能通过角色层级、共享规则等高级共享功能访问非自己拥有的记录。
  • Partner Community: 这是功能最强大的外部用户许可证,专为合作伙伴(如经销商、代理商)设计。用户拥有角色,可以访问和处理潜在客户(Leads)与商机(Opportunities),并且可以使用更复杂的共享机制。

架构师决策点: 必须根据业务需求(B2B vs B2C)、所需访问的对象、以及是否需要基于角色的复杂共享,来精确选择许可证类型。选错许可证,后续的共享设计将处处受限。

第二层:组织范围默认设置 (Organization-Wide Defaults, OWD) - 安全的底线

OWD 是整个 Salesforce 组织数据共享的基准线,它定义了用户对非自己拥有的记录的默认访问级别。对于 Experience Cloud,Salesforce 提供了独立的外部组织范围默认设置 (External Organization-Wide Defaults)

最佳实践: 始终将外部 OWD 设置为最严格的级别,通常是 Private。这意味着外部用户默认情况下只能看到他们自己拥有的记录。我们后续的所有共享设计,都应该是在这个“最小权限”原则的基础上,逐步、精确地开放访问权限,而不是反其道而行之。

第三层:扩展访问权限的机制

在设定了严格的 OWD 之后,我们需要使用不同的工具来为特定用户或用户组开放访问权限。

1. 角色层级 (Role Hierarchy)

对于拥有 Customer Community Plus 和 Partner Community 许可证的用户,我们可以利用角色层级。与内部角色层级不同,外部用户的角色层级是与客户(Account)绑定的。通常,一个合作伙伴或客户客户下会创建 1-3 个角色(如 User, Manager, Executive)。上级角色的用户可以访问下级角色用户拥有的记录。需要注意的是,每个客户下的角色数量有限制(默认为3个),这在设计时需要考虑。

2. 共享规则 (Sharing Rules)

当角色层级无法满足需求时,共享规则是我们的首选工具。它可以基于记录所有者或记录上的字段条件,将访问权限授予公共组 (Public Groups)、角色 (Roles) 或角色与下属 (Roles and Subordinates)。例如,我们可以创建一条规则:“将所有属于 A 合作伙伴客户的个案,共享给‘A 合作伙伴经理’这个角色”。

3. Sharing Sets & Share Groups

这是专门为 Customer Community 这种无角色许可证用户设计的共享机制。

  • Sharing Sets: 它通过一个公共的查找字段(通常是 Account 或 Contact)将用户与记录关联起来。例如,我们可以创建一个 Sharing Set,允许用户的关联客户(Account)下的所有个案(Cases)都对该用户可见。这是实现 B2C 场景下数据隔离的核心工具。
  • Share Groups: 它是 Sharing Set 的补充,允许将由内部用户拥有的记录,批量共享给拥有特定访问级别的外部用户组。

4. Apex 托管共享 (Apex Managed Sharing)

当标准共享机制都无法满足极其复杂的、动态的业务逻辑时,Apex 托管共享是我们的最后手段。它允许开发人员通过编写 Apex 代码,来创建或删除 `Share` 对象(如 `AccountShare`, `CaseShare`)的记录,从而实现对单个记录的精细化编程控制。

例如,一个项目管理场景中,只有被明确指定为“项目成员”的外部顾问才能看到某个项目记录,而项目成员列表是动态变化的。这种逻辑无法用标准规则实现,就必须使用 Apex 托管共享。


示例代码

以下是一个 Apex 托管共享的示例,演示了如何通过 Apex 代码将一个特定的客户(Account)记录共享给指定的用户。此代码严格遵循 Salesforce 官方文档中的实践。

场景: 假设有一个自定义的“项目团队成员(Project_Team_Member__c)”对象,当一个新的外部用户被添加为某个客户(Account)关联的项目团队成员时,我们需要自动将该客户的访问权限授予这位用户。

// 假设此方法在一个 Trigger Handler 中被调用
// Trigger on: Project_Team_Member__c (after insert)

public static void shareAccountWithTeamMember(List<Project_Team_Member__c> newTeamMembers) {

    // 1. 创建一个用于存储共享记录的列表
    List<AccountShare> accountSharesToInsert = new List<AccountShare>();

    // 2. 准备需要共享的 Account ID 集合,以便高效查询
    Set<Id> accountIds = new Set<Id>();
    for (Project_Team_Member__c member : newTeamMembers) {
        accountIds.add(member.Account__c);
    }
    
    // 查询现有共享,避免重复创建,防止 DML 错误
    // 这是一个重要的性能和健壮性优化
    Map<Id, AccountShare> existingShares = new Map<Id, AccountShare>();
    for(AccountShare existing : [
        SELECT Id, AccountId, UserOrGroupId 
        FROM AccountShare 
        WHERE AccountId IN :accountIds 
        AND RowCause = Schema.AccountShare.RowCause.Project_Access__c // 使用自定义的共享原因
    ]){
        // 使用 AccountId 和 UserId 作为复合键来唯一识别一条共享记录
        String uniqueKey = String.valueOf(existing.AccountId) + String.valueOf(existing.UserOrGroupId);
        existingShares.put(uniqueKey, existing);
    }


    // 3. 遍历新创建的团队成员记录
    for (Project_Team_Member__c member : newTeamMembers) {
        // 检查是否已经存在共享记录,如果不存在才创建
        String checkKey = String.valueOf(member.Account__c) + String.valueOf(member.User__c);
        if (member.Account__c != null && member.User__c != null && !existingShares.containsKey(checkKey)) {
            
            // 4. 创建一个新的 AccountShare 对象实例
            AccountShare accShare = new AccountShare();

            // 5. 设置被共享的记录ID
            accShare.AccountId = member.Account__c;

            // 6. 设置被授予权限的用户或组的ID
            accShare.UserOrGroupId = member.User__c;

            // 7. 设置访问级别,可以是 'Read' 或 'Edit'
            accShare.AccountAccessLevel = 'Edit';
            accShare.OpportunityAccessLevel = 'Read'; // 也可以控制相关子对象的访问级别
            accShare.CaseAccessLevel = 'None';
            accShare.ContactAccessLevel = 'Read';


            // 8. 设置共享原因 (RowCause)。这是 Apex 托管共享的关键!
            // 必须在对象的共享原因中预先创建一个自定义的 Apex Sharing Reason。
            // 这里我们假设已经创建了一个名为 "Project Access" 的共享原因,其 API Name 为 Project_Access__c。
            accShare.RowCause = Schema.AccountShare.RowCause.Project_Access__c;

            // 9. 将创建的共享记录添加到列表中
            accountSharesToInsert.add(accShare);
        }
    }

    // 10. 如果列表不为空,则执行 DML 插入操作
    if (!accountSharesToInsert.isEmpty()) {
        try {
            Database.SaveResult[] saveResults = Database.insert(accountSharesToInsert, false);
            // 错误处理逻辑...
            // 遍历 saveResults 检查是否有失败的记录
        } catch (DmlException e) {
            // 记录异常
            System.debug('Error sharing accounts: ' + e.getMessage());
        }
    }
}

注释解释:

  • RowCause: 这是 Apex 托管共享的灵魂。你必须先在 Salesforce 设置中为该对象创建一个自定义的 Apex Sharing Reason。这样做的好处是,Salesforce 知道这条共享规则是由你的代码逻辑维护的。当你删除或修改相关业务逻辑时(例如,团队成员被移除),你可以通过查询这个 `RowCause` 来精确地找到并删除对应的 `Share` 记录,而不会影响其他共享规则。
  • 批量处理: 代码严格遵循了 Salesforce 的批量化最佳实践,使用 List 和 Set 来处理多个记录,避免在循环中执行 SOQL 查询或 DML 操作。
  • 幂等性: 通过提前查询已存在的共享记录,代码避免了因重复触发(例如,数据导入)而尝试插入重复共享记录导致的错误。

注意事项

作为架构师,我们不仅要关注功能实现,更要关注非功能性需求,如性能、限制和治理。

  1. 性能考量:
    • 共享重计算 (Sharing Recalculation): 每当用户的角色、组员身份发生变化,或者共享规则的条件被满足/不满足时,Salesforce 都需要重新计算共享。对于大量数据和复杂规则的组织,这个过程可能会非常耗时,甚至导致锁定。在设计时,应尽量简化共享规则,避免创建过于复杂的层级。
    • 数据倾斜 (Data Skew): 这是 Experience Cloud 中常见的问题,特别是 B2C 场景。当一个客户(Account)拥有超过10,000个联系人(Contacts)或子对象记录时,就会发生所有权倾斜。当这个客户的所有权或共享发生变化时,对其下所有子记录的共享重计算会带来巨大的性能负担。设计时需要识别并规避数据倾斜。
  2. 许可证限制:
    • 再次强调,深刻理解你所选许可证的共享能力。不要尝试为 Customer Community 用户设计基于角色的共享,这是行不通的。
    • 注意 Partner Community 许可证的角色数量限制,如果一个合作伙伴客户需要非常复杂的内部层级,默认的3个角色可能不够用,需要提前规划或申请增加限额。
  3. 混合 DML 错误 (Mixed DML Error):
    • 在同一个事务中,不能同时对设置对象(如 User, Profile)和非设置对象(如 Account, Contact)执行 DML 操作。在 Experience Cloud 用户自助注册或信息更新的场景中,这很容易发生。例如,在一个事务中先创建了 Contact 记录,然后又创建了 User 记录。解决方案是使用异步处理(如 @future, Queueable Apex)将事务分开。
  4. Guest 用户安全性:
    • 对于未登录的访客用户,Salesforce 近年已大幅收紧了其安全策略。访客用户的 OWD 必须是 Private,不能分配更新或删除权限,并且能访问的记录必须通过特定的 Guest User Sharing Rule 来授予,不能查看所有用户。设计公共站点时必须严格遵守这些新规。

总结与最佳实践

设计一个可扩展、安全的 Experience Cloud 共享模型是一项系统工程,它要求架构师具备全局视野和深厚的技术功底。以下是几个关键的最佳实践:

  1. 最小权限原则: 始终从最严格的权限设置开始(外部 OWD 为 Private),然后根据明确的业务需求,逐层、精确地开放权限。
  2. 善用标准功能: 优先使用声明式的共享工具。顺序应该是:OWD -> 角色层级 -> 共享规则/Sharing Sets。只有在这些工具都无法满足需求时,才考虑使用 Apex 托管共享。
  3. 为规模而设计: 在设计阶段就要充分预估未来的用户量和数据量。主动识别潜在的数据倾斜风险,并设计规避方案。
  4. 许可证即架构: 将许可证选择视为架构决策的一部分。许可证决定了你的工具箱里有什么工具,这是设计的起点。
  5. 代码即责任: 如果使用 Apex 托管共享,必须编写健壮、可维护、批量化的代码。务必处理好共享记录的创建、更新和删除的完整生命周期,避免产生孤立的共享数据。
  6. 持续审查: 共享模型不是一成不变的。随着业务的发展,应定期审查和优化共享规则,移除不再需要的权限,确保安全模型与业务需求始终保持一致。

总之,Experience Cloud 的共享模型设计是科学与艺术的结合。它要求我们不仅理解技术细节,更要洞察业务本质,最终构建出一个既能赋能外部用户,又能守护企业核心数据资产的强大数字体验平台。

评论

此博客中的热门博文

Salesforce Experience Cloud 技术深度解析:构建社区站点 (Community Sites)

Salesforce 登录取证:深入解析用户访问监控与安全

Salesforce Data Loader 全方位指南:数据迁移与管理的最佳实践