背景

此前我们的经验中,apex代码一直运行在system mode中,但是当summer'26 release(即apex api version: 67)以后,apex将运行在 user mode,所以以后创建apex class的时候,也需要严格的检查当前系统设置默认生成的api version。否则程序中会出现到意想不到的结果。

此前我们的博客解释过 System Mode以及User Mode,这里一句话进行总结:

User Mode(用户模式): 按当前运行用户的权限执行,会检查对象权限(CRUD)、字段安全(FLS)以及共享规则(sharing);

System Mode(系统模式):不检查当前用户的对象权限以及FLS,相当于用户拥有所有对象和字段的访问权。记录级共享是否生效,要看apex class声明的sharing关键字。

那么 api version 66及以前和以后都有哪些明显的区别呢,我们按照以下的demo可以更好的了解:

测试举例, 由AI生成

1. 创建一个自定义表,包含两个字段,其中一个有FLS,一个没有FLS

image

2.  AccessModeDemoController,主要实现三个功能:

  • 展示当前用户信息权限
  • 通过前端按钮进行不同的路由,执行 system mode和 user mode
  • 对查询所有的场景进行测试
/**
 * Apex 用户模式 / 系统模式对比演示(LWC 控制器)。
 *
 * 外层类声明为 with sharing;需要绕过记录共享的场景放在内部 without sharing 类 Elevated 中,
 * 以便同时演示:访问模式(CRUD/FLS)与 sharing 关键字(记录级)是两套独立的机制。
 */
