第 2 章 安全概述
2.1. 关于声明式安全性
声明式安全性(Declarative security)是一个通过容器管理安全性来从应用程序代码里分离安全性。容器提供一个基于文件权限或用户、组和角色的授权系统。这个方法通常比让应用程序完全负责安全性的编程式(programmatic)安全性更好。
JBoss EAP 6 通过安全域来提供声明式安全性。
2.1.1. Java EE 的声明式安全性概述
J2EE 的安全模型是声明式的,你可以在标准的 XML 描述符文件里描述安全角色和权限,而不需要将安全性嵌入到商业组件里。这种安全性和商业级别的代码的隔离更倾向于是关于组件部署的功能而不是组件的商业逻辑的内在方面。例如,用于访问银行帐号的自动柜员机(ATM),安全要求、角色和权限将独立于你是如何访问银行帐号的,它会基于银行管理这个帐号的方式、ATM 的位置等来确定。
对 J2EE 应用程序的保护是基于应用程序的安全性要求的规格并通过标准的 J2EE 部署描述符来实现的。对企业级应用程序里的 EJB 和 Web 组件的安全访问是通过
ejb-jar.xml
和 web.xml
部署描述符来设置的。
2.1.2. 安全引用
EJB 和 Servlet 都可以声明一个或多个 <security-role-ref> 元素。
图 2.1. 安全角色引用模型
这个元素声明了组件将 <role-name> 组件的
role-nameType
属性值作为 isCallerInRole(String)
方法的参数使用。通过使用 isCallerInRole
方法,组件可以检验调用者是否具有用 <security-role-ref> 或 <role-name> 声明的角色。<role-name> 元素值必须通过 <role-link> 链接到 <security-role> 元素。isCallerInRole
的典型用法是执行一个无法用基于角色的 <method-permissions> 元素定义的安全检查。
例 2.1. ejb-jar.xml 描述符文件片段
<!-- A sample ejb-jar.xml fragment --> <ejb-jar> <enterprise-beans> <session> <ejb-name>ASessionBean</ejb-name> ... <security-role-ref> <role-name>TheRoleICheck<role-name> <role-link>TheApplicationRole</role-link> </security-role-ref> </session> </enterprise-beans> ... </ejb-jar>
注意
这个片段只是一个例子而已。在部署里,这个部分里的元素必须包含和 EJB 部署相关的角色名称以及链接。
例 2.2. web.xml 描述符文件片段
<web-app> <servlet> <servlet-name>AServlet</servlet-name> ... <security-role-ref> <role-name>TheServletRole</role-name> <role-link>TheApplicationRole</role-link> </security-role-ref> </servlet> ... </web-app>
2.1.3. 安全标识符
EJB 可以用 <security-identity> 元素指定另外一个 EJB 调用组件上的方法时必须使用的标识符。
图 2.2. J2EE 安全标识符数据模型
调用标识符可以是当前调用者的标识符,也可以是专门的角色。应用程序组装者使用 <security-identity> 元素和 <use-caller-identity> 子元素。这表示对于这个 EJB 进行的方法调用,当前调用者的标识符应该作为安全标识符传播。在没有显性的 <security-identity> 元素声明时,默认会进行调用者标识符的传播。
或者,应用程序组装者可以使用 <run-as> 或 <role-name> 子元素来指定 <role-name> 元素值提供的专有安全角色必须用作 EJB 进行方法调用的安全标识符。
请注意,这并没有修改
EJBContext.getCallerPrincipal()
方法所看到的调用者的标识符。而调用者的安全角色被设置为 <run-as> 或 <role-name> 元素值指定的单一角色。
<run-as> 元素的一个用法是阻止外部客户访问内部的 EJB。你可以通过为内部 EJB 分配 <method-permission> 元素来配置这种行为,这个元素限制了对从不分配给外部客户的角色的访问。而反过来使用内部 EJB 的 EJB 必须用和受限角色相等的 <run-as> 或 <role-name> 进行配置。下面的描述符文件片段是一个<security-identity> 元素的用法示例。
<ejb-jar> <enterprise-beans> <session> <ejb-name>ASessionBean</ejb-name> <!-- ... --> <security-identity> <use-caller-identity/> </security-identity> </session> <session> <ejb-name>RunAsBean</ejb-name> <!-- ... --> <security-identity> <run-as> <description>A private internal role</description> <role-name>InternalRole</role-name> </run-as> </security-identity> </session> </enterprise-beans> <!-- ... --> </ejb-jar>
当你使用 <run-as> 来分配专门的角色给转出调用时,名为
anonymous
的 principal 将分配给所有的转出调用。如果你想用其他的 principal 关联这个调用,你必须将 <run-as-principal> 和 jboss.xml
文件里的 bean 进行关联。下列代码片段将名为 internal
的 principal 和前面例子里的 RunAsBean
进行关联。
<session> <ejb-name>RunAsBean</ejb-name> <security-identity> <run-as-principal>internal</run-as-principal> </security-identity> </session>
<run-as> 元素在
web.xml
文件的 servlet 定义里也是可用的。下面的例子展示了如何将角色 InternalRole
分配给 servlet:
<servlet> <servlet-name>AServlet</servlet-name> <!-- ... --> <run-as> <role-name>InternalRole</role-name> </run-as> </servlet>
这个 servlet 里的调用和 ‘anonymous’
principal
相关联。jboss-web.xml
文件里的 <run-as-principal> 元素用于和 run-as
角色一起使用的专有 principal。下列代码片段展示了如何将名为 internal
的 principal 关联至上面的 servlet。
<servlet> <servlet-name>AServlet</servlet-name> <run-as-principal>internal</run-as-principal> </servlet>
2.1.4. 安全角色
security-role-ref
或 security-identity
引用的安全角色名称需要映射到应用程序所声明的其中一个角色。应用程序组装者通过声明 security-role
来定义安全角色。role-name
值是一个逻辑的应用程序角色名,如 Administrator、Architect、alesManager 等等。
J2EE 规格认为将部署描述符里的安全角色用于定义应用程序的逻辑安全视图是很重要的。在 J2EE 部署描述符里定义的角色不应该和用户组、用户、Principal 以及其他存在于目标企业操作环境里的概念混淆。部署描述符角色是带有应用程序专有域名称的应用程序构件。例如,banking 程序可能使用角色名如 BankManager、Teller 或 Customer。
在 JBoss EAP 里,
security-role
元素只被用来映射 security-role-ref/role-name
值到组件角色引用的逻辑角色。用户的角色是应用程序的安全管理者动态分配的。JBoss 不要求定义 security-role
元素来声明方法权限。然而,我们仍然推荐使用 security-role
元素以确保跨应用服务器的可移植性且便于部署描述符的维护。
例 2.3. 解释 security-role 元素用法的 ejb-jar.xml 描述符片段。
<!-- A sample ejb-jar.xml fragment --><ejb-jar><assembly-descriptor><security-role><description>The single application role</description><role-name>TheApplicationRole</role-name></security-role></assembly-descriptor></ejb-jar>
例 2.4. 解释 security-role 元素用法的 web.xml 描述符片段。
<!-- A sample web.xml fragment --><web-app><security-role><description>The single application role</description><role-name>TheApplicationRole</role-name></security-role></web-app>
2.1.5. EJB 方法权限
应用程序组装者可以通过 method-permission 元素声明来设置被允许调用 EJB 的 home 和 remote 接口方法的角色。
图 2.3. J2EE 的 method-permission 元素
每个
method-permission
元素都包含一个或多个定义逻辑角色的 role-name 子元素,这些逻辑角色可以访问 method-child 元素确定的 EJB 方法。你也可以指定 unchecked
而不是 role-name
元素来声明任何验证用户都可以访问 method-child 元素确定的 EJB 方法。此外,你可以声明无人可以访问具有 exclude-list
元素的方法。如果 EJB 具有没有用 method-permission
元素声明可以访问的方法,那 EJB 方法默认是不能被使用的。这等同于将方法默认放入 exclude-list
元素里。
图 2.4. J2EE 方法元素
方法元素声明支持三种风格。
第一种用来引用 EJB 的所有的 home 和 component 接口方法:
<method><ejb-name>EJBNAME</ejb-name><method-name>*</method-name></method>
第二种用来引用 EJB 的指定的 home 和 component 接口方法:
<method><ejb-name>EJBNAME</ejb-name><method-name>METHOD</method-name></method>
如果有多个方法具有相同的重载名称,这种方法将引用所有重载的方法。
第三种风格用来引用具有重载名称的一系列方法里指定方法:
<method><ejb-name>EJBNAME</ejb-name><method-name>METHOD</method-name><method-params><method-param>PARAMETER_1</method-param><!-- ... --><method-param>PARAMETER_N</method-param></method-params></method>
这个方法必须在指定的 EJB 的 home 或 remote 接口里进行定义。method-param 元素的值是对应的方法参数类型的全限定名称。如果有多个方法具有相同的重载签名,这些权限将应用到所有匹配的重载方法上。
可选的
method-intf
元素可以用来区分 EJB 里在 home 和 remote 接口都定义了且具有相同名称和签名的方法。
例 2.5 “解释 method-permission 元素用法的 ejb-jar.xml 描述符片段。” 提供了关于
method-permission
用法的完整示例。
例 2.5. 解释 method-permission 元素用法的 ejb-jar.xml 描述符片段。
<ejb-jar><assembly-descriptor><method-permission><description>The employee and temp-employee roles may access any method of the EmployeeService bean </description><role-name>employee</role-name><role-name>temp-employee</role-name><method><ejb-name>EmployeeService</ejb-name><method-name>*</method-name></method></method-permission><method-permission><description>The employee role may access the findByPrimaryKey, getEmployeeInfo, and the updateEmployeeInfo(String) method of the AardvarkPayroll bean </description><role-name>employee</role-name><method><ejb-name>AardvarkPayroll</ejb-name><method-name>findByPrimaryKey</method-name></method><method><ejb-name>AardvarkPayroll</ejb-name><method-name>getEmployeeInfo</method-name></method><method><ejb-name>AardvarkPayroll</ejb-name><method-name>updateEmployeeInfo</method-name><method-params><method-param>java.lang.String</method-param></method-params></method></method-permission><method-permission><description>The admin role may access any method of the EmployeeServiceAdmin bean </description><role-name>admin</role-name><method><ejb-name>EmployeeServiceAdmin</ejb-name><method-name>*</method-name></method></method-permission><method-permission><description>Any authenticated user may access any method of the EmployeeServiceHelp bean</description><unchecked/><method><ejb-name>EmployeeServiceHelp</ejb-name><method-name>*</method-name></method></method-permission><exclude-list><description>No fireTheCTO methods of the EmployeeFiring bean may be used in this deployment</description><method><ejb-name>EmployeeFiring</ejb-name><method-name>fireTheCTO</method-name></method></exclude-list></assembly-descriptor></ejb-jar>
2.1.6. EJB 的安全性注解
EJB 使用注解将信息传递给负责应用程序的安全性和其他方面的部署者。部署者可以根据注解或部署描述符里的设置来设立合适的 EJB 安全策略。
在部署描述符里显性指定的任何方法值都会覆盖注解的值。如果在部署描述符里没有指定某个方法的值,那注解的值将被使用。覆盖的颗粒度是以方法为基础的。
EJB 里定义安全性的注解包括:
@DeclareRoles
- 它指定了代码里声明每个安全角色。关于配置角色的信息,请参考 Java EE 5 TutorialDeclaring Security Roles Using Annotations。
@RolesAllowed
、@PermitAll
和@DenyAll
- 它指定注解的方法权限。关于配置注解的方法权限的信息,请参考 Java EE 5 TutorialSpecifying Method Permissions Using Annotations。
@RunAs
- 它配置组件的传播安全标识符。关于使用注解配置传播安全标识符的信息,请参考 Java EE 5 TutorialConfiguring a Component’s Propagated Security Identity。
2.1.7. Web 内容安全约束
在 Web 应用程序里,安全性是通过角色来定义的。这些角色根据确定被保护内容的 URL 模式来设置访问权限。这一系列信息是使用
web.xml
的 security-constraint 元素来声明的。
图 2.5. Web 内容安全约束
要保护的内容是使用一个或多个 <web-resource-collection> 元素来声明的。每个 <web-resource-collection> 元素都包含一个可选的 <url-pattern> 元素序列以及一个 <http-method> 元素序列。<url-pattern> 元素指定访问受保护内容的请求必须匹配的 的 URL 模式,而 <http-method> 元素则指定所允许的 HTTP 请求类型。
可选的 <user-data-constraint> 元素指定了对客户到服务器连接的传输层的要求。这些要求可能用于内容的完整性(防止数据在通讯过程中被篡改)或用于保密性(在传输时防止被读取)。<transport-guarantee> 元素指定了保护客户和服务器间通讯的程度。它的值是
NONE
、INTEGRAL
和 CONFIDENTIAL
。NONE
表示应用程序不要求任何传输保证。INTEGRAL
表示应用程序要求客户和服务器间发送的数据以传输中不可以更改的方式进行。CONFIDENTIAL
表示应用程序要求以防止其他实体观察传输内容的方式进行传输。大多数情况下,INTEGRAL
或 CONFIDENTIAL
标记的出现都表示要求使用 SSL。
可选的 <login-config> 元素被用来配置应该使用的验证方法、安全区的名称以及表单登录机制所需的属性。
图 2.6. Web 登录配置
<auth-method> 子元素指定了 Web 应用程序的验证机制。作为访问任何被授权约束保护的 web 资源的前提条件,用户必须使用配置机制进行验证。合法的 <auth-method> 值为
BASIC
、DIGEST
、FORM
和 CLIENT-CERT
。 <realm-name> 子元素指定了用于 HTTP 基本和摘要模式授权的安全区名。<form-login-config> 子元素指定了用表单登录的登录页面的错误页面。如果 <auth-method> 值不是FORM
,那么 form-login-config
及其子元素将被忽略。
下面的配置示例指定了 Web 应用程序的
/restricted
路径下的任何 URL 都要求 AuthorizedUser
角色。这里不要求传输保证,用于获取用户标识符的验证方式是基本(BASIC)HTTP 验证模式。
例 2.6. web.xml 描述符文件的片段
<web-app> <security-constraint> <web-resource-collection> <web-resource-name>Secure Content</web-resource-name> <url-pattern>/restricted/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>AuthorizedUser</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>The Restricted Zone</realm-name> </login-config> <!-- ... --> <security-role> <description>The role required to access restricted content </description> <role-name>AuthorizedUser</role-name> </security-role> </web-app>
2.1.8. 启用基于表单的验证
基于表单的验证提供了使用自定义 JSP/HTML 页面的灵活性,它可以定义登录页面、单独的在登录出错时重定向的页面。
基于表单的验证是在部署描述符
web.xml
的 <login-config> 元素里包含 <auth-method>FORM</auth-method>
来实现的。登录和错误页面也在 <login-config> 里定义:
<login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.html</form-login-page> <form-error-page>/error.html</form-error-page> </form-login-config> </login-config>
当使用表单验证的 web 应用程序被部署时,Web 容器将使用
FormAuthenticator
把用户引到合适的页面。JBoss EAP 维护了一个会话池,所以并不需要为每个请求准备验证信息。当 FormAuthenticator
接收到一个请求时,它会向 org.apache.catalina.session.Manager
查询现有的会话。如果没有会话存在,新的会话将被创建。FormAuthenticator
然后再检验这个会话的凭证。
注意
每个会话都用 Session ID 来标识,这是一个随机生成的 16 个字节的字符串。在默认情况下,这些值是从
/dev/urandom
(Linux) 获取并用 MD5 进行哈希加密的。对 Sesion ID 生成进行检查可以确保 ID 的唯一性。
在经过检验后,这个 Session ID 将作为 cookie 的一部分进行分配,然后返回给客户。这个 cookie 将在随后的客户请求里出现并用于确认用户会话。
传递给客户的 cookie 是一个带有几个可选属性的名称/值对。标识符属性名为
JSESSIONID
。它的值是 Session ID 的十六进制字符串。这个 cookie 被配置成非持久性的。这意味着当浏览器退出时它不能在客户端删除。在服务器端,会话将在 60 秒不活动后过期,此时会话对象和凭证信息会被删除。
假设某个用户试图访问用基于表单验证保护的 Web 应用程序。
FormAuthenticator
将捕获这个请求,如果有需要则创建一个新的会话,并重定向用户到 login-config
里定义的登录页面。(在之前的代码示例里,登录页面是 login.html
)。然后这个用户在 HTML 表单里输入用户名和密码。用户名和密码将通过 j_security_check
表单动作传入 FormAuthenticator
。
FormAuthenticator
然后根据附加到 Web 应用程序上下文的安全区验证用户名和密码。在 JBoss EAP 里,安全区是 JBossWebRealm
。当验证成功后,FormAuthenticator
从缓存里提取保存的请求并将用户重定向到原始的请求。
注意
服务器只有在 URI 以
/j_security_check
结尾,且至少存在 j_username
和 j_password
参数时才会识别表单验证请求。
2.1.9. 启用声明式安全性
到目前为止涵盖的 Java EE 安全性元素只是从应用程序的角度描述安全性要求。因为 Java EE 安全性元素声明了逻辑角色,应用程序部署者将这些角色从应用程序域映射到部署环境。Java EE 规格忽略了这些应用服务器专有的细节。
要将应用程序角色映射到部署环境,你必须用 JBoss EAP 专有的部署描述符来指定实现 Java EE 安全模型的安全管理者。关于相关的安全性配置细节,请参考自定义登录模块示例。