安全性对于企业环境非常重要。在 Java EE 5 / GlassFish 环境中,您可以通过以下几种方式实现安全性:
- 传输层安全性 (TLS) / 安全套接字层 (SSL) 技术
- 身份验证 (Authentication) 和授权 (Authorization)
- 消息层安全性(仅适用于 GlassFish 中的 Web 服务)
本文讨论身份验证和授权。参考资料 [1]、[2] 和 [3] 讨论了如何在客户端和服务器端为 Enterprise JavaBeans 和 Web 服务建立 SSL 环境。Web 服务的消息层安全性将在以后的文章中讨论。
身份验证服务一般通过要求用户输入用户名和密码来实现校验用户身份的目的。在 Java EE 环境中,身份验证是和域(realm)相关联的。域可以通过多种方式存储用户身份信息,包括文件、LDAP 目录、甚至是通过 JDBC 访问的数据库(请参阅 参考资料 [4])。它还可以与 Solaris 可拔插验证模块 (Pluggable Authentication Modules, PAM) 框架一起工作。
授权服务根据所运行的软件和运行该软件的用户的身份来执行访问控制授权操作。每次当用户登录时,应用程序都会为他/她赋予一组权限。
在 Java EE 5 之前,如果您希望在某个应用程序使用授权,则需要在应用程序部署描述符 ejb-jar.xml 或 web.xml中指定授权信息。Java EE 5 的重要改进之一就是简化了 Java EE 应用程序的开发。从 Java EE 5 开始,开发人员可以在 Java 源文件中指定注释,而无需在部署描述符中加入元数据。注释简化了 Java EE 应用程序的开发,缩短了开发周期,并降低了总体拥有成本。
JSR 250(请参阅 参考资料 [5])定义了 Java 平台中的常用注释。本文将讨论 JSR 250 中定义的安全注释,并演示如何在应用程序中通过它们来实现身份验证和授权,以获得安全性。
目录
- | |
- | |
- | |
- | |
- | 结束语 |
- | 参考资料 |
- | 致谢 |
基本定义和示例
注释 (Annotation) 是一种特殊的修饰符,可以与其他修饰符共同使用。注释由 @
符号、注释类型和包含在括号中的元素值对列表组成。
本节讨论 JSR 250 定义的常用安全注释。共有 5 种(请参阅 参考资料 [6]):
-
javax.annotation.security.PermitAll
-
javax.annotation.security.DenyAll
-
javax.annotation.security.RolesAllowed
-
javax.annotation.security.DeclareRoles
-
javax.annotation.security.RunAs
@PermitAll
、@DenyAll
和 @RolesAllowed
注释是为指定 EJB 业务方法权限而定义的。@DeclareRoles
和 @RunAs
是 TYPE 级注释,用于指定与角色相关的元数据。
对于 Web 模块,您仍然需要在 web.xml 应用程序部署描述符中定义一个 <security-constraint>
来添加授权约束,这与 J2EE 1.4 相类似。在 Java EE 5 环境中,与权限相关的注释仅为 EJB 模块定义。下表总结了这些注释的基本用法。有关详细信息,请参阅 JSR 250 规范(参考资料 [5])。
| X | X | X |
| 指示某 EJB 的某个方法或所有业务方法允许被所有用户访问。 |
|
| X | X |
| 指示 EJB 的某个方法不允许被任何用户访问。 |
| X | X | X |
| 指示 EJB 的某个方法或所有业务方法允许被角色列表中的用户访问。 |
| X |
| X | X | 定义安全检查的角色,供 EJBContext.isCallerInRole、HttpServletRequest.isUserInRole 和 WebServiceContext.isUserInRole 使用。 |
| X |
| X(不适用于非 EJB 超类) | X(仅适用于 Servlet) | 指定某个组件的 run-as 角色。 |
注释 | 目标 | EJB 或其超类 | Servlet 或 Web 库 | 描述 | |
类型 | 方法 |
注意:
- 对于
@PermitAll
、@DenyAll
和 @RolesAllowed
注释,类级别的注释适用于类,方法级的注释适用于方法。方法级注释覆盖类级注释行为。 - 示例:请参考以下代码:
@Stateless
@RolesAllowed("javaee")
public class HelloEJB implements Hello {
@PermitAll
public String hello(String msg) {
return "Hello, " + msg;
}
public String bye(String msg) {
return "Bye, " + msg;
}
}
- 在该示例中,
hello()
方法允许被所有用户访问,bye()
方法允许被 javaee 角色中的用户访问。 -
@DeclareRoles
注释定义了某个组件将要使用的角色列表。在 Java EE 5 环境中,您可以通过 @javax.annotation.Resource
来查找资源,以及通过调用以下 API 来确认用户是否属于某个角色:
组件
检查角色的 API
EJB
javax.ejb.EJBContext.isCallerInRole(role)
Servlet
javax.servlet.http.HttpServletRequest.isUserInRole(role)
Web 服务
javax.xml.ws.WebServiceContext.isUserInRole(role)
- 尽管
@PermitAll
、@DenyAll
和 @RolesAllowed
注释允许实现大部分的授权决策,但仍需要 @DeclareRoles
注释来帮助实现更为复杂的逻辑。 - 例如,假设
hello
方法允许被属于角色 A 但同时不属于角色 B 的用户访问,则以下代码片段可以实现此目的:
@Stateless
@DeclaresRoles({"A", "B"})
public class HelloEJB implements Hello {
@Resource private SessionContext sc;
public String hello(String msg) {
if (sc.isCallerInRole("A") && !sc.isCallerInRole("B")) {
...
} else {
...
}
}
}
- 在 Web 模块中,您可以在 Servlet、过滤器和标记库中指定
@DeclareRoles
。JSP 页面不支持注释。 - 嵌入式 Web 服务调用中的 run-as 或调用者的身份标识和客户身份标识之间的关系是没有定义的。无定义则意味着您不能假定 Web 服务调用中的 run-as 和调用者身份标识作为客户标识传递。
注释的无效用法示例
- 同一方法中不能同时使用
@DenyAll
、@PermitAll
和 @RolesAllowed
。例如,以下这些用法是无效的:
@PermitAll
@DenyAll
public String hello()
- 有关详细信息,请参阅 JSR 250 规范的 2.11 节。
- 同一方法中不能使用
@RolesAllowed
注释两次以上。 - 例如,以下用法无效并且会导致编译失败:
@RolesAllowed("javaee")
@RolesAllowed("j2ee")
public String hello()
- 可以在
@RolesAllowed
注释中提供授权角色列表以实现想要的效果,如下所示:
@RolesAllowed({"javaee", "j2ee"})
public String hello()
安全注释的继承
本节讨论安全注释的继承。因为 GlassFish 方法的默认行为是 @PermitAll
,因此为便于阅读,以下讨论将省略该注释。
继承的一般规则如下:
- 对于方法,使用与继承层次结构中选择的方法有关联的注释。
- 对于
@RunAs
注释,只考虑层次结构叶节点中的注释。 - 对于
@DeclareRoles
注释,继承附加在继承层次结构中。
下面这个示例演示了这些注释继承规则:
示例:在 EJB 的如下层次结构中:
可以使用以下方法权限:
Hello 方法 | HelloBaseEJB 方法权限 | HelloEJB 方法权限 |
| 允许被 manager 角色中的用户访问 | 允许被所有用户访问 |
| 允许被 employee 角色中的用户访问 | 允许被 staff 角色中的用户访问 |
| 允许被 employee 角色中的用户访问 | 允许被 staff 角色中的用户访问 |
示例:在 Servlet 的以下层次结构中:
Servlet | 定义的角色 | RunAs |
| employee | engineer |
| employee、manager | staff |
| employee |
|
注意,HelloServlet2
中没有设置 run-as 角色。
使用部署描述符
使用注释可以简化应用程序的部署描述符。但是在某些场景中,我们仍然需要或者更喜欢使用部署描述符。本节将描述这些场景。
- 对于
@RolesAllowed
的 EJB Web 服务端点,您需要在 sun-ejb-jar.xml 中指定 <login-config>
和 <auth-method>
元素来定义将要使用的身份验证类型。对于用户名密码身份验证,将 <auth-method>
元素设置为 BASIC,如以下示例所示。只有 EJB Web 服务端点需要执行此步骤,EJB 不需要。
<sun-ejb-jar>
<enterprise-beans>
<ejb>
<ejb-name>HelloEjb</ejb-name>
<webservice-endpoint>
<port-component-name>HelloEjb</port-component-name>
<login-config>
<auth-method>BASIC</auth-method>
<realm>default</realm>
</login-config>
</webservice-endpoint>
</ejb>
</enterprise-beans>
</sun-ejb-jar>
@PermitAll
、@RolesAllowed
和@DenyAll
注释在 servlet 中不受支持。当使用 servlet 时,在 web.xml 部署描述符中指定身份验证和授权方式。以下信息必须在 web.xml 中指定:
-
<security-constraint>/<web-resource-collection>
这些元素指定了受保护 Web 资源的 URL 模式和 HTTP 方法。 -
<security-constraint>/<auth-constraint>
这些元素指定了允许访问受保护 Web 资源的角色列表。 -
<login-config>
该元素指定了将要执行的身份验证类型(例如,<auth-method>
(BASIC 或 FORM))、将要执行身份验证的域 (<realm-name>
),以及(如果是基于表格验证)登录表格和错误页面的位置 (<form-login-config>
)。 -
<security-role>
该元素指定了该 Web 应用程序使用的角色列表。该列表必须包含上面提到的 <auth-constraint>
中指定的角色。 - 例如,假设您希望保护 index.jsp 页面的 GET 和 POST 方法,只允许 employee 角色访问它们,则需要使用以下 web.xml 配置来实现:
...
<web-app>
<servlet>
...
</servlet>
<security-constraint>
<web-resource-collection>
<web-resource-name>MySecureResource</web-resource-name>
<url-pattern>/index.jsp</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>employee<role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>default</realm-name>
<login-config>
<security-role>
<role-name>employee</role-name>
</security-role>
</web-app>
- 对于只有某个角色才具有访问权限的应用程序来说,您需要在应用程序使用的角色与应用服务器定义的组或主体之间建立映射。为了在应用程序所使用的 employee 角色和应用服务器默认域定义的 engineer 组之间建立映射,您仍然需要在运行时部署描述符 sun-application.xml、sun-ejb-jar.xml 或 sun-web.xml 中指定该安全角色映射。
- 例如,将以下代码添加到 sun-application.xml 中就可以实现在 engineer 组的所有用户与某个应用程序的 employee 角色之间建立映射。
<security-role-mapping>
<role-name>employee</employee>
<group-name>engineer</group-name>
</security-role-mapping>
- 部署描述符覆盖注释行为。
- 示例:假设有以下 Enterprise JavaBean:
@Stateless
public class HelloEJB implements Hello {
@PermitAll
public String hello1(String msg) {
return "1: Hello, " + msg;
}
@RolesAllowed("javaee")
public String hello2(String msg) {
return "2: Hello, " + msg;
}
@DenyAll
public String hello3(String msg) {
return "3: Hello, " + msg;
}
}
-
您可以通过应用程序部署描述符 ejb-jar.xml 来覆盖方法权限:
<ejb-jar ...>
<enterprise-beans>
...
</enterprise-beans>
<assembly-descriptor>
<security-role>javaee</security-role>
<method-permission>
<role-name>javaee</role-name>
<method>
<ejb-name>HelloEJB</ejb-name>
<method-intf>Local</method-intf>
<method-name>hello1</method-name>
</method>
</method-permission>
<method-permission>
<unchecked/>
<method>
<ejb-name>HelloEJB</ejb-name>
<method-intf>Local</method-intf>
<method-name>hello3</method-name>
</method>
</method-permission>
<exclude-list>
<method>
<ejb-name>HelloEJB</ejb-name>
<method-intf>Local</method-intf>
<method-name>hello2</method-name>
</method>
</exclude-list>
</assembly-descriptor>
</ejb-jar>
- 行为如下:
方法
注释
部署描述文件覆盖
hello1()
允许被所有用户访问
允许被 javaee
角色的用户访问
hello2()
允许被 javaee
角色的用户访问
不允许被任何用户访问
hello3()
不允许被任何用户访问
允许被所有用户访问
- 在 GlassFish 中,如果应用程序部署描述文件中没有指定域,则该应用程序将使用默认域。您可以在运行时部署描述符 sun-application.xml 和 sun-ejb-jar.xml 中使用
<realm>
元素或在 web.xml 中使用 <realm-name>
元素来覆盖该设置。 - 如果在 GlassFish 中使用
@RunAs
注释,则需要将 run-as 角色关联到一个主体,如果只关联了一个主体,则该主体将作为 run-as 主体的默认值;如果关联了多个主体,则需要明确设置 run-as 主体。以下示例演示了如何在 sun-ejb-jar.xml 中设置 run-as 主体:
...
<ejb>
...
<principal>
<name>user1</name>
</principal>
...
</ejb>
-
以下示例演示了如何在 sun-web.xml 中设置 run-as 主体:
...
<servlet>
<servlet-name>myServlet</servlet-name>
<principal-name>user1</principal-name>
...
</servlet>
...
结束语
总而言之,注释可以通过身份验证和授权为 Java EE 5 环境中的应用程序提供安全性。在 Java EE 5 环境中使用身份验证和授权时,通常需要遵循以下几个步骤:
- 创建 Java EE 应用程序。
- 使用 Enterprise JavaBeans 安全注释建立授权约束,或者在 Web 模块的 web.xml 文件中添加安全约束。
- 在相应的部署描述符中添加身份验证需求
<login-config>
。 - 建立域(如果未使用默认域),或者在应用程序部署描述符中指定 run-as 主体。
- 将
<security-role-mapping>
元素添加到运行时部署描述文件中,在应用程序角色和应用服务器组或主体之间建立映射。 - 打包和部署应用程序。