public with sharing class AccessModeDemoController {

    private static final String NOT_RETURNED = '— 未返回 —';
    private static final String DEMO_FIELDS = 'Id, Name, Public_Note__c, Secret_Note__c, Owner.Name';
    private static final List DEMO_PATHS = new List{ 'Name', 'Public_Note__c', 'Secret_Note__c', 'Owner.Name' };
    private static final List DEMO_PUBLIC_PATHS = new List{ 'Name', 'Public_Note__c', 'Owner.Name' };

    // ---------------------------------------------------------------- DTO

    public class ScenarioResult {
        @AuraEnabled public String key;
        @AuraEnabled public String category;
        @AuraEnabled public String title;
        @AuraEnabled public String mode;
        @AuraEnabled public String sharing;
        @AuraEnabled public String codeSnippet;
        @AuraEnabled public String explanation;
        @AuraEnabled public Boolean success = false;
        @AuraEnabled public Integer rowCount;
        @AuraEnabled public List> columns = new List>();
        @AuraEnabled public List> rows = new List>();
        @AuraEnabled public String errorType;
        @AuraEnabled public String errorMessage;
        @AuraEnabled public List problemFields = new List();
    }

    public class UserContext {
        @AuraEnabled public String userId;
        @AuraEnabled public String userName;
        @AuraEnabled public String loginName;
        @AuraEnabled public String profileName;
        @AuraEnabled public Boolean modifyAllData;
        @AuraEnabled public Boolean canCreateDemo;
        @AuraEnabled public Boolean canReadSecret;
        @AuraEnabled public Integer totalDemoRecords;
        @AuraEnabled public Integer ownDemoRecords;
    }

    // ---------------------------------------------------------------- 内部 without sharing 类

    private without sharing class Elevated {
        List querySystemMode() {
            return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
                    FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate];
        }
        Integer countAll() {
            return Database.countQuery('SELECT COUNT() FROM Access_Mode_Demo__c', AccessLevel.SYSTEM_MODE);
        }
        void insertAsSystem(Access_Mode_Demo__c rec) {
            insert as system rec;
        }
    }

    // ---------------------------------------------------------------- 当前用户上下文

    @AuraEnabled
    public static UserContext getUserContext() {
        Id uid = UserInfo.getUserId();
        UserContext ctx = new UserContext();
        ctx.userId = uid;
        ctx.userName = UserInfo.getName();
        ctx.loginName = UserInfo.getUserName();
        User me = [SELECT Profile.Name FROM User WHERE Id = :uid WITH SYSTEM_MODE];
        ctx.profileName = me.Profile?.Name;
        ctx.modifyAllData = [SELECT COUNT() FROM PermissionSetAssignment
                             WHERE AssigneeId = :uid AND PermissionSet.PermissionsModifyAllData = true
                             WITH SYSTEM_MODE] > 0;
        ctx.canCreateDemo = Schema.SObjectType.Access_Mode_Demo__c.isCreateable();
        ctx.canReadSecret = Schema.SObjectType.Access_Mode_Demo__c.fields.Secret_Note__c.isAccessible();
        ctx.totalDemoRecords = new Elevated().countAll();
        ctx.ownDemoRecords = [SELECT COUNT() FROM Access_Mode_Demo__c WHERE OwnerId = :uid WITH SYSTEM_MODE];
        return ctx;
    }

    // ---------------------------------------------------------------- 查询对比场景

    @AuraEnabled
    public static List runAllScenarios() {
        List results = new List();

        // A. 未声明访问模式 · 类 API 66.0
        ScenarioResult a = newResult('A', '默认模式', '未声明访问模式(类 API 66.0)', '默认(系统)', 'without sharing',
            '// AccessModeDefaultV66.cls  (apiVersion 66.0)\n[SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c]',
            'API 66.0 及以前默认系统模式:不检查当前用户的对象权限和 FLS;without sharing 也不按共享过滤。');
        try {
            fill(a, AccessModeDefaultV66.queryWithoutModeClause(), DEMO_PATHS);
        } catch (Exception e) { fail(a, e); }
        results.add(a);

        // B. 未声明访问模式 · 类 API 68.0
        ScenarioResult b = newResult('B', '默认模式', '未声明访问模式(类 API 68.0)', '默认(用户)', 'without sharing',
            '// AccessModeDefaultV68.cls  (apiVersion 68.0)\n[SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c]',
            'API 67.0 及以后默认用户模式:同样的代码,仅因类的 API 版本不同,就会检查 CRUD、FLS 和共享。');
        try {
            fill(b, AccessModeDefaultV68.queryWithoutModeClause(), DEMO_PATHS);
        } catch (Exception e) { fail(b, e); }
        results.add(b);

        // C. WITH USER_MODE(包含受限字段)
        ScenarioResult c = newResult('C', '显式声明', 'WITH USER_MODE(包含 Secret_Note__c)', '用户模式', 'with sharing',
            '[SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c WITH USER_MODE]',
            '用户模式检查 FLS:用户没有 Secret_Note__c 读取权限时整条查询抛 QueryException,可用 getInaccessibleFields() 拿到全部无权限字段。');
        try {
            fill(c, [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
                     FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate], DEMO_PATHS);
        } catch (Exception e) { fail(c, e); }
        results.add(c);

        // D. WITH USER_MODE(仅有权限字段)
        ScenarioResult d = newResult('D', '显式声明', 'WITH USER_MODE(仅查询有权限字段)', '用户模式', 'with sharing',
            '[SELECT Id, Name, Public_Note__c, Owner.Name FROM Access_Mode_Demo__c WITH USER_MODE]',
            '用户模式始终遵守共享规则:OWD 为 Private 时,普通用户只能看到自己拥有或被共享的记录。');
        try {
            fill(d, [SELECT Id, Name, Public_Note__c, Owner.Name
                     FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate], DEMO_PUBLIC_PATHS);
        } catch (Exception e) { fail(d, e); }
        results.add(d);

        // E. WITH SYSTEM_MODE + without sharing
        ScenarioResult e1 = newResult('E', '显式声明', 'WITH SYSTEM_MODE + without sharing', '系统模式', 'without sharing',
            'private without sharing class Elevated {\n  [SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c WITH SYSTEM_MODE]\n}',
            '系统模式不检查 CRUD/FLS;类为 without sharing,因此也不按共享过滤 —— 能看到全部记录和 Secret_Note__c。');
        try {
            fill(e1, new Elevated().querySystemMode(), DEMO_PATHS);
        } catch (Exception e) { fail(e1, e); }
        results.add(e1);

        // F. WITH SYSTEM_MODE + with sharing
        ScenarioResult f = newResult('F', '显式声明', 'WITH SYSTEM_MODE + with sharing', '系统模式', 'with sharing',
            'public with sharing class AccessModeDemoController {\n  [SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c WITH SYSTEM_MODE]\n}',
            '系统模式下记录可见性由类的 sharing 关键字决定:with sharing 仍按共享过滤行,但 FLS 不检查,Secret_Note__c 照样返回。');
        try {
            fill(f, [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
                     FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate], DEMO_PATHS);
        } catch (Exception e) { fail(f, e); }
        results.add(f);

        // G. Security.stripInaccessible
        ScenarioResult g = newResult('G', '优雅降级', 'Security.stripInaccessible(READABLE)', '系统模式查询 + 剥离', 'with sharing',
            'List recs = [SELECT ... FROM Access_Mode_Demo__c WITH SYSTEM_MODE];\n'
                + 'SObjectAccessDecision dec = Security.stripInaccessible(AccessType.READABLE, recs);\n'
                + 'dec.getRecords(); dec.getRemovedFields();',
            '不抛异常,而是把当前用户无权读取的字段从结果里移除;getRemovedFields() 返回被移除的字段。');
        try {
            List raw = [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
                                             FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate];
            SObjectAccessDecision dec = Security.stripInaccessible(AccessType.READABLE, raw);
            fill(g, dec.getRecords(), DEMO_PATHS);
            for (String obj : dec.getRemovedFields().keySet()) {
                for (String fld : dec.getRemovedFields().get(obj)) {
                    g.problemFields.add(obj + '.' + fld);
                }
            }
        } catch (Exception e) { fail(g, e); }
        results.add(g);

        return results;
    }

    // ---------------------------------------------------------------- DML 对比场景

    @AuraEnabled
    public static ScenarioResult createRecord(String name, String publicNote, String secretNote, String mode) {
        Boolean asUser = (mode == 'USER');
        ScenarioResult r = newResult(asUser ? 'DML_USER' : 'DML_SYSTEM', 'DML',
            asUser ? 'insert as user' : 'insert as system',
            asUser ? '用户模式' : '系统模式', asUser ? 'with sharing' : 'without sharing',
            asUser ? 'insert as user rec;' : 'insert as system rec;  // 在 without sharing 内部类中执行',
            asUser
                ? '用户模式 DML 检查对象“新建”权限与字段编辑 FLS。本 demo 实测:缺少对象新建权限时抛 System.TypeException(DML operation INSERT not allowed);官方文档说明 FLS 错误可通过 DmlException.getDmlFieldNames() 获取。'
                : '系统模式 DML 不检查当前用户的 CRUD/FLS,即使用户没有“新建”权限也能写入。');
        Access_Mode_Demo__c rec = new Access_Mode_Demo__c(
            Name = String.isBlank(name) ? 'Demo ' + System.now().format('HH:mm:ss') : name,
            Public_Note__c = publicNote,
            Secret_Note__c = secretNote);
        try {
            if (asUser) {
                insert as user rec;
            } else {
                new Elevated().insertAsSystem(rec);
            }
            r.success = true;
            r.rowCount = 1;
            Access_Mode_Demo__c saved = [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
                                         FROM Access_Mode_Demo__c WHERE Id = :rec.Id WITH SYSTEM_MODE];
            fill(r, new List{ saved }, DEMO_PATHS);
        } catch (Exception e) {
            fail(r, e);
        }
        return r;
    }

    // ---------------------------------------------------------------- helpers

    private static ScenarioResult newResult(String key, String grp, String title, String mode, String sharing,
                                            String code, String explanation) {
        ScenarioResult r = new ScenarioResult();
        r.key = key;
        r.category = grp;
        r.title = title;
        r.mode = mode;
        r.sharing = sharing;
        r.codeSnippet = code;
        r.explanation = explanation;
        return r;
    }

    private static void fill(ScenarioResult r, List recs, List paths) {
        r.success = true;
        r.rowCount = recs.size();
        for (String p : paths) {
            r.columns.add(new Map{ 'label' => p, 'fieldName' => p.replace('.', '_') });
        }
        for (SObject rec : recs) {
            Map row = new Map{ 'Id' => rec.Id };
            for (String p : paths) {
                row.put(p.replace('.', '_'), readPath(rec, p));
            }
            r.rows.add(row);
        }
    }

    private static void fail(ScenarioResult r, Exception e) {
        r.success = false;
        r.errorType = e.getTypeName();
        r.errorMessage = e.getMessage();
        if (e instanceof QueryException) {
            Map> inaccessible = ((QueryException) e).getInaccessibleFields();
            if (inaccessible != null) {
                for (String obj : inaccessible.keySet()) {
                    for (String fld : inaccessible.get(obj)) {
                        r.problemFields.add(obj + '.' + fld);
                    }
                }
            }
        } else if (e instanceof DmlException) {
            DmlException de = (DmlException) e;
            for (Integer i = 0; i < de.getNumDml(); i++) {
                List names = de.getDmlFieldNames(i);
                if (names != null) {
                    r.problemFields.addAll(names);
                }
            }
        }
    }

    private static Object readPath(SObject rec, String path) {
        Map populated = rec.getPopulatedFieldsAsMap();
        if (!path.contains('.')) {
            if (!populated.containsKey(path)) {
                return NOT_RETURNED;
            }
            Object v = populated.get(path);
            return v == null ? '(空值)' : String.valueOf(v);
        }
        String rel = path.substringBefore('.');
        if (!populated.containsKey(rel) || populated.get(rel) == null) {
            return NOT_RETURNED;
        }
        return readPath(rec.getSObject(rel), path.substringAfter('.'));
    }
}

