ORACLE EBS 和 APEX 集成登录及原理分析

前言: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账号的同学给我留言([email protected]),我邮箱把补丁传给你。

官方实例安装

从上面的对应的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(需自备梯子)

The Fast Way to Extend the Oracle E-Business Suite

Extending Oracle E-Business Suite Release 12.1.3 and Above Using Oracle Application Express (APEX) (Doc ID 1306563.1)

猜你喜欢

转载自blog.csdn.net/x6_9x/article/details/119187891