SaaS 应用程序实例java版.docx
《SaaS 应用程序实例java版.docx》由会员分享,可在线阅读,更多相关《SaaS 应用程序实例java版.docx(23页珍藏版)》请在冰豆网上搜索。
SaaS应用程序实例java版
保护多承租SaaS应用程序
用SpringSecurity和ApacheDirectoryServer进行身份验证和授权
MassimilianoParlione,解决方案架构师,EMC
ChicoCharlesworth,高级Java开发人员,EMC
简介:
软件即服务(SoftwareasaService,SaaS)应用程序多承租的性质决定了安全性是一个关键的问题。
本文介绍了一个保护多承租Java™应用程序的可行的、实用的方法,即结合使用开源SpringSecurity框架和ApacheDirectoryServer。
作者通过一个多承租示例Web应用程序来展示这个方法。
本文的标签:
安全, 应用开发, 开放源码
企业中的SaaS
这些年SaaS开始繁荣起来了。
越来越多的公司都转向这个随需应变的解决方案来更快地响应业务需求,降低成本。
ForresterResearch于2007年进行的一项调查发现,有16%的大型企业和15%中小型企业都在使用SaaS。
而且每年分别以33%和50%的速度增长(参见参考资料)。
事实上,根据 SaugatuckTechnology,2008年5月的统计,“到2012年时,超过100个员工的商业组织会有70%以上已经配备了至少一个SaaS应用程序”。
SaaS能够提供宿主的软件应用程序,并且可能向未开发市场领域提供服务,从而使服务供应商实现规模经济。
SaaS的 多承租的最大优点是:
它允许服务供应商向多个客户机组织提供服务(参见 参考资料)。
在SaaS应用程序中有很多用户共享相同的资源,所以保护它的最好的方法就是对数据和配置(基于承租者ID)进行逻辑分区,从而确保多承租的安全。
本文展示了如何实现第一道有效防线来保护基于Java的多承租SaaS应用程序。
该解决方案结合使用了SpringSecurity(一个经久不衰的开源安全框架)和ApacheDirectoryServer(一个基于Java的流行服务器,它是开源的并且遵从LightweightDirectoryAccessProtocolv3,即LDAPv3)。
本文提出的解决方案是一个 示例JavaWeb应用程序,它既可以部署到ApacheTomcat,也可以部署到ApacheGeronimo。
本文重点介绍SaaS模型内的身份验证和授权机制。
其他有关SaaS安全性的概念和技术—比如数据保密与隔离、法规、审计和密码等,超出了本文的范围。
多承租的SaaS应用程序中的身份验证与授权
身份验证和授权是现实应用程序的安全性概念中主要的两个:
∙身份验证允许一个应用程序在连接时验证一个人(或一个应用程序、智能卡等)是否与它声明的一样。
∙授权定义一个用户在一个系统上的权利与权限。
用户身份验证通过之后,授权会决定该用户在系统上有权做什么。
因此,授权应该发生在身份验证之后。
身份验证和授权在SaaS应用程序中很复杂。
在一个安全性SaaS解决方案中,底层的身份验证和授权基础设施有两种设计方法:
集中式或联邦式。
本文提出的解决方案使用集中式身份验证系统(LDAP服务器)。
集中式的身份验证系统并不排除支持分布式目录的可能性,分布式目录储存了可以分区和复制的信息。
本文不考虑采用另一种分散式处理方法,即 联邦身份管理。
在SaaS领域用联邦身份管理会给安全性带来很多新的挑战。
(典型的用例会涉及到跨域、基于Web的单点登录、跨域用户帐户供应、跨域授权管理和跨域用户属性交换等。
详细信息请参见 参考资料,里面的文章链接“MeetingtheSaaSSecurityChallenge”有详细的解释)。
回页首
SpringSecurity简介
SpringSecurity:
比Acegi的功能更强大
SpringSecurity从2003年开始作为Spring的AcegiSecuritySystem,直到2007年年末它才成为一个官方的Spring项目,并重命名为SpringSecurity。
我们推荐使用SpringSecurity(而不是Acegi)有以下几点原因:
∙它具备Acegi的所有优点,并且有额外的优点。
∙它利用自定义Spring2.0配置名称空间。
∙复杂的安全性细节现在隐藏在更简单的XML配置之后。
∙它具有自动配置的能力。
在默认情况下,JavaEnterpriseEdition(JavaEE)5安全机制不支持承租者ID(tenantID)等自定义属性,不管它们是什么样的验证者类型(basic,form,digest或clientcertificate)。
要支持多承租就必须要实现自定义的解决方案。
在本文中,我们将展示如何使用SpringSecurity来构建这样的解决方案。
SpringSecurity提供了一个综合的安全解决方案,这个方案大大地简化了在JavaEE应用程序中开发安全措施的工作。
它提供了更高级的摘要,能够让您插入不同的身份验证模型,同时还支持丰富的身份验证功能。
此外,它还在不同的应用程序服务器之间提供了高度可移植性。
SpringSecurity有以下特性:
∙声明性安全性
∙支持各种身份验证和授权机制,如basic、form、digest、JDBC和LDAP
∙支持方法级别的安全性以及JSR-250安全性注释
∙支持单点登录
∙支持容器集成
∙支持匿名对话、并行对话、remember-me、通道加强等
本文的重点是直接集成SpringSecurity和LDAP。
其他的部署场景可能会考虑到其他的方法,如Java身份验证和授权服务(JavaAuthenticationandAuthorizationService,JAAS),或者是由SpringSecurity框架提供的容器适配器进行的容器管理身份验证。
回页首
LDAP与ApacheDirectory概述
在企业中,管理用户和角色的常见方法是使用LDAP服务器。
有几个开源的商业LDAP解决方案可供选择(参见 参考资料)。
考虑到有些读者可能不熟悉LDAP或ApacheDirectoryServer,接下来我们对其进行概述。
LDAP的核心
LDAP本质上就是一个数据库。
但它趋向于包含更多描述性的、基于属性的信息。
由于LDAP目录中的信息的读多于写,所以LDAP被设计为读最优化。
最常见的例子就是电话簿,它里面的每一人都附有地址和电话号码。
作为身份验证和授权源,LDAP与关系数据库管理系统性相比有以下优点:
∙有线协议;无需驱动器
∙灵活的模式
∙以身份为中心
o身份验证、授权和审计
o组
o安全性(密码、证书)
ApacheDirectoryServer的核心
ApacheDirectoryServer是一个可嵌入的、可扩展的、遵从标准的开源LDAP服务器,它由Java语言编写而成。
我们为本文的解决方案选择ApacheDirectoryServer的理由是它的简单性,因为它是一个纯Java实现。
对于现实中的应用程序,您一定要正确衡量哪一个LDAP解决方案最符合您的业务和技术需求。
ApacheDirectory项目提供了一个ApacheDirectoryServer,它遵从LDAPv3和ApacheDirectoryStudio,后者是一组基于Eclipse的目录工具(参见 参考资料)。
回页首
在多承租的环境中集成SpringSecurity和ApacheDirectoryServer
在一般情况下,配置SpringSecurity使其能够协同LDAP服务器进行工作很简单。
虽然在多承租的环境中集成它们也相对容易,但还是比一般情况复杂些。
我们首先论述如何在ApacheDirectoryServer中创建一个多承租用户注册表,然后再展示一个动态LDAP路由解决方案如何为一个有效的多承租安全性解决方案提供便利。
多承租ApacheDirectoryServer用户注册表
本小节描述一个示例多承租用户注册表,它由两个安全性区域组成,名为tenant1和tenant2,每一区域个都有两组不同的用户:
管理员和访问者。
每一组都链接着许多用户,这些用户共用一个特定角色,而且都属于该组的相应安全区域。
不同区域的用户凭证储存于一个ApacheDirectoryServer用户注册表中,位于不同的子树下,如 图1所示。
将不同的LDAP后缀分配给不同的安全区域。
例如,tenant1的基本专有名称(BaseDistinguishedName,DN)是[dc=tenant1,dc=com],tenant2的基本DN为[dc=tenant2,dc=com]。
图1.多承租LDAP用户注册表示例
然后,不同的用户组(在现实中转换成了用户角色)会被分配到对应的每一个安全区域。
例如,组[cn=adm,ou=groups]和组[cn=gst,ou=groups](分别转换成管理员和访问者)属于基本DN为[dc=tenant1,dc=com]的tenant1安全区域。
反过来,不同的用户条目与一个特定安全区域下的特定组相关联。
例如,用户[uid=tenant1admin,ou=people]被赋予管理员的角色[cn=adm,ou=groups],属于安全区域[dc=tenant1,dc=com]。
一定要在ApacheDirectoryServer中的server.xml配置文件(ApacheDirectoryServerInstallDirectory/instances/default/conf/server.xml)中为 图1中的每一个安全区域创建一个新的分区和安全上下文条目,如 清单1所示:
清单1.ApacheDirectoryServer的server.xml文件
xmlversion="1.0"encoding="UTF-8"?
>
beansxmlns:
spring="http:
//xbean.apache.org/schemas/spring/1.0"
xmlns:
s="http:
//www.springframework.org/schema/beans"
xmlns="http:
//apacheds.org/config/1.0">
...
optimizerEnabled="true"syncOnWrite="true">
#tenant1ContextEntry
...
beanid="tenant1ContextEntry"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
propertyname="targetObject">
reflocal='directoryService'/>
property>
propertyname="targetMethod">
value>newEntry
value>
property>
propertyname="arguments">
list>
valuexmlns="http:
//www.springframework.org/schema/beans">
objectClass:
top
objectClass:
domain
objectClass:
extensibleObject
dc:
tenant1
value>
value>dc=tenant1,dc=com
value>
list>
property>
bean>
...
beans>
同样地,要像tenant1那样为tenant2添加一个新的分区和安全性上下文条目。
示例Web应用程序中有一个简单的示例.server.xml文件。
创建了安全区域后,就可以使用LDIFImportWizard——ApacheDirectoryStudioEclipse插件中的特色工具(参见 参考资料)——将类似于 清单2中的LDAPDateInterchangeFormat(LDIF)文件的内容导入到其相应的安全性区域中。
示例Web应用程序提供示例LDIF文件。
清单2.tenant1_users.ldif
dn:
ou=groups,dc=tenant1,dc=com
objectclass:
top
objectclass:
organizationalUnit
ou:
groups
dn:
ou=people,dc=tenant1,dc=com
objectclass:
top
objectclass:
organizationalUnit
ou:
people
dn:
uid=tenant1admin,ou=people,dc=tenant1,dc=com
objectclass:
top
objectclass:
person
objectclass:
organizationalPerson
objectclass:
inetOrgPerson
cn:
tenant1admin
sn:
tenant1admin
uid:
tenant1admin
userPassword:
tenant1admin
dn:
uid=tenant1guest,ou=people,dc=tenant1,dc=com
objectclass:
top
objectclass:
person
objectclass:
organizationalPerson
objectclass:
inetOrgPerson
cn:
tenant1guest
sn:
tenant1guest
uid:
tenant1guest
userPassword:
tenant1guest
dn:
cn=gst,ou=groups,dc=tenant1,dc=com
objectclass:
top
objectclass:
groupOfNames
cn:
gst
member:
uid=tenant1guest,ou=people,dc=tenant1,dc=com
dn:
cn=adm,ou=groups,dc=tenant1,dc=com
objectclass:
top
objectclass:
groupOfNames
cn:
adm
member:
uid=tenant1admin,ou=people,dc=tenant1,dc=com
SpringSecurity的动态LDAP路由
动态LDAP路由的原理是在运行时根据查找密钥动态地选择LDAP安全性上下文的可能性(参见 图2)。
在一个多承租环境中,这针对一个LDAP源(根据承租者的ID动态生成)转换成身份验证和授权。
图2.多承租动态LDAP路由
Spring本身不提供动态LDAP路由,所以需要亲自构建。
我们的想法受到类似解决方案—Spring的 AbstractRoutingDataSource —(参见 参考资料)的启发。
我们通过封装三个主要的类来实现动态LDAP路由:
∙清单3中所示的 AbstractRoutingSpringSecurityContextSource是一个抽象的实现,它基于Spring的LdapContextSource类。
它引用一组“真实的”安全性上下文源(参见 targetSpringSecurityContextSources),它的目的是根据固定的查找密钥将调用路由到众多的目标安全性上下文源之一(参见 getResolvedContextSource())。
清单3.AbstractRoutingSpringSecurityContextSource.java
publicabstractclassAbstractRoutingSpringSecurityContextSourceextendsSerializable>extendsLdapContextSource
implementsSpringSecurityContextSource,InitializingBean{
privateMap
targetSpringSecurityContextSources;
/**Determinethecurrentlookupkey.Thiswilltypicallybe
implementedtocheckathread-boundcontext.*/
protectedabstractTdetermineCurrentLookupKey();
/**Determinethe'real'securitycontextsourcedynamically
atruntimebaseduponalookupkey.*/
protectedDefaultSpringSecurityContextSourcegetResolvedContextSource(){
TlookupKey=determineCurrentLookupKey();
DefaultSpringSecurityContextSourcespringSecurityContextSource=
this.targetSpringSecurityContextSources.get(lookupKey);
if(springSecurityContextSource==null){
thrownewIllegalStateException(
"CannotdeterminetargetSpringSecurityContextSourceforlookupkey["+
lookupKey+"]");
}
returnspringSecurityContextSource;
}
publicvoidsetTargetSpringSecurityContextSources(
MaptargetSpringSecurityContextSources){
this.targetSpringSecurityContextSources=targetSpringSecurityContextSources;
}
publicvoidafterPropertiesSet()throwsException{
if(this.targetSpringSecurityContextSources==null){
thrownewIllegalArgumentException(
"targetSpringSecurityContextSourcesisrequired");
}
}
publicDirContextgetReadWriteContext(StringuserDn,Objectcredentials){
returnthis.getResolvedContextSource().getReadWriteContext(userDn,credentials);
}
@Override
publicDirContextgetReadOnlyContext(){
returnthis.getResolvedContextSource().getReadOnlyContext();
}
@Override
publicDirContextgetReadWriteContext(){
returnthis.getReso