44.12. EJB的安全管理

摘要

本节描述Apusic应用服务器中EJB体系结构中,有关安全管理方面的内容,包括安全角色、安全主体(Security Principal)、安全角色引用等概念的介绍;包括EJB体系结构中对组件方法级别的访问控制进行声明性的设置;包括组件模型中提供的用于安全管理方面的API的介绍;包括如何映射系统的安全主体与定义组件方法访问控制的安全角色,以及如何映射代码中的角色引用和安全角色的方法;包括如何配置组件方法在执行过程中,使用不同于调用者身份的其他身份对其他组件进行调用。

44.12.1. 安全模型

为降低开发者在保护应用安全方面的负担,并且使企业应用可以在在部署时灵活地制定安全策略,EJB体系结构中,采用的是基于角色授权的安全模型。

基于角色授权的安全模型使开发者不再在应用的业务逻辑中对安全策略进行硬编码,而且,大多数EJB组件的业务方法中也不应包含与安全有关的逻辑,在应用被部署到不同的生产环境时,根据不同的需要,可以改动应用的安全策略而不需要更改应用中的代码。

44.12.1.1. 安全角色(Security Role)与方法权限

EJB组件模型中的安全,是基于安全角色进行授权的。所谓安全角色,实际上它代表逻辑上的权限组合,例如,一个名为“teller”的安全角色,可能代表了能执行某个账户组件中的“checkTransactionHistory”和“checkBalance”方法的能力。

EJB体系结构中安全角色是指语义上的权限组合。权限指为成功运行应用,应用中给定类型的用户所必须具有的权限。

开发者在EJB的部署描述中声明安全角色,并使用已声明的安全角色对定义在组件接口和Home接口中的方法进行授权。通常,每个安全角色具有调用一组定义在组件接口和Home接口中的方法的权限。

44.12.1.2. 安全主体(Security Principal)

在Apusic应用服务器中,系统提供定义系统用户与角色进行映射的工具。通常意义上,安全主体指系统中代表某个具体组织或个人的用户标识。在用户登陆系统后,系统将为用户的调用赋予一个安全主体对象,在整个操作过程中,系统将使用此对象来区分不同的用户调用和按照安全策略对调用进行授权。

44.12.1.3. 安全角色与安全主体的映射

在应用部署到实际生产环境之前,按照EJB部署描述和应用部署描述中的关于安全角色的声明信息,部署者或系统管理员对安全角色与安全主体进行映射。因此,部署应用时,部署者看到的是代表权限的安全角色,和代表实际用户的安全主体,不需要对单个的方法进行权限设置,降低了部署者将应用部署到特定生产环境中去的负担。

使用这种将应用安全逻辑与实际运行环境分开的安全模型,提高了应用的可移植特性和组件的可重用特性,应用的安全策略通过修改部署描述文件即可改变。

44.12.2. 使用安全模型

EJB的安全体系结构提供了一个灵活而易于配置的模型,本节将详细描述在Apusic应用服务器中,EJB安全管理的配置。同时,因为所有的安全策略并非都能进行声明性的表示,因此,EJB的编程模型还提供了一个简单的接口,使开发者可以在业务方法中访问安全上下文。

44.12.2.1. 声明安全角色

开发者需要在组件的部署描述文件ejb-jar.xml中,使用assembly-descriptor的子元素security-role对组件中的安全角色进行声明,每个安全角色对应一个security-role元素。security-role元素的结构如下图:

security-role元素

开发者使用description元素提供此安全角色的描述信息给应用的装配者,并使用role-name元素声明此安全角色。

如下:

