11 使用用户与角色控制访问#
应用的访问控制不只是在菜单上隐藏几个入口。完整的安全设计需要先确认用户是谁,再判断这个用户能访问哪些页面、数据和操作。APEX 使用认证方案(Authentication Scheme)处理登录;使用角色(Roles)和角色分配(Role Assignments)表达用户在组织中的职责;再通过授权方案(Authorization Scheme)把这些职责转化为可执行的访问规则。
本章示例围绕 Woods HR 应用展开。该应用基于 EBA_DEMO_EMP 以及常见 EMP 表的副本,并补充了一些员工数据。最终目标是让普通员工、HR 代表、应用管理员和经理看到与其职责匹配的页面、列、按钮和数据行。
11.1 要求页面经过身份验证#
页面级的 Authentication 属性决定用户是否必须登录才能查看页面。Login Page 这类入口页通常设置为 Page Is Public;大多数业务页面应设置为 Page Requires Authentication,让 APEX 在进入页面前先完成登录检查。
- 在 Page Designer 中打开目标页面,例如 Woods HR 的登录页 9999。
- 在组件树中选中页面根节点。
- 在 Property Editor 中找到 Authentication 属性。
- 根据页面用途选择 Page Is Public 或 Page Requires Authentication。
验证点:公开页面应可直接访问;需要认证的页面应先跳转到登录流程,登录后再继续访问。
11.2 使用 APEX Accounts 身份验证#
应用的 Authentication Scheme 控制用户如何登录。新建应用默认使用 Oracle APEX Accounts,这意味着登录用户来自当前工作区中维护的 APEX 用户账户。
工作区管理员可以通过页面右上角的用户与扳手按钮进入 Manage Users and Groups。在账户列表中,管理员会为每个用户设置账户类型:例如 ADINA 是 Workspace Administrator,LEO 和 LUCY 是 Developer,其余用户只作为应用最终用户登录运行时应用。
验证点:拥有 Builder 权限的用户可以登录 App Builder;仅为最终用户的账户只能登录你开发的 APEX 应用。
11.3 分配用户角色#
角色用于表达用户在业务中的职责。进入 Shared Components > Application Access Control 后,可以定义应用角色并把用户分配到一个或多个角色。Woods HR 中所有用户都拥有 Employee 角色;ADINA 额外拥有 App Admin;SUSAN 额外拥有 HR Rep。
关键差异:角色定义属于应用定义的一部分,会随应用导出和导入;具体用户的角色分配不会随应用新版导入而覆盖目标环境。这样可以保护测试、生产等环境中已有的本地用户授权。
验证点:检查 ADINA、SUSAN 和普通员工登录后的功能差异是否与角色分配一致。
11.4 使用规则和角色塑造用户体验#
Authorization Scheme 是可以挂到应用元素上的访问规则。它不局限于页面,也可以用于导航菜单、报表列、页面项、按钮、编辑链接和页面流程。配置后,APEX 会根据当前用户的角色或自定义逻辑自动调整最终用户体验。
本节示例把授权方案应用到 Woods HR 的菜单、页面、列、按钮和编辑入口,目标是让用户只能看到并执行自己职责允许的操作。
11.4.1 声明授权方案规则#
授权方案应按它执行的规则命名。Woods HR 定义了三个基于角色成员身份的规则:
- Administrators Only:只授权应用管理员。
- Any Employee:授权所有员工。
- HR Representatives Only:只授权 HR 代表。
这三个规则都使用内置 Scheme Type Is In Role or Group。Administrators Only 使用 Application Role 检查类型,并指定角色名 App Admin。由于管理功能敏感,它设置为 Once per page view;其他两个规则可设置为 Once per session,以减少重复计算。
注意:Name(s) 字段可以填写逗号分隔的多个角色名;用户属于其中任意一个角色即可通过授权。
11.4.2 授权菜单访问#
Navigation Menu 列表决定用户能点击哪些业务入口。默认情况下,菜单项对所有用户可见,包括未登录用户。Woods HR 对菜单项配置不同授权方案:
- Home:使用 Must Not Be Public User,用户必须已登录。
- Employee Directory:使用 Any Employee,任何员工可访问。
- Salary Review:使用 HR Representative Only,只有 HR 代表可访问。
- Administration:使用 Administrators Only,只有应用管理员可访问。
验证点:不同角色登录后,菜单中只出现其被授权访问的入口。
11.4.3 授权页面访问#
页面级访问控制通过页面的 Authorization Scheme 属性完成。Salary Review 页面设置为 HR Representatives Only 后,只有 HR 代表可以进入该页面。
菜单授权可以避免用户看到入口,但页面授权才是直接访问保护。即使用户手工修改浏览器地址访问该页面,未授权用户也会看到 “Access Denied by Page security check.” 错误。
11.4.4 授权页面项或列访问#
授权方案也可以挂到具体列或页面项上。在 Employee Directory 页面中,将 SAL 和 COMM 列的 Authorization Scheme 设置为 HR Representatives Only 后,HR 代表能看到薪资和佣金列,其他员工完全看不到这些列。
验证点:普通员工打开同一报表时,敏感列不应出现在表头、列选择器或导出结果中。
11.4.5 授权按钮访问#
按钮的 Authorization Scheme 控制哪些用户能看到并使用按钮。在 Employee Directory 页面中,将 CREATE 按钮设置为 HR Representatives Only 后,只有 HR 代表能创建员工,其他用户看不到该按钮。
如果按钮会提交页面并触发页面流程,应把同一授权方案也应用到对应页面流程,作为服务端的额外检查。CREATE 按钮跳转的 Employee 页面本身也应有页面级授权,避免用户绕过按钮直接访问。
11.4.6 授权编辑链接列访问#
Interactive Report 的编辑链接列也可以设置 Authorization Scheme。在 Employee Directory 页面中,将 Link Column 设置为 HR Representatives Only 后,只有 HR 代表能看到并点击编辑链接,其他用户无法发现编辑入口。
验证点:非 HR 用户既看不到编辑链接列,也不能手工访问链接目标页面;目标页面仍应保留页面级授权。
11.4.7 体验授权效果#
当授权方案应用到菜单、页面、列、按钮和编辑链接后,同一个应用会根据用户角色自动呈现不同体验。SUSAN 是 HR Representative,因此她能看到 Employee Directory 和 Salary Review,能查看 Salary 与 Commission 列,也能使用 Create 按钮和编辑链接。ADINA 是 App Admin,能访问 Employee Directory 和 Administration,但在员工目录里看不到薪酬信息,也不知道创建或编辑员工的入口。LUCY 作为普通员工,只能以简化的只读方式查看 Employee Directory。
验证点:使用 SUSAN、ADINA、LUCY 分别登录,确认菜单、报表列、按钮、编辑链接和 Salary Review 页面访问结果都与角色匹配。
11.5 启用行级数据安全策略#
当需求不仅是“谁能进页面”,还包括“谁能看到哪些记录”时,需要行级数据安全。示例需求是:经理也可以访问 Salary Review;经理只能看到直接或间接下属;经理看到的数据只读。实现方式是新增授权方案,并结合 Oracle 数据库的 policy-driven row-level data security。
11.5.1 使用 SQL 和 PL/SQL 授权规则#
除了基于角色成员身份,APEX 授权方案还可以使用 SQL 或 PL/SQL。Woods HR 新增两个规则:
- Manages Others:当前用户至少管理一名员工时授权。
- HR Reps and Managers Only:当前用户是 HR 代表,或当前用户管理他人时授权。
11.5.1.1 使用 SQL 定义授权规则#
Manages Others 使用 Exists SQL Query 方案类型。只要查询返回一行,就表示当前用户管理至少一名员工。
select 1
from eba_demo_emp
where mgr = (select empno
from eba_demo_emp
where ename = :APP_USER)
fetch first row only
验证点:用一个有下属的经理账号登录时规则返回授权;普通员工账号不应通过该规则。
11.5.1.2 使用 PL/SQL 定义授权规则#
HR Reps and Managers Only 使用 PL/SQL Function Returning Boolean 方案类型,把两个已有授权方案组合起来:
return apex_authorization.is_authorized('HR Representatives Only')
or apex_authorization.is_authorized('Manages Others');
APEX_AUTHORIZATION.IS_AUTHORIZED 会以当前用户身份评估指定授权方案。由于两个被调用规则本身可设置为 Once per session,该组合规则也能受益于缓存后的判断结果。
11.5.2 启用行级数据安全#
要在表或视图上定义行级安全策略,工作区解析模式需要数据库权限。策略由 DBMS_RLS 包管理;启用后,数据库会对访问该表或视图的语句透明地追加安全谓词。
11.5.2.1 确认 DBMS_RLS 执行权限#
工作区解析模式必须拥有 DBMS_RLS 的 EXECUTE 权限。可先运行以下查询确认是否可以访问该包:
SELECT 'DBMS_RLS accessible' as result
FROM dual
WHERE EXISTS (
SELECT 1 FROM all_objects
WHERE object_name = 'DBMS_RLS'
AND object_type = 'PACKAGE'
AND owner = 'SYS')
如果结果为 No Rows Found,需要请 DBA 授权:
grant execute on dbms_rls to your_workspace_schema
11.5.2.2 定义安全策略函数#
行级安全策略把一个 security policy function 绑定到表或视图。该函数返回一段 WHERE 谓词文本,数据库会自动把它应用到对目标对象的访问中。
function my_policy_function (
schema_var in varchar2,
table_var in varchar2)
return varchar2
返回 1=0 表示当前用户看不到任何行;返回 1=1 表示可以看到全部行;否则返回具体谓词来限制可见记录。Woods HR 的 ONLY_OWN_REPORTS 函数逻辑如下:HR 代表返回全部记录;经理返回其直接和间接下属;其他用户返回空集。
function only_own_reports(
schema_var in varchar2,
table_var in varchar2)
return varchar2
is
l_user_empno number;
l_predicate varchar2(4000);
l_apex_user varchar2(255);
begin
l_apex_user := sys_context('APEX$SESSION','APP_USER');
if l_apex_user is null then
return '1=0';
end if;
if apex_authorization.is_authorized('HR Representatives Only') then
return '1=1';
end if;
if not apex_authorization.is_authorized('Manages Others') then
return '1=0';
end if;
begin
select empno
into l_user_empno
from eba_demo_emp
where ename = l_apex_user;
exception
when no_data_found then
return '1=0';
end;
l_predicate := 'empno in (
select empno
from eba_demo_emp
start with mgr = ' || l_user_empno || '
connect by prior empno = mgr)';
return l_predicate;
end only_own_reports;
验证点:HR 代表应能看到全部员工;经理只能看到管理链下的员工;无权限用户不应看到任何行。
11.5.2.3 添加并启用安全策略#
Employee Directory 仍需要显示所有员工,所以不要直接把策略加到原表上。为 Salary Review 创建一个专用视图 EBA_DEMO_EMP_V,再把行级安全策略绑定到这个视图。
create view eba_demo_emp_v as
select empno,
ename,
job,
mgr,
hiredate,
sal,
comm,
deptno
from eba_demo_emp;
随后调用 DBMS_RLS.ADD_POLICY,把视图、策略名和策略函数关联起来:
begin
begin
dbms_rls.drop_policy(
object_schema => sys_context('USERENV','CURRENT_USER'),
object_name => 'EBA_DEMO_EMP_V',
policy_name => 'ONLY_OWN_REPORTS_POLICY');
exception
when others then
null;
end;
dbms_rls.add_policy(
object_schema => sys_context('USERENV','CURRENT_USER'),
object_name => 'EBA_DEMO_EMP_V',
policy_name => 'ONLY_OWN_REPORTS_POLICY',
function_schema => sys_context('USERENV','CURRENT_USER'),
policy_function => 'ONLY_OWN_REPORTS',
statement_types => 'SELECT,INSERT,UPDATE,DELETE',
update_check => true,
enable => true);
end;
可用以下查询检查已应用的策略:
SELECT
object_name,
policy_name,
function,
policy_type,
enable,
sel, ins, upd, del,
chk_option
FROM USER_POLICIES
ORDER BY object_name, policy_name
11.5.2.4 引用其他上下文信息#
安全策略函数除了调用 APEX_AUTHORIZATION.IS_AUTHORIZED,还可以读取 APEX 会话上下文来构造更通用的谓词。
- 当前用户名:
SYS_CONTEXT('APEX$SESSION','APP_USER') - 应用 ID:
SYS_CONTEXT('APEX$SESSION','APP_ID') - 会话 ID:
SYS_CONTEXT('APEX$SESSION','APP_SESSION') - 租户 ID:
SYS_CONTEXT('APEX$SESSION','APP_TENANT_ID')
读取 APEX ACL 角色:
select role_name
from apex_appl_acl_user_roles
where user_name = sys_context('APEX$SESSION','APP_USER')
and application_id = sys_context('APEX$SESSION','APP_ID')
读取外部身份提供商启用的动态组:
select group_name
from apex_workspace_session_groups
where apex_session_id = sys_context('APEX$SESSION','APP_SESSION')
and user_name = sys_context('APEX$SESSION','APP_USER')
Tenant ID 可以在 After Authentication 应用流程或认证后过程中通过 APEX_SESSION.SET_TENANT_ID 设置。多租户应用可用它确保用户只访问自己公司的数据。
11.6 结合数据安全优化 Salary Review#
完成视图、行级安全策略和两个新增授权方案后,Salary Review 页面可以支持新需求:经理能进入页面,但只能看到自己管理链下的员工,并且薪资与佣金列只读;HR 代表仍能查看并编辑所有员工薪酬。
11.6.1 切换到启用数据安全的视图#
当区域基于带有行级安全策略的表或视图时,数据库会自动执行策略。把 Salary Review Interactive Grid 区域的 Table Name 从原表改为 EBA_DEMO_EMP_V,页面就会继承视图上的数据安全策略。
11.6.2 用授权规则驱动只读状态#
Salary Review 需要允许 HR 代表编辑 Salary 和 Commission,但经理只能只读查看自己的下属。可在这两列的 Read Only 表达式中使用:
not apex_authorization.is_authorized('HR Representatives Only')
验证点:经理可以打开页面但不能编辑薪资和佣金;HR 代表仍可编辑。
11.6.3 调整 Salary Review 页面访问#
把 Salary Review 页面的 Authorization Scheme 从仅 HR 代表访问改为 HR Reps and Managers Only,这样 HR 代表和经理都可以进入页面。
11.6.4 为经理启用 Salary Review 菜单#
页面授权放开后,还需要把导航菜单项的 Authorization Scheme 同步改为 HR Reps and Managers Only。这样经理可以从菜单正常进入 Salary Review,而不是只能通过 URL 访问。
11.6.5 测试增强后的 Salary Review 页面#
运行时,授权方案和行级数据安全共同决定功能与数据范围。经理 JONES 登录后,菜单中会出现 Salary Review;打开页面后,他只看到直接或间接向他汇报的四名员工,并且 Sal 与 Comm 列只读。HR 代表 SUSAN 登录后,仍能看到所有员工并编辑薪资与佣金。
11.7 集成外部身份提供商#
APEX Accounts 适合快速内置认证,但很多组织已经在外部身份提供商中管理用户和角色。此时可以创建 Social Sign-in 类型的 Authentication Scheme,通过 OpenID Connect 或 OAuth 2.0 登录,并在认证后动态启用用户所属组。
示例使用 OCI Identity and Access Management (IAM) 中名为 WoodsHR 的 IAM domain。该域包含用户、组、用户与组的分配,以及允许 APEX 应用认证的 integrated application client。为了便于比较,外部身份提供商中配置与 APEX Accounts 示例相同的用户和角色结构。
注意:这里展示的基础 IAM 功能可在 OCI Always Free 租户中试验;更多 IAM 功能可能需要付费租户。
11.7.1 在 IAM Domain 中定义用户#
在 WoodsHR domain 的 User management 标签页中定义用户。为了与 APEX Accounts 示例保持一致,这里创建同一组用户名。
11.7.2 在 IAM Domain 中创建组#
OCI IAM 使用 group 表达 APEX 中 role 的概念。在 User management 标签页中,除了默认的 All Domain Users 和 Domain_Administrators,创建与 APEX 角色对应的三个组:Employee、HR Rep、App Admin。
11.7.3 将 IAM Domain 用户分配到组#
在组编辑页的 Users 标签页中把用户加入组。示例中八个用户都是 Employee 组成员;SUSAN 额外加入 HR Rep;ADINA 额外加入 App Admin。
验证点:外部组分配应与 APEX 角色示例保持同构,否则后续授权结果会不同。
11.7.4 为 APEX 配置 Confidential App#
在 IAM domain 的 Integrated applications 标签页中,创建允许 Woods HR APEX 应用通过该域认证的应用客户端。创建时提供 Woods HR APEX 应用 URL。
随后编辑 OAuth 配置:
- 选择 Configure this application as a client now。
- Allowed grant types 选择 Authorization code。
- Redirect URL 填写 APEX 实例的
/ords/apex_authentication.callback绝对 URL。 - Client type 选择 Confidential。
- 可按需要启用 Bypass consent,以跳过返回 profile 与 group 信息时的用户同意页。
配置完成后,会得到 Client ID、Client Secret、Domain URL 和 Discovery URL,例如 https://idcs-xx...xx.identity.oraclecloud.com/.well-known/openid-configuration。
11.7.5 为身份提供商定义凭据#
在创建 Social Sign-in 认证方案前,先在 Workspace Utilities 中定义 Web Credential。示例凭据 Woods HR OAuth 使用 Basic Authentication 类型,用来安全保存 OCI IAM confidential application 的 Client ID 和 Client Secret。建议在 Valid for URLs 中填写 IAM Domain URL,避免该凭据被误用于其他 URL。
11.7.6 使用外部提供商认证#
Web Credential 定义完成后,创建名为 Woods HR OAuth 的 Social Sign-in Authentication Scheme,并引用该 Web Credential。关键配置如下:
- Credential Store:选择刚创建的 Web Credential。
- Authentication Provider:选择 OpenID Connect Provider。
- Discovery URL:填写 IAM domain discovery URL。
- Scope:填写
profile,groups。 - Username:使用
#sub#,以 IAM subject 作为 APEX username。 - Convert Username To Upper Case:Yes。
- Additional User Attributes:填写
groups。
11.7.7 在认证后过程中启用组#
外部身份提供商返回组信息后,需要在 post-authentication procedure 中把这些组注册为 APEX 动态组。Social Sign-in 认证方案的 Login Processing 标签页可以填写一个包过程名作为 Post-Authentication Procedure Name。
SUSAN 通过 OCI IAM 登录后,REST 响应中的 groups 属性包含她所属的组。每个对象的 name 属性就是要启用的组名。
{
"family_name": "Sunshine",
"given_name": "Susan",
"groups": [
{
"id": "xxxxxxxxxxxx",
"name": "Employee",
"$ref": "http://dp-admin:9246/admin/v1/Groups/xxxxxxxxxxxx"
},
{
"id": "yyyyyyyyyyyyy",
"name": "HR Rep",
"$ref": "http://dp-admin:9246/admin/v1/Groups/yyyyyyyyyyyyy"
}
],
"name": "Susan Sunshine",
"preferred_username": "susan",
"sub": "susan",
"updated_at": 1755958517
}
示例包过程从最近解析的 APEX_JSON 文档中读取 groups 数组,把每个 name 推入字符串列表,然后调用 APEX_AUTHORIZATION.ENABLE_DYNAMIC_GROUPS。
package eba_demo_woodshr_auth is
procedure post_authentication;
end eba_demo_woodshr_auth;
--
package body eba_demo_woodshr_auth is
function post_auth_json
return json_object_t
is
l_ret json_object_t;
begin
if apex_json.g_values.count > 0 then
apex_json.initialize_clob_output;
apex_json.write( p_values => apex_json.g_values );
l_ret := json_object_t(apex_json.get_clob_output);
apex_json.free_output;
else
l_ret := json_object_t();
end if;
return l_ret;
end post_auth_json;
function get_object(
p_array in json_array_t,
p_index in pls_integer)
return json_object_t
is
begin
return treat(p_array.get(p_index) as json_object_t);
end get_object;
procedure post_authentication
is
l_auth_data json_object_t := post_auth_json;
l_groups_arr json_array_t;
l_groups apex_t_varchar2 := apex_t_varchar2();
begin
if l_auth_data.has('groups')
and l_auth_data.get('groups').is_array
then
l_groups_arr := l_auth_data.get_array('groups');
for i in 0 .. l_groups_arr.get_size - 1 loop
apex_string.push(l_groups,
get_object(l_groups_arr,i)
.get_string('name'));
end loop;
if l_groups.count > 0 then
apex_authorization.enable_dynamic_groups(l_groups);
end if;
end if;
end post_authentication;
end eba_demo_woodshr_auth;
如果外部提供商需要额外 API 才能取得组信息,可在认证后过程中使用 APEX_WEB_SERVICE.MAKE_REST_REQUEST 或 REST Data Source 获取组名,再动态启用。
11.7.8 从自定义代码获取应用组#
使用 ENABLE_DYNAMIC_GROUPS 后,需要在应用安全设置中把 Source for Role or Group Schemes 改为 Custom Code。入口是 Shared Components > Security Attributes,在 Authorization 标签页调整该设置。
11.7.9 将授权规则切换为 Custom#
启用动态组后,原来基于 Is In Role/Group 的授权方案需要把 Type 改为 Custom,这样规则会根据认证后过程注册的动态组来判断成员身份。示例中 Employee 授权方案被切换为 Custom。
11.7.10 将 Social Sign-in 设为当前方案#
要在运行时试用新的认证方案,需要把 Woods HR OAuth Social Sign-in Authentication Scheme 设为当前方案。编辑该方案并点击 Make Current Scheme。
更高级的场景可以启用 Switch in Session,再按用户动态决定使用哪个认证方案。
11.7.11 体验外部身份提供商#
完成配置后,应用具备以下组件:OCI IAM domain 中的用户、组、组分配和 confidential app;保存 client id 与 secret 的 Web Credential;带 OpenID Connect discovery URL 的 Social Sign-in 认证方案;启用动态组的认证后过程;使用 Custom Code 解析组的应用设置;以及切换为 Custom 类型的授权方案。
最终用户运行应用时,会先看到外部身份提供商的登录页。登录完成后,应用其余行为与使用 APEX Accounts 时一致,仍然按角色控制菜单、页面、列、按钮和数据访问。
11.8 检查授权规则上下文#
授权方案的 Evaluation Point 决定规则能使用多少上下文信息。Once per session 适合只依赖用户名和角色/组成员身份的判断;如果规则依赖会话期间可能变化的信息,使用 Once per page view 的 SQL 或 PL/SQL 规则可以引用标准绑定变量。
APP_USER:当前登录用户名;未登录时为 nobody。APP_ID:当前应用 ID。APP_ALIAS:当前应用别名。APP_PAGE_ID:当前页面 ID。APP_PAGE_ALIAS:当前页面别名。WORKSPACE_ID:当前工作区 ID。
如果授权方案设置为 Once per component 或 Always (No Caching),SQL 或 PL/SQL 还能引用当前组件信息:
APP_COMPONENT_TYPE:包含当前组件的字典视图名。APP_COMPONENT_NAME:当前组件名。APP_COMPONENT_ID:当前组件在字典视图中的唯一 ID。
这些上下文可以支持更通用、数据驱动的规则,例如由应用管理员在设置页面维护访问矩阵。为性能考虑,应缓存影响授权决策的唯一因素组合的结果。