Extending JAAS
Guosheng Huang, PhD, is a seniorsoftware developer withWysdom Inc. He has over 15years of experience in software engineering and technical architecture. gorsenhuang@yahoo.com
译Q绿野风?2003/10
用户认证和访问控制是大多数java应用的重要安全尺度,特别是J2EE应用。Java认证和权限服?即JAAS)QJ2SE1.4?.5的核心APIQ描l表达了新的安全标准。其提供了一个可插拔?pluggable)和富有弹性的(flexible)框架(framework)允许开发者合不同的安全机制和丰富的已经存在各种安全斚w的资源?BR>伴随着卛_来(f)的J2SE1.5版本的发布,它包含了许多诸如加密技术、XML安全性、公钥机ӞPKIQ、Kerberos Q是一个网l附加系l?协议Q可以允许用户通过一个安全伺服器的服务来验证 自己。象q端登陆Q远端拷贝,pȝ间的怺档拷贝和另外高风险Q务的服务被?得相当安全和可控制。)和结盟认证(the federating identityQ的增强Q,JAAS会在J2EE实现中扮演一个更加重要的角色?BR>
认证
认证是校验一个用h有用已l被企业用户注册机构证明了的w䆾鉴定的权限的处理q程。JAAS的认证机制徏立于一整套可插拔的模块Q参看图1Q基上。JAAS允许不同的验证模型在q行时可被插拔。客户应用L通过登陆上下文对象和JAAS交互?BR>认证处理q程典型的要l过下面的步骤:
1?生成一个LoginContext对象。这个LoginContextL配置文g以决定用那个LoginModule。同P可选择的,有可能传递一个CallbackHandlerlLoginContext.
2?通过调用LoginContext的loginҎ(gu)执行认证Q它会加载预定义的LoginModuleL验是否用户可以被认证?BR>3?如果用户被认证,那么用规则和标识和其所属项q行兌?BR>4?或者在登陆p|的情况下跑出一个LoginException
5?使用LoginContext的logoutҎ(gu)q行注销登陆
在JAAS中,登陆是一个两阶段(two-phase)的处理过E。第一阶段是“登陆(loginQ”阶D(像上面2所描述的)。这个阶D唯一的Q务是认证。只要处理过E成功通过q个阶段Q认证处理过E就q入了“提?commit)”阶D(如上步骤3Q,q一阶段LoginModule的commitҎ(gu)被调用去兌所属子相关的规则和标识?BR>在JAAS中一个所属子表CZ个认证实体,比如一个h或者一台设备。它包含了一整套法则和安全相关的属性诸如密码和加密密钥。在JAAS体系l构中,所属子和其所附属的相x限,扮演了重要的角色在认证过E当中。所有的认证模块当中QLoginModule是事实上的认证机制的借口。虽然LoginModulex有得到直接调用客户应用的ZQ但是他l由一个可插拔的模块提供了一个认证的具体cdQ其实现了认证的法q且军_实际的认证过E是怎样被执行的?BR>SUN提供了几个默认的LoginModule 实现Q在sun.com.security.auth.module包里有诸如JndiLoginModule,Krb2LoginModule,UnixLoginModule和NTLoginModule{几个LoginModule实现。因为JAASdl构体系是可扩展的,所以你只要在配|文件中指定使用哪个LoginModule模块可以几乎全部插入Q何LoginModule模块?BR>如下即ؓ一个配|文件的例子Q?BR>MySample {
com.sample.module.MyLoginModule required debug=true;
};
q里MySample是登录上下文环境(login context)的名字,当你生成一个新的LoginContext开始认证过E时它会被传入LoginContex的构造函C。依据配|块提示Q那个文本块提醒JAAS有关LoginModule在登录过E中应该被用来执行认证。另外,对于LoginModuleQQ何关于他的选项也可以在q里被指定。在执行dq一步骤的过E中QCallbackHandlerc被LoginModulecȝ来跟用户通信已便于取得认证信息。CallbackHandlercd理三U类型的回调QCallbackQ:NameCallback,提示用户输入一个用户名QPasswordCallcack,提示输入密码QTextOutputCallback,报告错误、警告或则发送给用户一些其他信息?BR>
授权是决定是否认证的用户可以执行一些动作的工作Q例如访问一处资源。因为JAAS建立于已l存在的Java安全模型的基上,故这个过E时Z{略的。策略配|文件实质上包含了一pd的入口,诸如“Keystore”和/或“grant?
grant入口包含了所有的权限Q他是通过认证的代码或则法则被授予可以q行安全敏感的操作,例如Q访问一个具体的Web面或则本地的文件。JAAS支持Z法则的策略入口,赋权入口基本格式如下Q?
grant Codebase “codebase_URL?Signedby “signer_name,?BR>Principal principal_class_name “principal_name?
Principal principal_class_name “principal_name?
?{
permission permission_class_name “target_name? “action?
permission permission_class_name “target_name? “action?
?BR>}
上面格式中“动作(actionQ”可能是必需的或则可能被忽略依赖于权限类型。在JAAS体系l构中,{略对象表达了一个Java应用环境的系l安全策略和在Q何时间事实上只有一个策略对象。依据Java2 SDK文档Q默认的{略实现是sun.security.provider.PolicyFile,其中{略被指定在一个或多个{略配置文g里?BR>只要用户被认证,授权l由Subject.doAsҎ(gu)发生Q或者从Subjectcȝ静态方法doAsPrivilegedQdoASҎ(gu)用当前的AccessControlContext动态和子项q且同时调用runҎ(gu)L行动作,他导致安全验证。权限验证过E通过下面的步骤在?:
像LoginModule,{略也是可插拔的模型。你可以挂上其它的策略实现通过在java.security的属性文件中改变“policy.provider=sun.security.provider.PolicyFile?
C个你用的{略cR?BR>
Extend JAAS
JAAS建立于已l存在的Java安全模型的顶端,其基于“CodeSource”和q面文本格式{略文g实现。这可能对企业应用是不够用的Q你可能想用可定制的安全仓库。对于JAAS的其它实?诸如LDAP(d目录讉K协议)Q数据库或者其他文件系l,它可以通过~写你自q可定制模块被完成Q感谢JAAS的可插拔的特性。然而,q需要对模块和JAAS中的处理q程有完善的理解Q同时你必须做许多编码去覆写相关的类Qƈ且处理好配置和策略两U文件?BR>理想情况下,我们愿意能够扩展JAAS以一个更加容易的方式以便于无Z时一个可定制的安全知识库或者不同的讉K控制机制改变或者必d增加Ӟ你能够只开发和插入q些不同的小模块Q即Q适配器)去适应q些新的变化和需求,q且在最好的情况下,不必ȝ解和熟?zhn)JAAS处理q程的细节,同样Q我们也愿意能够dq些变化仅仅通过改变一个配|文件。另一个目标是我们的JAAS扩展lg能够被用在不同的J2EE应用中—独立的或者Web上的。图3描述了JAAS扩展lg的设计意图。我们的JAAS扩展l在实现可定制的LoginModule和策略模块时充分件利用了JAAS插拔式的体系l构。这些模块中Q我们委z数据请求到适配器。这些适配器的每个对于诸如数据取回的简单Q务是隔离的,所以你可以快速地使用不同的安全知识或者算法开发不同的适配器而不是尝试去实现不同的LoginModule或者策略模块,它们更加复杂q且需要更多的努力?BR>你可以从www.sys-con.com/java/sourcec.cfm.下在完整的源玛?BR>
实现的AuthLoginModulec?/B>
AuthLoginModulecL我们定制的LoginModule实现QLoginModulecL在JAAS中是一个可插拔lgq且服务于两个目的:
1、鉴定认证用?BR>2、如果认证成公,则用相关的负责h信息或者证书更C题?BR>
LoginModule?个方法去实现功能Q让我们x一下login()Ҏ(gu)。这个方法被调用以认证主题ƈ且主要作两g事情Q?BR>1、包含用户名和密码,典型圎ͼLoginModule要调用CallbackHandlercȝhandleҎ(gu)d到用户名和密?BR>2、通过和数据源中的比较校验密码。LoginModule从Callbacks取回用户名和密码?光认期望用h口的某种排序)Q这一点对于一个简单的演示E序或者就在命令行Q可是他对于一个J2EE应用来说不太实用Q例如,对于大多数的Web应用Q用户名和密码将比较典型的从一个form中读出。在q种情况下,使用JAAS认证会比较困难。考虑我们不直接用LoginModule,解决Ҏ(gu)是实C个可定制的CallbackHandlerc,他会接收用户名和密码然后递交它们lLoginModuleQ所以他没有必要提示用户输入信息
以下CZ説明用户信息如何从JSP或者Servlet中传递:
String userName = request.getParameter (“user?;
String password = request.getParameter(“password?;
LoginContext context = new LoginContext (“MySample?
new AuthCallbackHandler (userName, password));
一旦拥有了用户名和密码在手QAuthLoginModulec,我们的LoginModulecȝ定制实现Q会l由LoginSourceAdapterFactory实例化LoginSourceAdapterq同时委z֮际的认证q程到资源适配器。适配器只不过是一个简单的c,其从一个具体的数据适配器(比如数据库或者LDAP,或者一些别的系l)领取用户信息。在“提交”阶D,AuthLoginModulecMLoginSourceAdaptercd回相关的信息q且把他们和主题相关联?BR>
LoginSourceAdapterc?/B>
LoginSourceAdaptercL一个认证目的的资源适配器的接口Q它?个需要实现的Ҏ(gu)Q?BR>1、void initialize(Hashtable parameters):initializedҎ(gu)被调用来以相关的参数初始化适配器。此Ҏ(gu)在对象生成后立即被调用ƈ且优先于M对其他方法的调用?BR>2、boolean authenticate(String username,char[] password):此认证方法被调用来认证用戗?BR>3. String[] getGroupNames (String userName):getGroupNamesҎ(gu)被调用来在认证成功后得到相关的主要信息?BR>4. void terminate ():q个Ҏ(gu)在LoginModulecȝlogoutҎ(gu)被执行后调用Q它l适配器做一些清理工作的Z?BR>
AuthPolicyc?/B>
在JAAS架构下,安全{略被java.securety.Policy cL处理Q他会证明赋l一个具体的代码源或者主体的多种权限。就像在上一D被讨论的,sun.securety.provider.PolicyFile是其默认实现。PolicyFilecM用^面文本文件去证明在权限和代码源之间的对应关系Q这点对于企业应用可能不是太好。一个集中的pȝ比如支持Z角色的安全性的关系数据库将会更好。很明显Q扩展JAAS授权以处理不同的来源的不同的安全标记Q我们需要写我们自己的策略实现?BR>
生成一个定制的{略实现的步骤如下:
•扩展java.securety.Policyc?BR>•实现getPermissions()Ҏ(gu)
•实现refresh()Ҏ(gu).
如果你看到我们定制的{略cȝ实现Q你可能注意到我们的AuthPolicycL生在sun.security.provider.PolicyFile而不是java.security.Policy. Z么?首先Q我惌实现AuthPolicycM为通用的Policyc,q可以处理默认的{略cM需要用M适配器介入。通过从PolicyFilec,我们不需要去实现{略文g解析和其他相关的代码。同Ӟ党应用运行于一个安全管理器起作用的情况下,一些权限,比如doAsPrivileged AuthPermissioncdd配置文g的FilePermission(Z载入配置文g)Q需要被赋权Z执行JAAS.
当然,q些权限可以被存储在数据源里Q但是把它们攑օ标准Java安全{略文g中可能更为有利。可是,对于正规开发你应该实现一个适配器以应付q些事情。在扩展认证时要遵@相同的设计模式。我们的{略cd托权限请求于
PermisssionAdapterc?/B>
在权限类里,不同的权限保存于自己的PermissionCollectioncd例,如果你创Z个定制的权限c,你需要生成你自己的PermissionCollectioncȝ型,否则不能保证你的权限对象被参考确认?BR>
PermissionAdapterc?BR>PermissionAdaptercd我们的JAAS扩展lg中是认证q程可插拔模块的接口。它从一个具体的数据源评估策略ƈ且分发一个包含一套已赋予的权限的PermissionCollectioncRPermissionAdaptercL口有下面的方法:
• void initialize (Hashtable initParams):initializeҎ(gu)被调用以相关的参数初始化适配器。此Ҏ(gu)会被立即调用q且优先于Q何其它的Ҏ(gu)调用。同Ӟ在PolicycȝrefreshҎ(gu)被执行后它也会被调用?BR>• PermissionCollection getPermissions (ProtectionDomain
domain): 本方法只要某U主体权限被h׃被调用?BR>作ؓ一例子Q让我们看看如何实现一个基于角色的PermissionAdaptercR假设有三个角色Q管理员Q用P和客人,都拥有不同的权限Qƈ且所有的权限信息被存储在数据库中?BR>首先Q在initializeҎ(gu)中,我们从数据中取得所有角色的权限信息q且l装入集合类中,比如QHashtable.接下来,在getPermissionsҎ(gu)中,我们会收集到相关M有关的权限(q是仅有的会涉及到基于角色的讉K控制Qƈ且返回它们。注意我们可以得到相关的M通过调用ProtectedDomaincȝgetPrincipalsҎ(gu)。它是如此的单,不是吗?
JaasUtilc?/B>
对于我们的JAAS扩展lgJaasUtilcL主要的纽带,q且它有一个构造函数取得用户名和密码。有两个关键的方法:
1Q?boolean authenticate()
2Q?boolean checkPermission(Subject subject,final Permission perm)
JaasUtilcd际gq了LoginContextcȝ登陆h和SecurityManagercȝ权限查步骤?Listing 1 昄了如何用JaasUtilcR这D代码首先从HtttpServletRequestcd得用户名和密码ƈ且尝试认证用?然后其检是否用h权限讉K“editReg.jsp?
配置
现在我们拥有自己的定制的LoginModule,Policy和其他相x块实现。这些模块可以委托相关的数据hl合适的适配器;q是很好的事情。然而,在JAASl构中LoginModulecdPolicycȝ对不会被应用E序直接调用Q所以我们怎样知道那种适配器应该被实力化和怎样传递需要的参数或者信息,比如数据q接Q给适配器?{案是适配器可以通过更新一个XML配置文g被动态配|。这个XML配置文g׃个主要的数据D늻成:
1?lt;authentication>:本段内容定义登陆源适配器和认证q程需要的各种可能的输入参数?BR>2?lt;authorization>:本段内容定义权限适配器和授权q程序要的各U可能的输入参数
你可以制定用哪个LoginSourceAdaptercdPermissionAdaptercR在配置文g中传递额外的信息也是可能的。让JaasUtilcȝ道在那里L配置文g有两U途径Q?BR>1?制定配置文gl由命o行属性开? -Dcon.auth.config
2?调用JaasUtil.setConfigFileQconfigFileQ方?
当你部vJAAS扩展lgӞq个定制安全{略cL件必被加入到Java的jre/lib目录Q这引L略类文g被bootstrapcd载其载入。否则,即便你放|策略文件在你的Javac\径中Q它也将不能被检出ƈ且Sun默认提供的策略类会代替而被使用?BR>
ȝ
扩展JAAS是不困难的。JAASl构提供l你定制实现认证和授权过E的Ҏ(gu)。理解这些过E如何工作是懂得如何替换你自q实现的第一步。在本文中,我们重温JAAS的基Qƈ且检查验证了如何扩展JAAS使其成ؓ一个更加具备动态化、灵zd和规模化的特性的框架。用这U扩展框Ӟ你既能够L的生成自q陆和讉K控制机制以支持你自己的企业别的安全需求也能够支持新兴的安全标准,或者^衡你们已l存在的或定制安全模型作为适配器,然后热插拔他们进入JAAS中。这应该可以l你的企业应用提供一个基于标准的同时高可定制的认证和授权q程
]]>