...
<assembly-descriptor>
    <security-role>
        <description>
            This role includes the employees of the
            enterprise who are allowed to access the
            employee self-service application. This role
            is allowed only to access his/her own
            information.
        </description>
        <role-name>employee</role-name>
    </security-role>

    <security-role>
        <description>
            This role includes the employees of the human
            resources department. The role is allowed to
            view and update all employee records.
        </description>
        <role-name>hr-department</role-name>
    </security-role>

    <security-role>
        <description>
            This role includes the employees of the payroll
            department. The role is allowed to view and
            update the payroll entry for any employee.
        </description>
        <role-name>payroll-department</role-name>
    </security-role>
    <security-role>
        <description>
            This role should be assigned to the personnel
            authorized to perform administrative functions
            for the employee self-service application.
            This role does not have direct access to
            sensitive employee and payroll information.
        </description>
        <role-name>admin</role-name>
    </security-role>
...
</assembly-descriptor>

44.12.2.2. 定义方法许可

开发者通过在ejb-jar.xml文件中,使用assembly-descriptor的子元素method-permission,对security-role元素中声明的安全角色集合,以及在Session Bean和Entity Bean组件接口和Home接口中的方法(包括组件接口和Home接口继承的接口中定义的方法)集合之间的二元关系集合进行定义。即当且仅当安全角色R可以调用方法M时,则此二元关系集合中即存在一个元素E(R,M)。

method-permission元素的结构如下图:

method-permission元素

method-permission元素中,开发者可以使用role-name元素定义一个或多个安全角色,使用method元素定义一个或多个方法元素,其中,每个安全角色允许调用使用method元素定义的所有方法。

开发者可以使用method-permission元素中可选的description元素对此方法许可进行描述。

开发者可使用一个unchecked元素取代method-permission中的role-name元素列表,此uncheck元素用于指定本method-permission元素中定义的方法集合中的每个方法,在对方法进行调用之前,不需要进行授权。

另外,开发者也可使用exclude-list元素指定不应被调用的方法集合。exclude-list元素的结构如下图:

exclude-list元素

对于每个在exclude-list元素中指定的方法,无论调用者的使用何种身份还是方法已在method-permission元素中定义,对此方法的调用都将被拒绝。

method-permission元素和exclude-list中的method元素,使用ejb-name、method-name和method-params元素来指定定义在EJB组件接口或Home接口中的一个或多个方法,下面是几种定义method元素的正确方法:

<method>
    <ejb-name>Foo</ejb-name>
    <method-name>*</method-name>
</method>

上例指所有定义在名为Foo的EJB组件的接口和Home接口中的方法。

<method>
    <ejb-name>Foo</ejb-name>
    <method-name>Bar</method-name>
</method>

上例指定义在名为Foo的EJB组件的接口和Home接口中的所有名为Bar的方法。如组件接口和Home接口中定义了多个Bar方法,则本例包含所有这些方法。

<method>
    <ejb-name>Foo</ejb-name>
    <method-name>Bar</method-name>
    <method-params>
        <method-param>PARAMETER_1</method-param>
        ...
        <method-param>PARAMETER_N</method-param>
    </method-params>
</method>

上例指定义在名为Foo的EJB组件的接口和Home接口中的名为Bar并具有相同参数的方法。

另外,开发者可使用可选的method-intf元素指定具体接口中的方法,如下:

<method>
    <ejb-name>Foo</ejb-name>
    <method-intf>Home</method-inf>
    <method-name>*</method-name>
</method>

本例指名为Foo的EJB的Home接口中的所有方法。method-intf元素的值可以是Home、LocalHome、Remote和Local分别对应EJB组件模型中的远程Home接口、本地Home接口、远程接口和本地接口。

44.12.2.3. 定义角色映射

在应用被部署到生产环境之前,部署人员需要将组件中的安全角色映射到实际环境中的安全主体,映射在整个应用的部署描述文件apusic-application.xml中进行,使用apusic-application中的security-role元素进行映射。apusic-applicaton以及其中的security-role元素的结构如下图:

apusic-applicaton元素

由于此映射属于应用范畴,因此,本节不对此元素进行进一步描述。

44.12.2.4. 调用中的身份传播

