Apex 安全与共享模型完全指南
Apex安全与共享模型完全指南:涵盖共享规则强制执行(with sharing/without sharing/inherited sharing/内部类继承规则/Pricebook2例外)、对象与字段权限(SOQL WITH USER_MODE/SYSTEM_MODE、DML as user/as system、Database AccessLevel参数/权限集提升)、stripInaccessible方法(剥离不可访问字段/子查询/DML前清理/反序列化后清理/ID永不被剥离)、Describe安全方法(isAccessible/isCreateable/isUpdateable/isDeletable最细粒度控制)、类安全(基于Profile/PermissionSet/入口点控制/间接调用)、Apex托管共享(托管/用户手动/Apex托管三种类型/Share对象/RowCause/访问级别/Apex共享原因)、XSS/CSRF防护以及SOQL注入防护(escapeSingleQuotes/绑定变量/自定义设置)。
本课程共有 8 个章节
咱们今天聊聊 Apex 的安全模型,这可是 Salesforce 开发里非常核心的一块。你可以把它想象成给代码上了三道锁,确保数据不会在不该被看见的时候泄露出去。 这三道锁就是记录级安全、字段级安全和对象级安全。记录级安全管控的是“谁能看到哪一条数据”,比如你只能看到自己的客户,看不到别人的。字段级安全管控的是“能看到哪个字段”,比如虽然你有机会看到这条客户记录,但可能看不到客户的手机号。对象级安全呢,管的是“你连这个对象本身能不能访问”,比如你压根就没有“合同”这个对象的访问权限,那一条合同都看不见。 从 API 版本 67.0 开始,Apex 的运行方式有了一个很关键的变化:它默认就在用户模式下执行了。也就是说,你的代码现在会自动遵守当前登录用户的权限,包括记录级和字段级安全,不用你再特意去声明。以前我们经常要纠结类的运行是“有共享”还是“无共享”,现在如果类上没有显式写 with sharing 或者 without sharing,它就跟 with sharing 一样运行。这让我们少操很多心。 还有一个注意点,以前的 WITH_SECURITY_ENFORCED 这个关键字已经弃用了,现在改成了 WITH_USER_MODE,你后面写 SOQL 查询的时候会用到它,显式告诉系统要按用户模式执行。 这一章我们会把这三层安全都掰开揉碎来讲,还会说到类本身的安全(也就是类访问权限),怎么在代码里用编程共享来灵活控制共享模式,以及一些让你代码更安全的最佳实践。这样以后你写的 Apex,既灵活又不会闯祸。
咱们这节课来聊聊 Apex 里面的“共享”规则,这其实指的就是记录级安全性。很多人一听到“共享”会以为是跟别人共享数据,没错,但在代码里它控制的是:当前用户能看到、能操作哪些记录。 在 Apex 里,默认情况下,如果你没写任何关键字,它不强制施行共享规则,也就是说不理睬用户的权限,能看到所有数据。但通常我们会在类定义时加上关键字来告诉系统:“请尊重用户的权限”。 这里面有三个关键字:`with sharing`、`without sharing`、`inherited sharing`,我们一个一个说。 第一个,`with sharing`,就是“带着共享模式”。写在类定义上,这个类里的所有查询、更新操作都会遵守当前用户的共享规则。比如,销售只能看他自己的机会,那代码里查询机会时,就只会返回他有权限的记录。 第二个,`without sharing`,正好相反,“不带共享模式”。哪怕当前用户权限很低,这个类也能看到所有数据,像管理员一样。用它的时候要很小心,通常只在需要跨所有记录操作时,比如数据清理、生成报表这类后端逻辑。 第三个,`inherited sharing`,这是比较推荐的新写法。它意思是“听调用者的”——如果调用它的类是用 `with sharing` 跑的,那它就跟着用共享模式;如果调用者不用共享,它也不共享。这样更灵活,一个类可以被不同场景复用,而不会出现权限漏洞。 另外还有一点,内部类,也就是写在另一个类里面的类,它的共享模式跟外层调用者一样。如果是通过继承,子类会继承父类的共享设置。 这些共享设置主要影响两件事: - SOQL 和 SOSL 查询:如果用了共享模式,可能返回的行数会变少,因为系统自动过滤掉了用户看不见的记录。 - DML 操作:如果你尝试更新或插入一条记录,但它关联的外部记录(比如查找字段指向的东西)用户没有访问权,操作就会失败。 最后特别提醒一点:`with sharing` 只管记录级安全,不管字段级安全(FLS,Field-Level Security)。也就是说,就算用户对某个对象的某个字段没有读取权限,在 `with sharing` 的类里,代码仍然可以读取甚至修改那个字段的值。字段级安全需要用 `stripInaccessible` 或检查 `Schema.sObjectType` 来单独控制,这个咱们后面会详细讲。 好,今天的知识点就是这些。记住关键区别:共享控制的是 ,哪些行,,FLS控制的是 ,哪些列,。有疑问吗?没有的话我们就准备下一个内容啦。
同学们,我们来看这一页幻灯片,讲的是Apex代码运行时的权限模式。 大家可能知道,平时我们写的Apex代码,默认是在“用户模式”下执行的。也就是说,它会尊重当前登录用户的权限——用户能看见什么字段、能操作哪些对象,代码就只能做什么。但有些时候,我们需要暂时绕过这些限制,比如管理员要做一些后台批量更新,不希望被字段级安全挡住。这时候,我们就可以显式地切换到“系统模式”。 怎么切换呢?分几种情况。 对于查询语句,也就是SOQL和SOSL,你可以在查询末尾加一个子句,用`WITH USER_MODE`表示仍然按用户权限来,或者用`WITH SYSTEM_MODE`表示忽略字段和对象的访问限制。 对于DML操作,如果你用的是`Database`类的方法,比如`Database.insert`、`Database.update`,那么可以在参数里传入`AccessLevel.USER_MODE`或`AccessLevel.SYSTEM_MODE`。这个特性目前还是Developer Preview,未来还可以通过指定一个权限集,在系统模式下也保持一定的安全检查,增强安全性。 这里有一个容易混淆的地方,同学们要注意:用户模式会全面应用共享规则、字段级安全和对象权限,完全以用户身份执行。而系统模式虽然忽略了字段和对象权限,但它对共享规则的态度却很微妙——它到底看不看共享规则,取决于你类的声明里有没有用`with sharing`、`without sharing`这些关键字。换句话说,在系统模式下,记录级别的共享控制仍然受类本身的共享设置影响,不能简单认为系统模式就能看到所有记录。 好了,这一页的核心就是这些。简单总结一下:默认用户模式,需要超越权限时切到系统模式;查询用`WITH`子句,DML用`Database`方法的参数;系统模式不跳过共享规则的约束,共享规则由类的关键字决定。这样讲,大家明白了吗?
同学们好,今天我们来了解一下 Apex 中一个非常实用的安全方法,叫 `stripInaccessible`。你可能经常遇到这样的情况:查询数据或者从外部接收 sObject 记录时,里面有些字段当前用户并没有权限查看或编辑。一但直接拿这些记录去做增删改操作,系统就会抛出权限异常。那该怎么办呢?`stripInaccessible` 就专门来帮我们解决这个难题。 它的作用很简单,就是自动把查询结果,甚至子查询结果里那些当前用户无权访问的字段,全部“剥离”掉,从而严格执行字段级和对象级的数据保护。你可以在执行 DML 操作之前调用它,把 sObject 上不可访问的字段提前移除,这样子不但能避免异常,也能很好地清理来自不受信任来源的记录数据。 这个方法会返回一个跟源记录结构完全相同的列表,唯一的区别就是——其中用户无权访问的字段已经被清除了。相当于它给你了一组“干净”的记录,可以放心进行后续操作。 那么它背后的权限检查是怎么实现的呢?在最细的粒度上,其实用的是像 `DescribeSObjectResult` 和 `DescribeFieldResult` 这些描述类里提供的访问控制方法,比如 `isAccessible`、`isUpdatable`、`isDeletable`。`stripInaccessible` 在内部遍历每一个字段,挨个检查权限,然后把没权限的字段过滤掉。也就是说,它帮你封装好了这些繁琐的细粒度检查,你不需要自己写一堆判断逻辑。 所以,简单总结一下:`stripInaccessible` 是一个让数据操作更安全、更省心的工具。只要在查询或操作前调用它,就能确保只对有权限的字段进行操作,从根本上避免了因为字段安全限制而产生的运行时错误。 好了,关于 `stripInaccessible` 方法,我们就讲到这里。接下来,你可以动手试一试,看看它在实际项目中如何提升代码的安全性。
好,今天我们来讲讲Salesforce里两个很重要的安全概念:类安全性和记录共享。 首先,类安全性,它其实是用来控制“谁能调用顶级Apex类的方法”。比如说,你写了一个Aura控制器,或者一个@AuraEnabled方法,又或者一个Visualforce控制器、一个REST服务、甚至是Deliverc Apex入口——这些入口点的访问,就是靠类安全性来管的。简单讲,它是根据用户的配置文件或权限集,来决定这个人能不能执行这个类里的方法。 那什么叫共享呢?共享是管“记录”的,也就是一条一条的数据。它决定了谁可以对某条记录做哪些操作,比如能不能看、能不能改。Salesforce里有三种主要的共享方式: 第一种叫托管共享,就是系统自动控制的,包括记录的拥有者、角色层次结构,还有共享规则,都不需要用户自己手动去设。 第二种叫用户托管共享,也就是手动共享,用户可以自己把一条记录分享给其他人或者组。 第三种叫Apex托管共享,就是通过写Apex代码,用程序来控制共享。 在Salesforce里,共享记录本身也是一个对象,叫做共享对象。每种标准或自定义对象都有对应的共享对象,它里面有几个关键属性:访问级别——有私人、只读、读写和全部;父记录ID,就是你要分享的那条记录的ID;还有共享原因,我们叫它rowCause,用来标记是怎么分享出去的;另外就是被授予权限的用户或组的ID。 总的来说,类安全性管的是“能不能执行代码”,共享管的是“能不能看到和操作记录”,两者加在一起,就构成了Salesforce里立体的权限控制。
各位同学,大家好,今天我们来聊聊 Apex 托管共享这个很有用的功能。 简单来说,Apex 托管共享就是让你能直接用代码来控制 Salesforce 里记录的共享权限,不用每次都去 UI 上点点点。也就是说,咱开发人员可以通过写 Apex,想办法“嘿,这条记录该让谁看见”,然后用程序自动去实现。 要用这个功能,有几个关键点你得记住。 第一,每次你用代码共享记录时,都必须带上一个“共享原因”——你可以把它想象成一个标签,说明“这条记录为什么被分享出去?”。比如是因为业务流程需要,还是临时审批。这个共享原因要去自定义对象的详细信息页面里手动创建,创建好以后代码里才能引用。 第二,实际共享操作的对象是什么呢?是每个自定义对象自动生成的一个特殊的“共享对象”,名字一般就是你的对象名后面加个“__Share”,比如 CustomBody__Share。你直接往这个对象里插入一条记录,就能指定“把哪个用户/组”赋予“只读”或“读写”权限了。 这里有个小细节要注意:如果你给同一个用户、同一个共享原因插入一条新的共享记录,而这人之前已经有了一条,那系统会自动比较访问级别。如果新的级别更高,就会把旧的替换掉;如果更低,这条插入就会被忽略。总之,最终这个人拿到的永远是他能得到的最⾼权限,不会降级。 第三,Apex 托管共享还有一个“重新计算”的概念。比如你的数据变了,共享条件也跟着变,那你就得重新算一遍所有记录的共享。怎么做呢?必须写一个实现了 Database.Batchable 接口的批处理类,然后把这个类和你的自定义对象关联起来,这样系统就能在需要时自动跑这个批处理去刷新共享。 最后还有一个很重要的前提:你这个自定义对象的“组织范围默认值”绝对不能设成最宽松的级别,比如全员都可读可写。如果那样还搞什么 Apex 托管共享呢?必须是private或者至少是只读,才能通过代码去控制额外的人看到什么。 好了,今天这一页Slide的内容大概就是这些,理解了这几个点,你再去看后续具体的实现,就会轻松很多。我们下节课接着聊具体的代码怎么写。
同学们好,今天我们花几分钟聊聊Salesforce平台里的两个重要安全话题:跨站脚本攻击,也就是XSS,还有跨站请求伪造,简称CSRF。这部分内容虽然看起来有点技术,但我会尽量用大白话把它讲清楚。 首先说XSS。你想想,如果有人在网页里偷偷塞了一段恶意的HTML或者JavaScript代码,当其他用户浏览这个页面时,这段代码就会在用户的浏览器里执行,可能导致账号被盗、信息泄露。这就是跨站脚本攻击。Salesforce为了保护我们,在所有标准的Visualforce组件里都内置了反XSS过滤器。也就是说,如果你用官方的标签,像`<apex:outputText>`、`<apex:inputField>`,它们会自动把危险的字符转义掉,让恶意脚本没法执行。 但是,如果你自己写了一段自定义的JavaScript,或者用了`<apex:includeScript>`来引入外部的脚本文件,那就要小心了,因为这部分没有内置的保护。你必须在输出数据的时候,手动用Salesforce提供的编码函数去处理。这些函数有四个:`HTMLENCODE`用来转义HTML,`JSENCODE`用来在JavaScript字符串里安全地输出,`JSINHTMLENCODE`是在HTML内部嵌入JavaScript的时候使用,还有`URLENCODE`用于URL参数。记住,只要是你自己控制输出,就得给数据穿上防护衣,用对应的函数编码一下。 那再来看CSRF攻击。它跟XSS不一样,不是往页面里注入脚本,而是利用你已经登录的合法会话,在你不知情的情况下,诱导你的浏览器向后端发一个请求,执行一些你不想干的操作,比如改密码、转账。为了防这个,Salesforce在每个页面里都自动附带了一个随机的反CSRF令牌,而且标准控制器会自动验证这个令牌,确保请求是从它自己的页面发出来的。 可如果你的自定义控制器直接去读取请求的参数,比如通过`ApexPages.currentPage().getParameters().get('someParam')`拿到值就去执行敏感操作,那就绕过了反CSRF保护。因为标准验证只在标准控制器上起作用,你自己写的逻辑里要是没有额外加校验,就可能中招。所以,如果是自定义控制器,一定要多加注意,要么确保操作本身是无害的只读操作,要么自己实现令牌校验机制。 好,总结一下:Salesforce平台在标准组件和控制里帮我们做了很多安全防护,但一碰到自定义代码,尤其是自己写JavaScript、自己读参数的时候,就得自己上心,用对编码函数,留意CSRF风险。今天的要点就这些,有没有什么问题?没问题的话我们继续下一个主题。
同学们,今天我们来聊聊两个很重要的安全话题:SOQL注入和自定义设置。 先说说SOQL注入。我们写代码的时候,千万别直接把用户输入拼接到查询语句里,这种就叫动态SOQL查询,很容易被攻击。安全的做法是,用静态查询,也就是在查询里放一个冒号加变量名,这叫绑定变量。这样平台就能自动处理输入,防止注入。 但有时候,你真的没办法,必须使用动态SOQL,比如查询的字段名是动态的。这时候,你一定要用`escapeSingleQuotes`这个方法,对用户输入做转义处理,把单引号转义掉,能降低风险。不过要记住,这只是一个降级方案,能不用动态SOQL就尽量不用。 接下来看自定义设置。它很像自定义对象,但它最大的好处是数据会缓存起来,访问速度非常快,而且不需要写SOQL查询就能拿到数据。有两种类型:列表自定义设置,它可以存一些对全组织都一样的静态数据,像配置常量。还有层次结构自定义设置,它更聪明,可以按组织、简档甚至用户级别来设定不同的值,自带优先级逻辑。 但这里有个安全陷阱你要特别注意。自定义设置本身不是天然安全的。只有在托管包里,被设成“受保护”的自定义设置,它的数据才不会被外部随意访问。普通的公共自定义设置,权限是非常开放的,即使是未登录的来宾用户,也能读到里面的数据。所以,绝对不要在公共自定义设置里存任何敏感信息,像密码、密钥那些,千万不能放。 好了,这两个重点你记住了吗?尽量用绑定变量代替动态SOQL,自定义设置方便但要谨慎存放机密数据。有疑问的话,我们下节课继续。