前言:Oracle官方是支持EBS和APEX 无缝集成的,有三种方式,1.OAM 2.OSSO 3.客户化定制,如果EBS本身没安装前面OAM或者OSSO的话,客户化定制相对不那么麻烦点。
本文DEMO演示如何用客户化集成方式来演示从EBS菜单如何免密码输入跳转到APEX指定页面。
架构说明
EBS 版本 12.1.3 对应数据库版本 11.2.0.3
APEX 版本 20.1 (这个版本是能兼容IE前提下的最高版本,20.2就抛弃支持IE了)
EBS和APEX安装在同一个单实例数据库中,单独建了一个schema用来安装apex的工作空间,参考官方指导意见,为了安全起见没有安装到apps 用户下。
EBS补丁升级
See metalink notes :
Extending Oracle E-Business Suite Release 12.1.3 and Above Using Oracle Application Express (APEX) (Doc ID 1306563.1)
对于EBS 12.1.3的版本,只需要下载这四个补丁包就可以了
文档所说的其他前续依赖包都比较老,一般的系统都打了,可以用如下方式验证:
SELECT *
FROM AD_BUGS
WHERE BUG_NUMBER LIKE '%12316083%' --对应 patch号
p12316083_R12.FND.B_R12_GENERIC.zip
p12316083_R12.FND.B_R12_zht.zip
p12726556_R12.FND.B_R12_GENERIC.zip
p12726556_R12.FND.B_R12_zht.zip
需要进入维护模式打补丁,没有mos账号的同学给我留言(shixian.yang@qq.com),我邮箱把补丁传给你。
官方实例安装
从上面的对应的patch上下载下面几个脚本
apex_ebs_env.sql
apex_ebs_setup.sql
apex_ebs_application_rev3.sql --这个是apex实例安装程序,通过apex前台导入应用即可。
其中apex_ebs_env.sql不兼容12.1.3需要改动下,如下:
将如下脚本保存到sql文件中,用sqlplus连接apps账号执行
--执行方式:sqlplus "apps/apps_password" @apex_ebs_env
--脚本来源Oracle官方,为使用于EBS 12.1版本,略有改动
set define '^'
set concat on
set concat .
set verify off
set termout off
spool off
set termout on
column foo3 new_val log1
select 'apex_ebs_env_'||to_char(sysdate,'yyyy-mm-dd_hh24-mi-ss')||'.log' foo3 from dual;
spool ^log1
prompt .
prompt . << Enter Criteria >>
prompt .
accept APEX_URL CHAR prompt '. APEX URL {http://<EBS_Hostname>:8080/apex} : '
accept APEX_APPID char default '110' prompt '. APEX Application Id [110]: '
accept APEX_PAGE_NR char default '2' prompt '. APEX Page Number with No Repsonsibility [2]: '
accept APEX_PAGE_R char default '3' prompt '. APEX Page Number Using Responsibility [3]: '
accept EBS_USER CHAR default 'APEX' prompt '. EBS Username to be associated with Menu [APEX]: '
accept EBS_PWD CHAR default 'Welcome0' prompt '. EBS Username Password [Welcome0]: '
accept EBS_EMAIL CHAR prompt '. EBS Username Email Address: '
prompt .
prompt ... Set EBS APEX profile option
prompt .
declare
profile boolean default false;
begin
profile := fnd_profile.save ('FND_APEX_URL','^APEX_URL','SITE');
end;
/
commit;
prompt .
prompt ... Create 2 demo functions for APEX
prompt .
begin
fnd_form_functions_pkg.load_row(
X_FUNCTION_NAME => 'XX_APEX_DEMO_1',
X_APPLICATION_SHORT_NAME => null,
X_FORM_NAME => null,
X_PARAMETERS => null,
X_TYPE => 'JSP',
X_WEB_HOST_NAME => null,
X_WEB_AGENT_NAME => null,
X_WEB_HTML_CALL => 'GWY.jsp?targetAppType=APEX&p=^APEX_APPID:^APEX_PAGE_NR',
X_WEB_ENCRYPT_PARAMETERS => 'N',
X_WEB_SECURED => 'N',
X_WEB_ICON => null,
X_OBJECT_NAME => null,
X_REGION_APPLICATION_NAME => null,
X_REGION_CODE => null,
X_USER_FUNCTION_NAME => 'Update User Email (Without Responsibility)',
X_DESCRIPTION => 'Update the current user email without checking the user responsibility.',
X_OWNER => null,
X_CUSTOM_MODE => 'FORCE',
X_LAST_UPDATE_DATE => to_char(sysdate, 'YYYY/MM/DD'),
X_MAINTENANCE_MODE_SUPPORT => 'NONE',
X_CONTEXT_DEPENDENCE => 'RESP',
X_JRAD_REF_PATH => null
);
fnd_form_functions_pkg.load_row(
X_FUNCTION_NAME => 'XX_APEX_DEMO_2',
X_APPLICATION_SHORT_NAME => null,
X_FORM_NAME => null,
X_PARAMETERS => null,
X_TYPE => 'JSP',
X_WEB_HOST_NAME => null,
X_WEB_AGENT_NAME => null,
X_WEB_HTML_CALL => 'GWY.jsp?targetAppType=APEX&p=^APEX_APPID:^APEX_PAGE_R:::::EBS_RESP_ID,EBS_APP_ID,EBS_SEC_GROUP:[RESPONSIBILITY_ID],[RESP_APPL_ID],[SECURITY_GROUP_ID]',
X_WEB_ENCRYPT_PARAMETERS => 'N',
X_WEB_SECURED => 'N',
X_WEB_ICON => null,
X_OBJECT_NAME => null,
X_REGION_APPLICATION_NAME => null,
X_REGION_CODE => null,
X_USER_FUNCTION_NAME => 'Update User Email (Using Responsibilities)',
X_DESCRIPTION => 'Update the current user email after checking the user responsibility. This option is more secure!',
X_OWNER => null,
X_CUSTOM_MODE => 'FORCE',
X_LAST_UPDATE_DATE => to_char(sysdate, 'YYYY/MM/DD'),
X_MAINTENANCE_MODE_SUPPORT => 'NONE',
X_CONTEXT_DEPENDENCE => 'RESP',
X_JRAD_REF_PATH => null
);
end;
/
commit;
prompt .
prompt ... Create Menu and add two menu entries for the functions
prompt .
begin
-- Create menu
fnd_menus_pkg.load_row(
X_MENU_NAME => 'XX_APEX_MENU',
X_MENU_TYPE => 'HOMEPAGE',
X_USER_MENU_NAME => 'XX_APEX Demo',
X_DESCRIPTION => 'Menu for Oracle APEX Demonstration app.',
X_OWNER => null,
X_CUSTOM_MODE => 'FORCE',
X_LAST_UPDATE_DATE => to_char(sysdate, 'YYYY/MM/DD')
);
-- create menu entries
fnd_menu_entries_pkg.load_row(
X_MODE => 'FORCE',
X_ENT_SEQUENCE => '1',
X_MENU_NAME => 'XX_APEX_MENU',
X_SUB_MENU_NAME => null,
X_FUNCTION_NAME => 'XX_APEX_DEMO_1',
X_GRANT_FLAG => 'Y',
X_PROMPT => 'Update Email',
X_DESCRIPTION => 'Update your email without using responsibility.',
X_OWNER => null,
X_CUSTOM_MODE => 'FORCE',
X_LAST_UPDATE_DATE => to_char(sysdate, 'YYYY/MM/DD')
);
--
fnd_menu_entries_pkg.load_row(
X_MODE => 'FORCE',
X_ENT_SEQUENCE => '2',
X_MENU_NAME => 'XX_APEX_MENU',
X_SUB_MENU_NAME => null,
X_FUNCTION_NAME => 'XX_APEX_DEMO_2',
X_GRANT_FLAG => 'Y',
X_PROMPT => 'Update Email (Using Responsibility)',
X_DESCRIPTION => 'Update your email using EBS Responsibility.',
X_OWNER => null,
X_CUSTOM_MODE => 'FORCE',
X_LAST_UPDATE_DATE => to_char(sysdate, 'YYYY/MM/DD')
);
end;
/
commit;
prompt .
prompt ... Submit Concurrent Request to compile menu entries
prompt .
declare
l_conc_status varchar2(1);
begin
l_conc_status := fnd_menu_entries_pkg.submit_compile;
end;
/
commit;
prompt .
prompt ... Create Responsibility and User, and then add the responsibility to the user
prompt .
begin
fnd_responsibility_pkg.load_row(
X_APP_SHORT_NAME => 'FND',
X_RESP_KEY => 'XX_APEX_RESP',
X_RESPONSIBILITY_NAME => 'XX_APEX_RESP',
X_OWNER => null,
X_DATA_GROUP_APP_SHORT_NAME => 'FND',
X_DATA_GROUP_NAME => 'Standard',
X_MENU_NAME => 'XX_APEX_MENU',
X_START_DATE => to_char(sysdate, 'YYYY/MM/DD'),
X_END_DATE => null,
X_DESCRIPTION => 'Responsibility for APEX Demonstration',
X_GROUP_APP_SHORT_NAME => null,
X_REQUEST_GROUP_NAME => null,
X_VERSION => 'W',
X_WEB_HOST_NAME => null,
X_WEB_AGENT_NAME => null,
X_CUSTOM_MODE => 'FORCE',
X_LAST_UPDATE_DATE => to_char(sysdate, 'YYYY/MM/DD')
);
--
-- Add USER
--
fnd_user_pkg.createuser(
X_USER_NAME => upper('^EBS_USER'),
X_OWNER => null,
X_UNENCRYPTED_PASSWORD => '^EBS_PWD',
X_START_DATE => sysdate,
X_END_DATE => null,
X_PASSWORD_DATE => sysdate,
X_PASSWORD_LIFESPAN_DAYS => 30,
X_EMPLOYEE_ID => null,
X_EMAIL_ADDRESS => '^EBS_EMAIL'
);
--
-- Add Responsibility to USER
--
fnd_user_pkg.addresp(
USERNAME => upper('^EBS_USER'),
RESP_APP => 'FND',
RESP_KEY => 'XX_APEX_RESP',
SECURITY_GROUP => 'STANDARD',
DESCRIPTION => 'Add access to APEX Demo menu options',
START_DATE => sysdate,
END_DATE => null
);
end;
/
commit;
set define on
安全登录客户化程序包编写
本节以下代码全部部署在apps用户下.
下面用3段代码来演示常规需要18步配置才能达到的效果:
先刷依赖程序包:
/*这个程序包类似产生token及进行token校验的结果 */
CREATE OR REPLACE PACKAGE APPS.XXAPX_SECURITY_PKG AUTHID DEFINER AS
FUNCTION generate_hash (
p_string IN VARCHAR2
, p_offset IN NUMBER DEFAULT 0) RETURN VARCHAR2;
FUNCTION validate_hash (
p_string IN VARCHAR2
, p_hash IN VARCHAR2
, p_delay IN NUMBER DEFAULT 5) RETURN BOOLEAN;
END XXAPX_SECURITY_PKG;
/
CREATE OR REPLACE PACKAGE BODY APPS.XXAPX_SECURITY_PKG AS
g_key VARCHAR2(100);
FUNCTION generate_hash (
p_string IN VARCHAR2
, p_offset IN NUMBER DEFAULT 0) RETURN VARCHAR2
IS
BEGIN
IF p_string IS NULL THEN RETURN NULL; END IF;
RETURN RAWTOHEX(UTL_RAW.cast_to_raw(
DBMS_OBFUSCATION_TOOLKIT.MD5(
input_string=>p_string||':'||
TO_CHAR(SYSDATE-(p_offset/24*60*60), 'YYYYMMDD HH24MISS')||g_key)));
END generate_hash;
FUNCTION validate_hash (
p_string IN VARCHAR2
, p_hash IN VARCHAR2
, p_delay IN NUMBER DEFAULT 5) RETURN BOOLEAN
IS
BEGIN
FOR i IN 0..p_delay LOOP
IF p_hash = generate_hash (p_string, i) THEN RETURN TRUE; END IF;
END LOOP;
RETURN FALSE;
END validate_hash;
BEGIN
SELECT encrypted_user_password
INTO g_key
FROM FND_USER
WHERE user_name = 'SYSADMIN';
END XXAPX_SECURITY_PKG;
/
代码段1-客户化登录-验证函数
FUNCTION apex_validate_login(p_username IN VARCHAR2, p_password IN VARCHAR2) RETURN BOOLEAN IS
BEGIN
IF XXAPX_SECURITY_PKG.validate_hash(p_username, p_password) THEN
RETURN TRUE;
END IF;
RETURN(FND_WEB_SEC.validate_login(p_username, p_password) = 'Y');
END apex_validate_login;
这个函数放到如下地方:
代码段2-自动登录代码
PROCEDURE auto_login IS
lv_username VARCHAR2(300);
lv_password VARCHAR2(300);
lc_blacklist VARCHAR2(2000) := 'GUEST';
BEGIN
--ebs_getsession(lv_username);
wfa_sec.getsession(lv_username);
plog.debug('lv_username:' || lv_username);
lv_password := XXAPX_SECURITY_PKG.generate_hash(lv_username);
plog.debug('lv_password:' || lv_password);
--注意这个地方要过滤掉EBS的GUEST用户
IF lv_password IS NOT NULL AND lv_username IS NOT NULL AND lv_username <> 'GUEST' THEN
/* APEX_CUSTOM_AUTH.login(P_UNAME => lv_username,
P_PASSWORD => lv_password,
P_SESSION_ID => v('APP_SESSION'),
P_APP_PAGE => :APP_ID || :APEX_PAGE);*/
apex_authentication.login(p_username => lv_username, p_password => lv_password);
END IF;
EXCEPTION
WHEN OTHERS THEN
plog.error('自动登录异常' || lv_username || lv_password);
plog.full_error_backtrace;
END auto_login;
这个函数是用到登录页的befor header上的process中,如下图:
代码3-EBS环境信息传入
在做EBS报表相关开发时,会充分用到MO安全配置文件来达到多OU下各个数据的屏蔽,那么在EBS中,怎么把EBS会话的基础信息传回到APEX中呢,下面就是代码示例,
其本质是利用ICX相关表来实现,关于EBS登录会话原理解析,可以看下面这篇文章
EBS登录会话原理解析
FUNCTION check_ebs_credentials RETURN BOOLEAN IS
c_ebs VARCHAR2(240) := 'E-Business Suite';
l_authorized BOOLEAN;
l_user_id NUMBER;
l_resp_id NUMBER;
l_resp_appl_id NUMBER;
l_sec_group_id NUMBER;
l_org_id NUMBER;
l_time_out NUMBER;
l_ebs_url VARCHAR2(100);
l_appl_name VARCHAR2(240);
l_session_count NUMBER := 0;
l_user_name VARCHAR2(3000);
CURSOR get_apps_credentials IS
SELECT iss.user_id,
iss.responsibility_id,
iss.responsibility_application_id,
iss.security_group_id,
iss.org_id,
iss.time_out,
isa.value,
fu.user_name
FROM apps.icx_sessions iss, apps.icx_session_attributes isa, fnd_user fu
WHERE iss.session_id = apps.icx_sec.getsessioncookie
AND isa.session_id = iss.session_id
AND isa.name = '_USERORSSWAPORTALURL'
AND iss.user_id = fu.user_id;
CURSOR get_appl_name(b_appl_id NUMBER) IS
SELECT application_name
FROM apps.fnd_application_tl
WHERE application_id = b_appl_id
AND LANGUAGE = USERENV('LANG');
BEGIN
OPEN get_apps_credentials;
FETCH get_apps_credentials
INTO l_user_id, l_resp_id, l_resp_appl_id, l_sec_group_id, l_org_id, l_time_out, l_ebs_url, l_user_name;
IF get_apps_credentials%NOTFOUND THEN
l_authorized := FALSE;
plog.debug('没有数据');
ELSE
l_authorized := TRUE;
OPEN get_appl_name(l_resp_appl_id);
FETCH get_appl_name
INTO l_appl_name;
IF get_appl_name%NOTFOUND THEN
l_appl_name := c_ebs;
END IF;
CLOSE get_appl_name;
plog.debug('l_user_id' || l_user_id);
--下面这些项要在APEX共享组件的应用程序项中预先定义好,不然会报错。
apex_util.set_session_state('EBS_USER_NAME', TO_CHAR(l_user_name));
apex_util.set_session_state('EBS_USER_NAME', 'APEX');
apex_util.set_session_state('EBS_RESP_ID', TO_CHAR(l_resp_id));
apex_util.set_session_state('EBS_APP_ID', TO_CHAR(l_resp_appl_id));
apex_util.set_session_state('EBS_SEC_GROUP', TO_CHAR(l_sec_group_id));
apex_util.set_session_state('EBS_ORG_ID', TO_CHAR(l_org_id));
apex_util.set_session_state('EBS_TIME_OUT',TO_CHAR(l_time_out));
apex_util.set_session_state('EBS_URL', l_ebs_url);
apex_util.set_session_state('EBS_APPLICATION_NAME', l_appl_name);
apex_util.set_session_max_idle_seconds(l_time_out * 60, 'APPLICATION');
-- 执行登录
apex_authentication.send_login_username_cookie(p_username => FND_GLOBAL.user_name);
plog.debug(FND_GLOBAL.user_name);
/*
apex_authentication.login(p_username => l_user_name,
p_password => XXAPX_SECURITY_PKG.generate_hash(l_user_name));
*/
/*
APEX_CUSTOM_AUTH.login(P_UNAME => l_user_name,
P_PASSWORD => XXAPX_SECURITY_PKG.generate_hash(l_user_name),
P_SESSION_ID => v('APP_SESSION'),
P_APP_PAGE => V('APP_ID') || ':2');
*/
END IF;
IF apex_authentication.is_authenticated THEN
l_authorized := TRUE;
ELSE
l_authorized := FALSE;
END IF;
CLOSE get_apps_credentials;
plog.assert(l_authorized, '调用失败');
RETURN l_authorized;
plog.debug('调用成功');
EXCEPTION
WHEN OTHERS THEN
plog.debug('调用异常');
plog.full_error_backtrace;
IF get_apps_credentials%ISOPEN THEN
CLOSE get_apps_credentials;
END IF;
RETURN FALSE;
END;
APEX和第三方集成原理解析
和单点登录的token或者和验证原理类似,APEX灵活的提供了自定义token和自定义验证token的方式 ,并提供了一个统一的入口(apex_authentication.login和 APEX_CUSTOM_AUTH.login)来实现和自身的集成,利用这个原理,不但能和EBS集成,和其他系统集成也类似,这个架构思路值得我们学习。
参考文档
apex-ebs-extension-white-paper
Integration of Oracle Apex and E-Business Suite R12(需自备梯子)