3. AccessModeDefaultV66: 设置api version为 66

public without sharing class AccessModeDefaultV66 {
    public static List queryWithoutModeClause() {
        return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
                FROM Access_Mode_Demo__c ORDER BY CreatedDate];
    }
}

4. AccessModeDefaultV68: 代码和V66相同,唯一区别是设置api version 为68

public without sharing class AccessModeDefaultV68 {
    public static List queryWithoutModeClause() {
        return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
                FROM Access_Mode_Demo__c ORDER BY CreatedDate];
    }
}

5. AccessModeDemo.html

<template>
    <div class="slds-p-around_small">
        
        <lightning-card title="Apex 用户模式 vs 系统模式 演示" icon-name="standard:user_role">
            <div class="slds-p-horizontal_medium">
                <template lwc:if={ctx}>
                    <div class="slds-grid slds-wrap slds-gutters_x-small">
                        <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                            <p class="label">当前运行用户p>
                            <p class="value">{ctx.userName}p>
                            <p class="sub">{ctx.loginName}p>
                        div>
                        <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                            <p class="label">Profilep>
                            <p class="value">{ctx.profileName}p>
                        div>
                        <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                            <p class="label">演示记录(总数 / 本人拥有)p>
                            <p class="value">{ctx.totalDemoRecords} / {ctx.ownDemoRecords}p>
                        div>
                    div>
                    <div class="slds-m-top_small badges">
                        <span class={modifyAllClass}>Modify All Data:{modifyAllText}span>
                        <span class={canCreateClass}>Access_Mode_Demo__c 新建:{canCreateText}span>
                        <span class={canReadSecretClass}>Secret_Note__c 读取 FLS:{canReadSecretText}span>
                    div>
                template>
                <template lwc:if={error}>
                    <div class="slds-text-color_error slds-m-top_small">{error}div>
                template>
            div>
        lightning-card>

        
        <lightning-card title="① DML 对比:insert as user / insert as system" icon-name="standard:record_create">
            <div class="slds-p-horizontal_medium">
                <div class="slds-grid slds-wrap slds-gutters_x-small">
                    <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                        <lightning-input label="Name" value={newName} data-field="newName" onchange={handleInput}>lightning-input>
                    div>
                    <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                        <lightning-input label="Public Note" value={newPublic} data-field="newPublic" onchange={handleInput}>lightning-input>
                    div>
                    <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                        <lightning-input label="Secret Note(受限字段)" value={newSecret} data-field="newSecret" onchange={handleInput}>lightning-input>
                    div>
                div>
                <div class="slds-m-top_small">
                    <lightning-button label="用户模式新建(insert as user)" data-mode="USER" onclick={handleCreate} disabled={busy}>lightning-button>
                    <lightning-button class="slds-m-left_x-small" variant="brand" label="系统模式新建(insert as system)" data-mode="SYSTEM" onclick={handleCreate} disabled={busy}>lightning-button>
                div>
                <template lwc:if={dmlResult}>
                    <div class="slds-m-top_small">
                        <c-access-mode-demo-scenario scenario={dmlResult}>c-access-mode-demo-scenario>
                    div>
                template>
            div>
        lightning-card>

        
        <lightning-card title="② 查询对比场景" icon-name="standard:sossession">
            <lightning-button slot="actions" variant="brand" label="运行全部对比场景" onclick={handleRunAll} disabled={busy}>lightning-button>
            <div class="slds-p-horizontal_medium">
                <template lwc:if={busy}>
                    <lightning-spinner alternative-text="运行中" size="small">lightning-spinner>
                template>
                <template lwc:if={hasResults}>
                    <p class="slds-text-body_small slds-m-bottom_x-small">运行时间:{lastRunAt}p>
                    <table class="slds-table slds-table_cell-buffer slds-table_bordered summary">
                        <thead>
                            <tr class="slds-line-height_reset">
                                <th>#th>
                                <th>场景th>
                                <th>访问模式th>
                                <th>sharingth>
                                <th>结果th>
                                <th>问题字段 / 被剥离字段th>
                            tr>
                        thead>
                        <tbody>
                            <template for:each={summaryRows} for:item="s">
                                <tr key={s.key}>
                                    <td>{s.key}td>
                                    <td>{s.title}td>
                                    <td>{s.mode}td>
                                    <td>{s.sharing}td>
                                    <td><span class={s.statusClass}>{s.statusText}span>td>
                                    <td class="mono">{s.problemText}td>
                                tr>
                            template>
                        tbody>
                    table>
                    <template for:each={results} for:item="r">
                        <div key={r.key} class="slds-m-top_medium">
                            <c-access-mode-demo-scenario scenario={r}>c-access-mode-demo-scenario>
                        div>
                    template>
                template>
                <template lwc:else>
                    <p class="slds-text-color_weak">点击右上角“运行全部对比场景”。p>
                template>
            div>
        lightning-card>
    div>