从前面的EJB的安全模型的描述中,可以了解到当客户端调用组件接口或Home接口中的方法时,容器将检查方法的访问权限,如果方法的访问许可被设置,则容器将根据伴随调用的身份信息进行授权,判断调用者是否具有调用此方法的权限,如成功,则容器将调用组件实例的方法。

需要考虑到,被调用的方法也可能调用其他组件或对某些受保护资源进行操作,因此,需要确定这种情况下,伴随调用的身份信息如何传播。

一种情况是,组件在执行时不具有特定调用者的身份信息(如Message-driven Bean的onMessage方法),在这种情况下,组件只能调用其他组件的未设置访问权限的方法,和不受保护的资源。

另一种情况是,组件在执行时具有特定调用者的身份信息,并且在组件中需要调用其他组件的方法,或者访问受保护的资源,在这种情况下,组件只能对具有同样访问权限或没有定义访问权限的组件方法或资源进行访问。

因此,EJB组件模型提供了一种声明性的身份传播方式,使一个组件在调用其它组件或访问受保护资源时,采用原调用者的身份信息还是使用开发者定义的其他安全角色进行。同时,必须注意这种传播机制不会影响方法的原调用者的信息。

开发者可以使用security-identity元素来定义这种身份传播行为。security-identity元素的结构如下图:

security-identity元素

开发者可以使用use-caller-identity元素定义组件在访问其他组件时,使用组件调用者的身份。

[注意]注意

Message-driven Bean不能使用use-caller-identity元素

开发者可以使用run-as元素定义组件在访问其他组件时,使用其它的调用者身份,这里的调用者身份实际上是一个逻辑角色,必须对应一个ejb-jar.xml部署描述文件中使用security-role元素定义的安全角色。

下面是一个定义run-as元素的例子:

...
<enterprise-beans>
    ...
    <session>
        <ejb-name>EmployeeService</ejb-name>
        ...
        <security-identity>
            <run-as>
                <role-name>admin</role-name>
            </run-as>
        </security-identity>
        ...
    </session>
    ...
</enterprise-beans>
...

在运行时,当此EmployeeService组件的实例中访问了其他的组件或资源,则伴随此访问传播的将是admin的身份信息。

44.12.2.5. 关于安全角色引用

安全角色引用指开发者使用security-role-ref元素对EJB组件代码中使用的所有安全角色名称进行声明,将其映射到实际生产环境中定义的安全角色。以提高应用中安全控制策略的灵活性,同时,使安全策略仍保持在组件代码之外。

通常,涉及调用者对组件方法的调用的授权和访问控制策略对调用者是透明的。但声明型的安全策略有时不能完全满足应用的需求,因此,EJB规范中提供了可在组件代码中访问调用者安全上下文的API。在javax.ejb.EJBContext接口中提供了两个方法,开发者可以通过这两个方法访问调用者的安全相关的信息。

public interface javax.ejb.EJBContext {
    ...
    //
    // The following two methods allow the EJB class
    // to access security information.
    //
    java.security.Principal getCallerPrincipal();

    boolean isCallerInRole(String roleName);

    //
    // The following two EJB 1.0 methods are deprecated.
    //
    java.security.Identity getCallerIdentity();
    boolean isCallerInRole(java.security.Identity role);
    ...
}

以上这两个方法,开发者可以在实例具有了安全上下文之后进行调用。如以下列表中的方法:

  • 有状态Session Bean

    ejbCreate方法;

    ejbRemove方法;

    ejbActivate方法;

    ejbPassivate方法;

    定义在组件接口中的业务方法;

    对于容器管理事务的有状态Session Bean,可以在从SessionSynchronization接口中的afterBegin、beforeCompletion、afterCompletion等方法中调用上面提到的两个方法。

  • 无状态Session Bean

    ejbCreate方法;

    ejbRemove方法;

    定义在组件接口中的业务方法。

  • Entity Bean

    ejbCreate方法;

    ejbPostCreate方法;

    ejbRemove方法;

    ejbHome方法;

    ejbLoad和ejbStore方法;

44.12.2.6. getCallerPrincipal

