J2EE 中的安全第一部分
现在越来越多的企业应用构建在j2ee平台上,这得益于j2ee为企业应用的开发提供了良好的框架和服务的支持.j2ee为企业应用提供了多方面的服务
(Security、Transaction、Naming等).本文将介绍j2ee提供的安全服务.作者首先介绍j2ee中的安全概念和j2ee的安全
体系架构.然后结合具体的实例向读者展示如何在自己的程序中应用j2ee提供的安全特性。
一.简介
现
在越来越多的企业应用构建在j2ee平台上,这得益于j2ee为企业应用的开发提供了良好的框架和服务的支持.j2ee为企业应用提供了多方面的服务
(Security、Transaction、Naming等).本文将介绍j2ee提供的安全服务.作者首先介绍j2ee中的安全概念和j2ee的安全
体系架构.然后结合具体的实例向读者展示如何在自己的程序中应用j2ee提供的安全特性。本文所介绍的内容是基于j2ee1.3版本的。
二.j2ee中的安全概念
主体(Principal):主
体(Principal)是被在企业安全服务验证了的实体。主体(Principal)用主体名作为它的标识,通过与主体相关的验证数据进行验证。通常情
况下主体名就是用户的登陆名,验证数据就是登陆的密码。J2EE规范中并没有限定J2EE
产品提供商使用怎样的认证方法,因此主体名和验证数据的内容和格式依不同的认证协议而不同。
安全策略域(Security Policy Domain):也
称安全域(security domain)或
realm,它是一个逻辑范围或区域,在这一范围或区域中安全服务的管理员定义和实施通用的安全策略。它是从安全策略的角度划分的区域。比如可以将企业应
用系统划分为企业员工、供应商、合作伙伴等不同的安全域,对这些安全区域采用不同的安全策略。
安全技术域(Security Technology Domain):它是从安全技术的角度划分的区域,在一个安全技术域中使用同样的安全机制来执行安全策略。一个安全技术域可以包括多个安全策略域。
安全属性(Security Attributes):每
个主体(Principal)都有一系列与之相关的安全属性。安全属性可用来访问被保护的资源,检查用户的身份和完成其他一些安全相关的用途。J2EE产
品提供商或具体的验证服务的实现来决定怎样将安全属性与一个主体联系起来。J2EE规范并没有限定什么样的安全属性将与主体相联系。
凭证(Credential):凭证包含或引用为J2EE 系统验证一个主体的验证信息(安全属性)。如果成功的通过了验证,主体将获得一个包括安全属性的凭证。如果被允许的话,一个主体也可能获取另一个主体的凭证。在这种情况下两个主体在同一安全域中具有相同的安全属性。
三.j2ee的安全体系结构
1. 基于容器的安全
在j2ee
的环境中,组件的安全是由他们各自的容器来负责的,组件的开发人员几乎可以不用或者很少在组件中添加有关安全的代码。这种安全逻辑和业务逻辑相对独立的架
构,使得企业级应用系统有更好的灵活性和扩展性。J2ee规范要求j2ee
产品必须为应用程序开发者提供两种形式的基于容器的安全性-说明性的安全性和可编程的安全性。
a. 说明性的安全性
说
明性的安全性通过安全结构描述的方式来代表应用程序的安全需求,安全结构一般包括安全角色,访问控制和验证要求等。在j2ee平台中部署描述符充当了说明
的安全性的主要工具。部署描述符是组件开发者和应用程序部署者或应用程序组装者之间的交流工具。应用程序的开发者用它来表示应用中的安全需求,应用程序部
署者或应用程序组装者将安全角色与部署环境中的用户和组映射起来。
在程序运行时容器从部署描述符中提取出相应的安全策略,然后容器根据安全策略执行安全验证。说明的安全性不需要开发人员编写任何安全相关的代码,一切都是通过配置部署描述符来完成的。
b. 可编程的安全性
可
编程的安全性在说明性的安全性的基础上,使安全敏感的应用可以通过调用被容器提供的API来对安全作出决断。这在说明性的安全性不足以满足企业的安全模型
的情况是非常有用的。J2ee在EJB EjbConext interface和servlet HttpServletRequest
interface中各提供两个方法:
isCallerInRole (EJBContext) getCallerPrincipal (EJBContext) isUserInRole (HttpServletRequest) getUserPrincipal (HttpServletRequest)
|
这些方法允许组件根据调用者或远程用户的安全角色来作出商业判断。在文章的后面部分将有这些方法的详细介绍和例程,以便读者更好的理解可编程的安全性的用途。
2.J2ee的验证模型
身份验证是用户或组件调用者向系统证明其身份的过程。用户通过某种方式向系统提交验证信息(通常是用户名和密码或者是用户的数字证书),系统用用户提供的验证信息和系统的安全策略来验证用户的身份。
图一 初始验证过程
图一 初始验证过程
图二 验证URL
图三 验证EJB方法调用
用户的验证
用户的验证根据其客户端类型不同分为两种:Web 客户端的验证和Application客户端的验证
a. Web 客户端的验证
Web
客户端通常通过http协议来请求web服务器端的资源,这些web资源通常包括html网页、jsp(java server
page)文件、java
servlet和其他一些二进制或多媒体文件。在企业环境中,企业的某些资源往往要求只允许某些人访问,有些资源甚至是机密的或安全敏感的。因此对企业中
各种web资源进行访问控制是十分必要的。为了满足企业中的不同安全级别和客户化的需求,j2ee提供了三种基于web客户端的验证方式:
HTTP基本验证(HTTP Basic Authentication)
HTTP基本验证
是HTTP协议所支持的验证机制。这种验证机制使用用户的用户名和密码作为验证信息。Web客户端从用户获取用户名和密码,然后传递他们给web服务器,
web服务器在指定的区域(realm)中验证用户。但需要注意的是,这种验证方法是不够安全的。因为这种验证方法并不对用户密码进行加密,而只是对密码
进行基本的base64的编码。而且目标web服务器对用户来说也是非验证过的。不能保证用户访问到的web服务器就是用户希望访问的。可以采用一些安全
措施来克服这个弱点。例如在传输层上应用SSL或者在网络层上使用IPSEC或VPN技术。
基于表单的验证(Form-Based Authentication)
基于表单的验证
使系统开发者可以自定义用户的登陆页面和报错页面。这种验证方法与基本HTTP的验证方法的唯一区别就在于它可以根据用户的要求制定登陆和出错页面。基于
表单的验证方法同样具有与基本HTTP验证类似的不安全的弱点。用户在表单中填写用户名和密码,而后密码以明文形式在网路中传递,如果在网路的某一节点将
此验证请求截获,在经过反编码很容易就可以获取用户的密码。因此在使用基本HTTP的验证方式和基于表单的验证方法时,一定确定这两种方式的弱点对你的应
用是可接受的。
基于客户端证书的验证(Client-Certificate Authentication)
基于客户端证书的验证方式要比上面两种方式更安全。它通过HTTPS(HTTP over SSL)来保证验证的安全性。安全套接层(Secure
Sockets
Layer)为验证过程提供了数据加密,服务器端认证,信息真实性等方面的安全保证。在此验证方式中,客户端必须提供一个公钥证书,你可以把这个公钥证书
看作是你的数字护照。公钥证书也称数字证书,它是被称作证书授权机构(CA)-一个被信任的组织颁发的。这个数字证书必须符合X509公钥体系结构
(PKI)的标准。如果你指定了这种验证方式,Web服务器将使用客户端提供的数字证书来验证用户的身份。
b. 应用程序客户端的验证(Application Client User Authentication)
java客户端程序是执行在用户本地java虚拟机上的java程序,它拥有main方法,通常由用户可通过java.exe或javaw.exe直接启
动执行。J2ee应用程序客户端与java客户端程序相似,也拥有main方法,但他们在运行时存在一定的差别。J2ee应用程序客户端和其他j2ee组
件一样运行在自己的容器中。用户通过容器来执行J2ee应用程序客户端。这样J2ee应用程序客户端容器就有机会在J2ee应用程序客户端被执行之前完成
用户身份的验证。J2ee提供了一种可自定义的方式来获取用户的验证信息。可以选择使用容器提供的缺省的方式来获取j2ee应用客户端程序的用户的验证信
息,也可以选择自定义的方式来获取用户的验证信息。当选择自定义方式时,应用程序开发者必须提供一个实现了
javax.security.auth.callback.CallbackHandler
interfce的类,并且在j2ee部署描述文件application-client.xml中的元素callback-handler中加入这个类
的类名。这样,当系统需要验证用户身份时,客户端程序的容器将部署描述文件中的CallbackHandler实现类的类名传递给系统的登陆模块(验证模
块),登陆模块再实例化这个实现类。这个类的实例负责收集用户验证信息,并将收集到的用户验证信息传递给登陆模块,登陆模块用这些验证信息来验证用户。这
个实现类可以是具有用户界面的,或是通过要求用户输入来收集用户验证信息,也可以是通过命令行来获取用户验证信息,还可能是通过读取本地或在线的用户证书
库来获取用户的电子证书。选取哪种方式取决于验证信息的存储方式。
有些j2ee产品厂商把容器的验证服务和本地系统的验证服务或其他应用系统产品的验证服务集成起来,从而在一定的应用系统的范围内实现单点登陆的能力。
单点登陆 (Single Sign-On)
单点登从用户的视角是指用户在特定的逻辑安全区域中,只需进行一次登陆即可在访问在此逻辑安全区域中不同应用系统中的被授权的资源,只有超越了安全区域边
缘时才要求再次登陆。这种能力对多种IT应用系统共存的企业显得尤为有价值。随着企业信息化建设程度的不断提高,企业中的应用系统也越来越多。在传统的应
用系统中,各系统各自维护自己的安全策略,这些安全策略典型的包括组织结构定义,安全角色定义,用户身份验证,资源访问控制等。由于各系统互相独立,一个
用户在使用每一应用系统之前,都必须按照相应的系统身份进行系统登陆。这对于用户来说必须记住每一个系统的用户名和密码,给用户带来了不小的麻烦。针对于
这种情况,单点登陆的概念随之产生,并不断的应用到企业的应用系统的集成当中。J2ee1.3也在规范中建议j2ee产品应为应用系统提供单点登陆的能
力。但j2ee1.3规范并没有规定j2ee产品应遵循何种标准,因此不同的厂商的产品在单点登陆上的实现和应用各不相同。有的j2ee产品实现了在本产
品环境范围内的单点登陆,有的实现了特定系统环境之间的单点登陆(如IBM WebSphere Application 4.0 AE
实现了WebSphere Application Server与WebSphere Application Server、WebSphere
Application Server与Lotus Domino server、WebSphere Application
Server与Lotus Domino
server之间的单点登陆能力)。在j2ee中单点登陆是通过传递凭证(Credential)来实现的.当用户进行系统登陆时,客户端容器(包括
WEB客户端和应用程序客户端)根据用户的凭证(Credential)为用户建立一个安全上下文(security
Context),安全上下文包含用于验证用户的安全信息,系统用这个安全上下文和安全策略来判断用户是否有访问系统资源的权限。遗憾的时j2ee规范并
没有规定安全上下文的格式,因此不能在不同厂商的j2ee产品之间传递安全上下文。到目前为止还很少有在不同的j2ee产品间互相共享安全上下文,因此在
不同j2ee产品间实现单点登陆只能通过第三方产品(如LDAP server等)集成的方式。
惰性验证(Lazy Authentication)
身份验是有代价的。例如,一次验证过程也许包括多次通过网络信息交换。因此惰性验证就非常有用了。惰性验证使当用户访问受保护的资源时才执行验证过程,而不是在用户第一次发起请求时就执行验证过程。
3. J2ee的授权模型
代码授权(Code Authorization)
j2ee产品通过java 2 安全模型来限制特定J2SE的类和方法的执行,以保护和确保操作系统的安全。详细描述请参阅《J2SE规范文档》。
调用者授权(Caller Authorization)
安全角色:安全角色是具有相同安全属性的逻辑组。它由应用程序的装配者(Application Assembler)或应用程序的部署者(Application Deployer)分配的。
安全角色引用:安全角色引用是应用程序提供者(Application Provider)用来引用安全角色的标识。应用程序提供者(Application Provider)可以用安全角色引用来为安全角色分配资源访问的权限。也在安全相关的程序代码中引用安全角色。
用户和组:用户和组是在实际系统环境下的用户和用户的集合。它们对应者现实当中的人和群体。
访问控制:访问控制可以确保安全角色只能访问已授予它安全权限的授权对象。授权对象包括EJB的远程方法、web资源(html网页,jsp/servlet和多媒体或二进制文件)等。在j2ee中访问控制在应用程序描述文件中与安全角色关联起来。
映射:通过映射应用程序的系统管理员将实际系统环境中的用户和角色与安全角色联系起来,从而是实际的用户拥有对企业资源访问的适当授权。
被传播的调用者身份标识(Propagated Caller Identities)
在j2ee
1.3中可以选择用传播调用者标识作为web组件和ejb组件调用者的标识来进行验证。在这种方式下,整个ejb组件的调用链中interface
EJBContext的方法getCallerPrincipal返回相同的主体名(principal
name)。如果调用链中的第一个ejb是被jsp/servlet调用的,interface
EJBContext的方法getCallerPrincipal返回的主体名(principal name)应与interface
HttpServletRequest的方法getUserPrincipal的返回值相同。要注意的是在调用链中传递的是用户的标识,而不是凭证
(credentials),这一点非常重要,因为在调用链的每个节点上用户可能使用不同的安全属性。
Run As Identities
J2ee 1.3中提供了允许组件开发者和部署这来指定组件以什么身份运行的方法。符合j2ee1.3规范的产品会提供将组件设置成Run As
Identities方式的方法。如果Run As Identities方式被选中,在运行中被设置为Run As
Identities的组件的调用者不再是调用链中第一个节点的调用者了,而是在部署时被指定的调用者。而调用链中随后节点的调用者也变为与被设置为
Run As Identities的组件的调用者相同。
图四 用户标识传递
这一部分介绍了j2ee的安全概念,意在使读者能够对j2ee在安全方面有一定的了解,后面还会有应用这些概念的具体例子。
J2EE 中的安全第二部分--j2ee安全应用
在本系列文章的第一部分作者介绍了j2ee的安全概念、验证模型和授权模型,这部分更偏重于理论的介绍。本文的第二部分作者将通过具体的例子向读者展示如何在开发中应用j2ee提供的安全服务。本部分的重点在于应用与实践。
注
释:本文的目的是介绍如何应用j2ee提供的安全服务,而并不针对特定的产品。因此作者选择sun的
j2ee参考实现(j2sdkee)作为演示平台。因为j2sdkee是完全遵照j2ee规范开发的,虽然它不像IBM WebSphere 、BEA
WebLogic等j2ee产品那么产品化和商业化,但它绝对是学习j2ee的理想平台。你可以通过 http://java.sun.com/j2ee/获取sun 的j2ee参考实现的最新版本。本文选择的是Sun的j2sdkee1.3.1。
本文将包括以下内容:
- 一个采用HTTP基本的验证的例子
- 一个采用基于表单的验证的例子
- 一个ejb方法授权的例子
- 一个可编程安全性和传播调用者身份标识的例子
采用HTTP基本的验证的例子
http基本验证是Web客户端验证的一种,它和系统的授权机制一起控制受保护资源的访问。
步骤:
1. 创建一个j2ee应用
在应用程序部署工具的File菜单选中New子菜单中的Application菜单项(见图1)。会弹出新建应用程序对话框。填写应用程序文件名和应用程序显示名(见图2)。
图1
图2
2. 创建一个web组件
在
应用程序部署工具的File菜单选中New子菜单中的Web Compent菜单项,会弹出新建web组件向导对话框(见图3)。选择Create
New WAR File in Application,在下拉框中选择步骤1创建的应用test,在WAR Display
Name框中填写WebAppTest.点击Content栏中
的Eidt按钮选择此Web组件包含的文件。在这个例子中只有一个webtest.jsp文件。然后点击Next,进入下一个对话框(见图4)。由于我们
的web组件是一个jsp文件,因此在组件类型中选择JSP。然后一直按Next直到结束。此时我们已经创建了一个只包含一个jsp文件的web组件。接
下来是配置安全属性的步骤。
图3
图4
3. 配置安全属性
3.1创建安全角色
在部署工具的左导航栏中点中步骤2创建的web组件WebAppTest,在右边的属性页中选择Roles属性页(见图5)。点击Add按钮,在Name栏中填写安全角色名user,Description栏填写描述信息。安全角色代表具有相同安全权限用户的集合。
图5
3.2 配置安全策略
创建了安全角色后,应该对安全角色配置相应的安全策略。点击Security属性页(见图6)。
图6
首
先选择你想用的验证方式,从User Authentication
Method下拉框中选择Basic。这意味着你将通过基本的HTTP验证方式验证用户。下面我们进行web资源的授权。点击Security
Constraint栏中的Add按钮添加一条安全约束,约束名可以自定。接下来对创建好的约束添加Web资源。首先在Web Resource
Collections中添加资源集合,然后选取资源集合包含的资源。此例中WRCollection资源集合中包含webtest.jsp文件,也可以
包含各种属于这个web组件的文件。接下来选择哪些web操作要收到约束,j2sdkee1.3.1中只包含两种操作(GET和POST),不同的产品支
持的操作有所不同,在开发是应结合具体产品提供的操作来选取。现在应该指定安全角色了,点击Authorized
Roles栏中的Edit按钮,会弹出安全角色列表对话框,从中选取已定义的安全角色。本例中选择user。至此安全策略已经配置完毕,下面的步骤是将实
际环境中的用户和用户组映射与安全角色进行映射。
4. 映射
在
左导航栏中选中应用程序test在右边选择Security属性页(见图7),在Role Name
Reference栏中选中user,点击正下方的Add按钮,会弹出用户和用户组列表对话框,从中选择要映射成安全角色user的用户或组。此例中我们
将用户j2ee映射为安全角色user。这样用户J2ee将具有为安全角色user分配的访问授权。
图7
5. 部署应用
选中Web Context属性页,在Context Root文本框中填写test,右键点击左导航栏的应用test,在弹出菜单中选择deploy完成应用程序的发布。至此我们完成了第一个例子的全部步骤。
部署描述文件
这个例子使用了说明性的安全服务,因此我们不需要编写任何的安全相关的代码,而是完全通过配置组件的部署描述文件来实现的。下面是这个web组件的部署描述文件。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'> <web-app> <display-name>WebAppTest</display-name> //Web组件名称 <servlet> <servlet-name>webtest</servlet-name> <display-name>webtest</display-name> <jsp-file>/webtest.jsp</jsp-file> //组件中包含的jsp文件 </servlet> <session-config> <session-timeout>30</session-timeout> </session-config> <security-constraint> //安全约束部分 <web-resource-collection> //受约束的web资源集 <web-resource-name>WRCollection</web-resource-name> //资源集名 <url-pattern>/webtest.jsp</url-pattern> //资源的url表达式 <http-method>GET</http-method> //受约束的资源操作方法 <http-method>POST</http-method> </web-resource-collection> <auth-constraint> //对安全角色授权 <role-name>user</role-name> //安全角色名 </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> //验证方式设置 <auth-method>BASIC</auth-method> //使用基本的HTTP验证方式 <realm-name></realm-name> </login-config> <security-role> //定义安全角色 <description>this is a user</description> <role-name>user</role-name> </security-role> </web-app>
|
从部署描述文件可以知道这是
一个名为WebAppTest的web组件,包含一个名为webtest.jsp的文件,只有被赋予user安全角色的用户或用户组才有权对
webtest.jsp进行GET或POST操作。这里并没有包含安全角色对实际用户的映射,j2ee部署描述文件的DTD中并没有定义用于安全角色和实
际用户的映射的元素,因为实际环境中有多种不同的用户系统(如关系数据库,系统文件形式和LDAP系统等)。因此安全角色和实际用户的映射方式是由
j2ee产品厂商制定的。
测试运行结果
打
开ie,在导航栏输入http://localhost:8000/test/webtest.jsp回车,会弹出验证对话框,要求用户提供用户名和密码
(见图8),输入用户名j2ee和密码j2ee。通过用户验证后执行jsp文件,webtest.jsp打印出"hello!"(见图9)。
图8
图9
注释:在第一个例子中已经详细的描述了各个步骤,在接下来的例子中会有一些与第一个例子相同的操作,因此对下面的例子只描述与第一个例子不同的步骤。
基于表单的验证的例子
基于表单的验证与基本的HTTP验证的唯一区别是基本的HTTP验证用浏览器提供的验证信息对话框收集用户验证信息,而基于表单的验证允许自定义登陆页面来收集用户验证信息。本例子与第一个例子的步骤基本相同,不同的地方在于此例子要提供登陆页面和出错页面。
登陆页面login.html
<form method="POST" action="j_security_check"> <input type=text name="j_username"> <input type=password name="j_password"> <input type=submit name="login" value="login"> </form>
|
此文件有几个地方值得注意:
- Action的值必须为"j_security_check"
- 获取用户名的域名必须是"j_username"
- 获取用户密码的域必须是" j_password"
出错页面 error.html
出错页面只是简单的显示出错信息。
配置基于表单的验证
首先将login.html和error.html加入到WebAppTest组件中。
然后见图10选择Security属性页,在User Authentication Method下拉框中选择Form
Based选项。点击Settings…弹出用户验证设置对话框,在Login Page下拉框选login.html,在Error
Page下拉框选error.html。
图10
重新部署应用,再一次访问http://localhost:8000/test/webtest.jsp 会出现login页面(见图11),如果用户名或密码错误,error.html将显示给用户。
图11
部署描述文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'> <web-app> <display-name>WebAppTest</display-name> . . . <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> //使用基于表单的验证方式 <realm-name>Default</realm-name> //使用缺省的安全域 <form-login-config> <form-login-page>/login.html</form-login-page> //定义登陆页面 <form-error-page>/error.html</form-error-page> //定义出错页面 </form-login-config> </login-config> <security-role> <description>this is a user</description> <role-name>user</role-name> </security-role> </web-app>
|
ejb方法授权的例子
从j2ee1.3
开始便提供了对ejb的方法进行授权的安全服务,这种授权服务由ejb容器实现。当调用者调用ejb的方法时,ejb容器用调用者的身份来查找授予此调用
者的访问权限条目,如果调用者调用的方法属于授权条目,那么ejb容器调用方法。否则,ejb容器拒绝调用此方法,并向调用者返回拒绝访问异常。可以对远
程方法和home接口方法进行授权。本例中我们将对一个远程方法和一个home接口方法进行授权。
首先创建一个session bean CountEjb 远程接口 Count.java import javax.ejb.*; import java.rmi.RemoteException;
public interface Count extends EJBObject {
/** * 远程方法count */ public int count() throws RemoteException; }
Home接口 CountHome.java import javax.ejb.*; import java.rmi.RemoteException;
/** * This is the home interface for CountBean. * One create() method is in this Home Interface, which * corresponds to the ejbCreate() method in the CountBean file. */ public interface CountHome extends EJBHome {
/* * This method creates the EJB Object. * * @param val Value to initialize counter to * * @return The newly created EJB Object. */ Count create(int val) throws RemoteException, CreateException; }
实现类 CountBean.java import javax.ejb.*; import java.security.Principal;
/** public class CountBean implements SessionBean { // The current counter is our conversational state. public int val; private SessionContext sessionCtx; // // 远程商业方法实现 public int count() { System.out.println("count()"); return ++val; }
// // home接口Create方法的实现 //
public void ejbCreate(int val) throws CreateException { this.val = val; System.out.println("ejbCreate()"); }
public void ejbRemove() { System.out.println("ejbRemove()"); }
public void ejbActivate() { System.out.println("ejbActivate()"); }
public void ejbPassivate() { System.out.println("ejbPassivate()"); }
public void setSessionContext(SessionContext ctx) { sessionCtx=ctx; } } 客户端程序 CountClient.java import javax.ejb.*; import javax.naming.*; import java.util.Properties;
/** * This class is a simple example of client code. */ public class CountClient {
public static void main(String[] args) {
try { InitialContext ctx = new InitialContext(); CountHome home = (CountHome) javax.rmi.PortableRemoteObject.narrow( ctx.lookup("java:comp/env/CountHome"), CountHome.class);
int countVal = 0; Count count=null;
/* 创建并执行远程方法 */ System.out.println("Instantiating beans..."); count = home.create(countVal); countVal = count.count();
System.out.println(countVal);
/* remove Count对象 */ count.remove(); } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } } }
|
这个ejb包括一个远程商业方法count(),我们将将此方法授权给某个安全角色。此外还将home接口的Create()方法授权给安全角色。
步骤1
编译以上源程序并用j2sdkee1.3.1的组装发布工具(deploytool.bat)进行组装(如图12)。
图12
步骤2:配置安全角色
在对方法进行授权前,必须创建将被授权的安全角色。创建安全角色的步骤前边已经介绍过了,此处不再重复。本例中我们创建名为admin的安全角色。
步骤3:方法授权
方法授权的过程是确定那些安全角色可以访问特定方法的过程。方法授权一般是应用程序组装或应用程序部署者的责任。他们根据企业特定的需求创建不同的安全角色,并授予这些安全角色特定的访问权限。
用
鼠标选中CountBean,在右端的窗口选择Security属性页(如图13),在Security Identity选项中选择Use
Caller ID,这意味着ejb容器将用方法调用者的身份来验证方法调用权限。
Run As Specified
Role选项将在"传播调用者身份标识的例子"进行介绍。由于在前面创建了admin安全角色,因此你可以看到Method
Permissions栏中出现admin列。首先对远程方法count()进行授权。选择Remote选项,并在count()方法的
Availability列中选择Sel
Roles,然后选中count()方法的admin列。到此为止我们已对远程方法count()进行了授权。接下来对home接口的create()方
法进行授权。在Show栏中选择Remote
Home,剩下的步骤与count()方法授权相同。我们已经将count()方法和create()方法授权给了admin安全角色。但安全角色这是一
个逻辑的集合,并不代表具体的用户或用户组,因此结下来我们要做的就是将安全角色与实际的用户映射起来。
步骤4:角色映射
首先我们需要在我们的j2ee环境中创建一个用户,用户起名为Tony,密码为1。
图13
这
里我们使用用户名和密码的方式进行身份验证。我们在default Realm中创建此用户。可以使用命令行方式:"realmtool -add
Tony 1
eng"。详细的使用方法参见j2sdk1.3.1文档的工具部分。接下来映射安全角色到用户。选中ejb应用CountEjb,在右边窗口中选择
Security属性页(如图14),点击Edit
Roles按钮,选择安全角色admin。再点击Add按钮选择Tony用户。这样已经将安全角色admin和用户Tony映射起来了。
步骤5:部署应用
部署应用到本地机,右键点击ejb应用CountEjb,选择弹出菜单的deploy项,按要求配置各项。
步骤6:创建客户端
创建客户端将客户端程序的主类和其他辅助类打包。创建端将客的过程比较简单,这里就不作描述了。
步骤7:运行程序
现在可以运行客户端程序来验证方法的授权了。通过命令runclient.bat -client客户端jar包文件名 -name 主类名来执行客户端程序。客户端程序的容器将显示一个对话框来提示用户输入用户名和密码(如图15),填写用户名和密码,按OK。
图15
若用户名或密码与授权的方法不符,则会抛出没有权限异常(如图16)。
图16
Countejb的部署描述文件ejb.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
<ejb-jar> <display-name>count</display-name> <enterprise-beans> <session> // CountBean属于session bean <display-name>CountBean</display-name> //ejb组件的显示名 <ejb-name>CountBean</ejb-name> //ejb组件名
<home>CountHome</home> //Home接口 <remote>Count</remote> //远程接口 <ejb-class>CountBean</ejb-class> //实现类 <session-type>Stateful</session-type> // CountBean属于Stateful Bean <transaction-type>Container</transaction-type> //CountBean事务类型为容器管理的 <security-identity> //安全标识 <description></description> <use-caller-identity></use-caller-identity> //CountBean使用调用者的身份标识 </security-identity> </session> </enterprise-beans> <assembly-descriptor> <security-role> <role-name>admin</role-name> //定义安全角色admin </security-role> <method-permission> //将方法count和remove授权给安全角色admin <role-name>admin</role-name>
<method> //方法定义 <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>count</method-name> <method-params /> </method> <method> <ejb-name>CountBean</ejb-name> <method-intf>Home</method-intf> <method-name>remove</method-name> <method-params> <method-param>java.lang.Object</method-param> </method-params> </method> </method-permission> <method-permission> <unchecked /> //不检查以下方法的授权 <method> <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>getHandle</method-name> <method-params /> </method> . . . . <method> <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>getEJBHome</method-name> <method-params /> </method> </method-permission> <container-transaction> // CountBean的事务属性 <method> <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>count</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
|
|
可编程安全性和传播调用者身份标识的例子
此例程包括两个部分,分别演示可编程安全性和调用者身份传播。
可编程安全性例程
可
编程安全性可应用在web层和EJB层,分别是通过javax.servlet.http.HttpServletRequest接口的
isUserInRole ()、getUserPrincipal
()方法和javax.ejb.EJBContext接口的isCallerInRole ()、getCallerPrincipal
()方法来实现的。
public boolean isUserInRole(java.lang.String role)方法
此方法用来判断调用者是否属于某一特定的安全角色,如果属于返回true,否则返回false。
参数role指定某一安全角色。通过此方法开发者可以在程序代码中加入自己的安全逻辑判断,从而增强了J2EE在安全方面的灵活性。
public
java.security.Principal getUserPrincipal()方法
调用此方法可以得到一个java.security.Principal对象,此对象包含了调用者的用户名,通过Principal.getName()
方法可以得到用户名。通过调用getUserPrincipal()方法开发者可以得到调用者的用户名,然后对调用者的用户名进行特定的逻辑判断。
public java.security.Principal getCallerPrincipal()方法 和public boolean isCallerInRole(java.lang.String roleName)方法的作用和方法同上。
下面我们通过例程来演示这些方法的用法
程序清单:
webtest.jsp
<%@page contentType="text/html"%> <html> <head><title>JSP Page</title></head> <body>
<%-- <jsp:useBean id="beanInstanceName" scope="session" class="package.class" /> --%> <%-- <jsp:getProperty name="beanInstanceName" property="propertyName" /> --%> Hello! the caller is <%=request.getUserPrincipal().getName()%><br/> <%--得到调用者的用户名--%> <% if (request.isUserInRole("admin")){%> <%--判断调用者是否属于"admin"安全角色--%> the caller is admin Role; <%} %> </body> </html>
web.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'> <web-app> <display-name>WebApp</display-name> <servlet> <servlet-name>webtest</servlet-name> <display-name>webtest</display-name> <jsp-file>/webtest.jsp</jsp-file> <security-role-ref> <role-name>adminref</role-name> <role-link>admin</role-link> </security-role-ref> <security-role-ref> <role-name>guestref</role-name> <role-link>guest</role-link> </security-role-ref> </servlet> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>webtest.jsp</welcome-file> </welcome-file-list> <security-constraint> <web-resource-collection> <web-resource-name>WRCollection</web-resource-name> <url-pattern>/webtest.jsp</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <role-name>guest</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></realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>guest</role-name> </security-role> </web-app>
|
从web.xml文件的内容可以看出,只有安全角色为"admin"和"guest"的用户才有权对webtest.jsp文件进行POST和GET操作。
运行结果:
创
建一个web应用,将webtest.jsp作为一个web组件,并按照web.xml的内容配置web应用,在运行环境中将用户j2ee分配为
admin安全角色,将用户Tony分配为guest角色。发布web应用到本地j2ee Server上。用ie访问webtest.jsp
如图17用用户j2ee的身份进行验证,用户j2ee属于admin安全角色。显示结果见图18
图17
图18
如果用用户Tony进行验证,结果见图19
图19
ejb中应用可编程的安全性与在web中的方法相似,本文不再进行介绍
传播调用者身份标识例程
本例程将演示调用者身份标识如何在调用链中传递的,并且介绍如何应用"Run As"来实现在调用链中更改调用者的身份。本例将用一个web组件(一个jsp文件)和两个ejb组件来形成一个调用链。
程序清单:
webtest.jsp
<%@page contentType="text/html"%> <%@page import="andy.*"%> <%@page import="javax.naming.*"%> <html> <head><title>JSP Page</title></head> <body>
<%-- <jsp:useBean id="beanInstanceName" scope="session" class="package.class" /> --%> <%-- <jsp:getProperty name="beanInstanceName" property="propertyName" /> --%> Hello! the caller is <%=request.getUserPrincipal().getName()%> <br/> <% if (request.isUserInRole("admin")){%> the caller is admin Role; <%} %> <% try { Context ctx = new InitialContext(); andy.CountHome home = (andy.CountHome)javax.rmi.PortableRemoteObject.narrow( ctx.lookup("java:comp/env/CountHome"), andy.CountHome.class); andy.Count count = home.create(1); count.count(); }catch (Exception e) { e.printStackTrace(); } %> </body> </html>
|
CountBean.java
package andy; import javax.ejb.*; import javax.naming.*; public class CountBean implements SessionBean { public int val; private SessionContext EjbCxt = null; public int count() { int temp = 0; System.out.println("CountBean.count()"); //打印调用者名 System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName()); //判断调用者的安全角色 if(EjbCxt.isCallerInRole("adminref")) // adminref为安全角色admin的引用名 { System.out.println("the caller is admin Role"); } if(EjbCxt.isCallerInRole("guestref")) // guestref为安全角色guest的引用名 { System.out.println("the caller is guest Role"); } if(EjbCxt.isCallerInRole("userref")) // userref为安全角色user的引用名 { System.out.println("the caller is user Role"); } //调用另一个ejb的远程方法 try { Context ctx = new InitialContext(); CountHome1 home = (CountHome1)javax.rmi.PortableRemoteObject.narrow( ctx.lookup("java:comp/env/CountHome1"), CountHome1.class); Count1 count = home.create(1); temp = count.count(); }catch (Exception e) { e.printStackTrace(); } return ++temp; } public void ejbCreate(int val) throws CreateException { this.val = val; } public void ejbRemove() { } public void ejbActivate() { } public void ejbPassivate() { } public void setSessionContext(SessionContext ctx) { EjbCxt = ctx; //获取EjbContext对象 } }
CountBean1.java package andy; import javax.ejb.*;
public class CountBean1 implements SessionBean { public int val; private SessionContext EjbCxt = null; public int count() { System.out.println("CountBean1.count()"); System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName()); if(EjbCxt.isCallerInRole("adminref")) { System.out.println("the caller is admin Role"); } if(EjbCxt.isCallerInRole("guestref")) { System.out.println("the caller is guest Role"); } if(EjbCxt.isCallerInRole("userref")) { System.out.println("the caller is user Role"); } return ++val; } public void ejbCreate(int val) throws CreateException { this.val = val; } public void ejbRemove() { } public void ejbActivate() { } public void ejbPassivate() { } public void setSessionContext(SessionContext ctx) { EjbCxt = ctx; } }
|
以上的三个文件分别是一个
web组件和两个ejb组件的源代码,这三个组件构成了一个调用链.webtest.jsp中,首先通过HttpServletRequest..
getUserPrincipal
()方法来得到调用webtest.jsp的用户的Principal对象,在通过Principal.getName()方法得到调用者的用户名.
然后通过HttpServletRequest..isUserInRole()方法来判断调用这是否属于特定的安全角色.CountBean是一个
stateful
SessoinBean,它拥有一个count()远程方法,在这个远程方法中写了用于得到调用者用户名和判断调用这安全角色的代码,还包括调用
CountBean1对象的代码,用于展示调用者身份标识是如何在调用链中传递和调用者的安全角色如何被改变的.CountBean1也是一个
stateful SessoinBean,它的代码内容与CountBean基本相同,只不过它不包含调用其他Bean的代码.
现
在我们应该配置各组件的安全属性了.我们在组件webtest中创建安全角色admin,引用名为adminref,在组件CountBean和
CountBean1中分别创建安全角色admin和user,
引用名分别为adminref和userref.将webtest组件配置为"HTTP Basic
Authentication",给安全角色admin赋予访问webtest.jsp的权限.把CountBean设置为Run As
Specified
Role,选择user安全角色.在运行环境中将用户j2ee赋予admin安全角色和user安全角色.然后发布应用到服务器.用用户j2ee访问
webtest.jsp.
执行结果:
客户端见图20
图20
服务器端:
CountBean.count()
the caller is j2ee
the caller is admin Role
CountBean1.count()
the caller is j2ee
the caller is user Role
从
运行结果看,访问webtest.jsp的用户为j2ee,其安全角色为admin,访问CountBean的用户名为j2ee,安全角色为admin.
可以看到用户身份标识从web容器传递到了ejb容器.再看组件CountBean1的输出结果,由于CountBean被设置成了Run As
Specified
Role,因此在CountBean向下调用其他组件对象时,用户安全角色已经被改为指定的安全角色,这里是user.所以我们会看到,调用组件
CountBean1的用户名为j2ee,安全角色为user.j2ee的这种安全特性满足了同一用户在不同应用中具有不同安全角色的需求.开发人员也可
以利用这种特性进行灵活的安全逻辑判断.