template>
AccessModeDemo.js
import { LightningElement } from 'lwc';
import getUserContext from '@salesforce/apex/AccessModeDemoController.getUserContext';
import runAllScenarios from '@salesforce/apex/AccessModeDemoController.runAllScenarios';
import createRecord from '@salesforce/apex/AccessModeDemoController.createRecord';

const yesNo = (v) => (v ? '有' : '无');
const badge = (v) => `slds-badge ${v ? 'slds-theme_success' : 'slds-theme_error'}`;

export default class AccessModeDemo extends LightningElement {
    ctx;
    error;
    busy = false;
    results = [];
    dmlResult;
    lastRunAt;
    newName = '';
    newPublic = '公开备注';
    newSecret = '机密备注';

    connectedCallback() {
        this.loadContext();
    }

    async loadContext() {
        try {
            this.ctx = await getUserContext();
        } catch (e) {
            this.error = this.reduceError(e);
        }
    }

    get modifyAllText() { return yesNo(this.ctx?.modifyAllData); }
    get modifyAllClass() { return badge(this.ctx?.modifyAllData); }
    get canCreateText() { return yesNo(this.ctx?.canCreateDemo); }
    get canCreateClass() { return badge(this.ctx?.canCreateDemo); }
    get canReadSecretText() { return yesNo(this.ctx?.canReadSecret); }
    get canReadSecretClass() { return badge(this.ctx?.canReadSecret); }