开发者可以调用getCallerPrincipal方法取得一个使用java.security.Principal接口表示的调用者的信息。

必须注意,getCallerPrincipal方法返回的是EJB调用者的身份信息,不代表任何run-as元素指定的身份信息。

以下是调用getCallerPrincipal方法的例子:

public class EmployeeServiceBean implements SessionBean {

    EJBContext ejbContext;    
    public void changePhoneNumber(...) {
        ...
    
        // Obtain the default initial JNDI context.
        Context initCtx = new InitialContext();
        
        // Look up the remote home interface of the EmployeeRecord
        // enterprise bean in the environment.
        Object result = initCtx.lookup(
            "java:comp/env/ejb/EmplRecord");
        
        // Convert the result to the proper type.
        EmployeeRecordHome emplRecordHome = (EmployeeRecordHome)
            javax.rmi.PortableRemoteObject.narrow(
                result,
                EmployeeRecordHome.class);

        // obtain the caller principal.
        callerPrincipal = ejbContext.getCallerPrincipal();
        
        // obtain the caller principal’s name.
        callerKey = callerPrincipal.getName();

        // use callerKey as primary key to EmployeeRecord finder
        EmployeeRecord myEmployeeRecord =
            emplRecordHome.findByPrimaryKey(callerKey);

        // update phone number
        myEmployeeRecord.changePhoneNumber(...);
        ...
    }
}

44.12.2.7. isCallerInRole

对于较难使用部署描述中的声明型的方法许可进行定义的安全检查,开发者可以通过调用此方法来完成。

开发者使用isCallerInRole(String roleName)方法检查调用者是否被指定到通过roleName参数代表的角色名。

下面是调用isCallerInRole方法的例子:

public class PayrollBean ... {
    EntityContext ejbContext;
    public void updateEmployeeInfo(EmplInfo info) {
        oldInfo = ... read from database;

        // The salary field can be changed only by callers
        // who have the security role "payroll"
        if (info.salary != oldInfo.salary &&
            !ejbContext.isCallerInRole("payroll")) {
            throw new SecurityException(...);
        }
        ...
    } 
    ...
}

当开发者在代码中调用了isCallerInRole,则必须在EJB的部署描述中使用security-role-ref元素,将代码中的安全角色引用名称,映射到实际的安全角色名。

44.12.2.8. 声明代码中使用的安全角色引用

开发者需要在部署描述中,使用security-role-ref元素声明代码中使用的安全角色名。这样,在应用被部署到实际生产环境中时,可以通过部署描述将代码中使用的安全角色名和使用security-role元素定义的安全角色进行映射。

当开发者使用了isCallerInRole方法,则必须对每个代码中引用的安全角色进行声明,security-role-ref元素结构如下图。

security-role-ref元素

开发者需要使用role-name元素声明代码中使用的角色名,使用role-link元素将其映射到某个使用security-role元素声明的安全角色名上。如下例:

...
<enterprise-beans>
    ...
    <entity>
        <ejb-name>AardvarkPayroll</ejb-name>
        <ejb-class>com.aardvark.payroll.PayrollBean</ejb-class>
        ...
        <security-role-ref>
            <description>
                This role should be assigned to the
                employees of the payroll department.
                Members of this role have access to
                anyone’s payroll record.
                The role has been linked to the
                payroll-department role.
            </description>
            <role-name>payroll</role-name>
            <role-link>payroll-department</role-link>
        </security-role-ref>
        ...
    </entity>
    ...
    <assembly-descriptor>
        ...
        <security-role>
            <description>
                This role includes the employees of the payroll
                department. The role is allowed to view and
                update the payroll entry for any employee.
            </description>
            <role-name>payroll-department</role-name>
        </security-role>
        ...
    </assembly-descriptor>
    ...
</enterprise-beans>
...

上例中,使用了security-role-ref元素,将代码中使用的安全角色名payroll映射到了部署描述中使用security-role元素声明的payroll-department安全角色。