    get hasResults() {
        return this.results && this.results.length > 0;
    }

    get summaryRows() {
        return this.results.map((r) => ({
            key: r.key,
            title: r.title,
            mode: r.mode,
            sharing: r.sharing,
            statusText: r.success ? `成功 · ${r.rowCount} 行` : `失败 · ${r.errorType}`,
            statusClass: badge(r.success),
            problemText: (r.problemFields || []).join(', ')
        }));
    }

    handleInput(event) {
        this[event.target.dataset.field] = event.target.value;
    }

    async handleCreate(event) {
        const mode = event.target.dataset.mode;
        this.busy = true;
        this.error = undefined;
        try {
            this.dmlResult = await createRecord({
                name: this.newName,
                publicNote: this.newPublic,
                secretNote: this.newSecret,
                mode
            });
            await this.loadContext();
        } catch (e) {
            this.error = this.reduceError(e);
        } finally {
            this.busy = false;
        }
    }

    async handleRunAll() {
        this.busy = true;
        this.error = undefined;
        try {
            this.results = await runAllScenarios();
            this.lastRunAt = new Date().toLocaleString();
        } catch (e) {
            this.error = this.reduceError(e);
        } finally {
            this.busy = false;
        }
    }

    reduceError(e) {
        return e?.body?.message || e?.message || JSON.stringify(e);
    }
}

6. 创建普通用户:没有 Access_Mode_Demo的创建权限,没有Secret_Note__c的读取权限。

测试结果展示:

一. DML 场景

1. 点击用户模式新建,因为走 User Mode,所以报错:DML operation INSERT not allowed on Access_Mode_Demo__c。

后台的DML主要执行insert as user rec;//rec为这条Access_Mode_Demo的记录

image

2. 添加permission set,允许 Access_Mode_Demo__c新建权限,点击用户模式新建,因为走 User Mode,所以报错:System.DmlException:Operation failed due to fields being inaccessible on Sobject Access_Mode_Demo__c, check errors on Exception or Result!

image

 3. 点击系统模式新建,因为走 System Mode,所以创建成功。

后台的DML主要执行insert as system rec;//rec为这条Access_Mode_Demo的记录

image

 二. 查询场景

查询场景主要包含了以下的组合。通过下图的排列组合可以看到,当在api version是 68.0时,由于走 User Mode, 当SOQL中对某个字段没有查询权限时,会报错。System.QueryException,

场景 访问模式 sharing SOQL 结果 问题字段
未声明访问模式(类 API 66.0) 系统模式 Without Sharing
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c ORDER BY CreatedDate
成功  
未声明访问模式(类 API 68.0) 用户模式 Without Sharing
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c ORDER BY CreatedDate
失败 · System.QueryException Access_Mode_Demo__c.Secret_Note__c
WITH USER_MODE(包含 Secret_Note__c;类 API 68.0) 用户模式 With Sharing
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate
失败 · System.QueryException Access_Mode_Demo__c.Secret_Note__c
WITH USER_MODE(仅查询有权限字段;类 API 68.0) 用户模式 With Sharing
SELECT Id, Name, Public_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate
成功  
WITH SYSTEM_MODE + without sharing(类 API 68.0) 系统模式 Without Sharing
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate
成功  
WITH SYSTEM_MODE + with sharing(类 API 68.0) 系统模式 With Sharing
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate
成功  
Security.stripInaccessible(类 API 68.0) 系统模式查询 + 无权限字段移除 With Sharing
List raw = [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate];
SObjectAccessDecision dec = Security.stripInaccessible(AccessType.READABLE, raw);
成功 结果集中,返回的结果将不会包含Secret_Note__c

总结:

AI时代,Salesforce对于安全性看得越来越重要,所以最近的release增强了很多的安全的更新。 如果你的项目中的代码对于权限的安全性要求很高,你可以考虑user mode或者使用 system mode 搭配stripInaccessible方法实现基于用户级别的权限访问和操作,把数据操作控制在最小范围。后续创建apex前,也思考一下你的项目架构中适用于system mode还是 user mode运行,不同的场景使用不同的api version(66及以前 / 66以后)。

参考文档:

https://developer.salesforce.com/docs/platform/apex-dev/references/apex-dev/apex-classes-enforce-usermode.html

https://developer.salesforce.com/docs/platform/apex-dev/references/apex-dev/apex-classes-with-security-stripinaccessible.html

 

原文地址: https://www.cveoy.top/t/topic/qHUu 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录