文章《体验WebLogic Server 8.1 SP4新增功能之WebLogic Single Sign-On》(http://dev2dev.bea.com.cn/techdoc/200507499.html)中介绍了使用WLS8.1 SP4版本中的Single Pass Negotiate Identity Assertion Provider完成与Windows平台的SSO。但有更多的人可能希望在不借助,或者不与Windows集成的情况下完成Single Sign-On。Kerberos当然可以是其中的一种解决办法,但是目前互联网上应用更多的是SAML,一个用于在网络实体间交换安全认证信息的基于XML的框架。
本文据此提出一种适用(针对)于WebLogic Server的,简单、可行的SSO解决方案。这个方案以集中统一的用户信息为基础,但不包括通常理解的权限管理,而仅仅提供一个身份验证的服务。因为一般情况下,一个企业内部的多个成熟系统通常分别有自己的已经比较完善的用户身份验证和权限管理功能。在这种情况下,为整合多个业务系统而提供包括权限管理在内的多种安全服务,常常将问题复杂化(尽管SAML可以做到这一点)。
因此本文提出的方案将只解决用户的身份验证,即实现SSO的本义。在SSO帮助用户完成身份验证后,由各个业务系统本身的权限管理进行用户行为的进一步控制。这样不仅完成了用户帐号的集中管理(SAML可以完成不同系统间不同帐号的federated但不在本文讨论范围内),又不失原有系统灵活的权限控制。而且方案简单可行,不需要对原有应用做较大更改,适合快速解决Single Sign-On问题。
如果有更复杂的需求,完全可以在本文的基础上,参考Shibboleth(http://shibboleth.internet2.edu/)项目以及SourceID(http://www.sourceid.org/index.html)的项目进行深入的研究和了解,以找出最适合自己的解决方案。
名词
SAML
本文将以SAML 1.1实现SSO。SAML定义了一个用于在线商业系统间交换安全信息的基于XML的框架,它由OASIS (the Organization for the Advancement of Structured Information Standards)组织的Security Services Technical Committee (SSTC)开发。详情参考文章后面的参考资料部分。
OpenSAML
一个开源的SAML1.1实现,本文使用opensaml1.1完成与SAML相关的逻辑处理。OpenSAML是Shibboleth项目的一部分。Shibboleth是一个针对SSO的开源项目。
WebLogic Server
业界领先的J2EE应用服务器。本文的方案以WebLogic Server 8.1SP4为基础。本文附带的Demo应用只能跑在WLS8.1 SP4上,如果8.1SP3等版本需要适当改动。
Identity Assertion Provider
Identity Assertion Provider是WebLogic 7以后提供的众多Provider的一种,它其实就是一个Authentication Provider,只不过它不需要口令来完成用户的身份验证;如果在其他平台比如Tomcat上实现,也可以是一个普通的LoginModule,只需要通过CallbackHandler获取Token然后验证就可以了。
本文附带一个示例的Identity Assertion Provider。
Java KeyStore
本文将附带一个自签名的java key store,它由JDK带的Keytool生成并签名。
Service Provide
服务提供者。就是我们一般理解的业务系统,它通过配置在其上的Identity Assertion Provider完成对用户提交Token的校验,一般又称为SAML Assertion Consumer。文章后面简称为SP。
Identity Provider
身份认证提供者。通过它对用户进行身份验证,以及生成SAML Assertion Token,又称为SAML Assertion Productor。本方案的实现中核心为一个Servlet。文章后面简称为IDP。
Use Case
本文描述的方案实现了SAML 1.1 提供的POST Profile User Case,并有适当修改。
- 用户打开浏览器访问Service Provider 网站,访问受SAML Auth Filter保护的页面
- SP网站的SAML Auth Filter 检查到用户没有登录,那么将用户请求的页面地址(TARGET)附加在IDP的URI之后,并将用户的浏览器重定向到IDP网站
- 否则SP网站接受用户请求
- IDP网站接受到用户请求,判断用户是否已经登录
- 如果用户已经在IDP登录,那么到10
- 如果用户没有在IDP登录,那么向用户响应登录表单
- 用户通过IDP传回的登录表单,输入用户名和口令并提交登录请求
- IDP接受到用户登录请求,使用用户提交的用户名和口令进行身份验证
- 如果验证失败,向用户响应登录失败页面
- 如果登录成功,并且提交的Query String中指定了TARGET,那么会向用户响应一个自动提交表单,将SAML Assertion Token提交到TARGET指定的URL
- SP网站接受到用户提交的SAML Assertion Token,并将Token传递给Identity Assertion Provider
- Identity Assertion Provider校验Token是否合法,并从中读取用户信息
- 如果Token通过验证,Identity Assertion Provider返回经过验证的Subject,否则抛出验证异常
- SP获取到Identity Assertion Provider返回的Subject,表示用户登录成功,返回用户请求的页面
- 否则SP获取到Identity Assertion Provider抛出的LoginException,向用户响应登录失败页面,用户登录失败
技术实现
这里描述一下前面Use Case中涉及的几个技术方面。
- 从安全角度考虑,用户到IDP和SP间的连接必须通过HTTPS,尤其是在外网Internet;对于内部网络Intranet应用可以通过其他办法强化网络安全
- 使用SAML Auth Filter控制哪些页面加入SSO
这里表示SP全部的页面受SAMLAuthFilter保护。
- IDP根据用户提交的帐号和口令进行身份验证
我们使用 WebLogic提供的 API进行用户身份验证,这样才能完成一个让 WebLogic Server认为是合法的登录。这个weblogic.servlet.security.ServletAuthentication提供的方法具体实现在不同的SP版本中总有变化,比如WLS8.1 SP3和8.1 SP4,因此需要留意这一点。本文方案的实现,以WELS8.1SP4为准。
public static int login(java.lang.String username,
java.lang.String password,
javax.servlet.http.HttpServletRequest request)
throws javax.security.auth.login.LoginException
Returns an int value for AUTHENTICATED or FAILED_AUTHENTICATION after using the username and password to authenticate the user and setting that user information into the session. This method is similar to "weak", except that the LoginException is propogated to caller.
Parameters:
username - String
password - String
request - HttpServletRequest
Returns:
int authentication value
Throws:
javax.security.auth.login.LoginException -
- IDP根据用户信息生成相应的SAML Assertion Token
这里的代码来自于opensaml提供的POSTProfileTest.java,用于生成 SAMLResponse并进行签名
SAMLResponse r = SAMLPOSTProfile.prepare(
"www.opensaml.org",
"www.opensaml.org",
Collections.singleton("http://www.opensaml.org"),
"foo",
"foo",
null,
"127.0.0.1",
"foo",
new Date(),
Collections.singleton(
new SAMLAuthorityBinding(SAMLBinding.SAML_SOAP_HTTPS,
"http://www.opensaml.org",
new QName(XML.SAMLP_NS,"AttributeQuery")
)
)
);
assertNotNull("No SAMLResponse was generated.",r);
Iterator i = r.getAssertions();
((SAMLAssertion)i.next()).sign(
XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1,
ks.getKey(alias,password),
Arrays.asList(ks.getCertificateChain(alias))
);
r.sign(
XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1,
ks.getKey(alias,password),
Arrays.asList(ks.getCertificateChain(alias))
);
- Identity Assertion Provider校验IDP生成的Token
具体Identity Assertion Provider的实现超出了本文讨论的篇幅。这里只描述其需要实现的主要逻辑,同样来自于POSTProfileTest.java
assertTrue("SAMLResponse is not signed.",r.isSigned());
System.err.println("================ Generated Response ===============");
r.toStream(System.err);
System.err.println();
r.verify(ks.getCertificate(alias));
SAMLResponse r2 = SAMLPOSTProfile.accept(r.toBase64(), "www.opensaml.org", 60, true);
assertTrue("SAMLResponse is not signed.",r2.isSigned());
SAMLPOSTProfile.getSSOAssertion(r2,Collections.singleton("http://www.opensaml.org")).verify(ks.getCertificate(alias));
r2.verify(ks.getCertificate(alias));
System.err.println("================ Verified Response ===============");
r2.toStream(System.err);
System.err.println();
只要将上面的逻辑放入在Identity Assertion Provider中就可以了。
- 自动提交表单
在IDP响应给用户的页面包含了一个自动提交的表单,将SAML Assertion Token通过HTTP POST提交给用户请求的SP页面。这部分由SAML1.1中的Browser/POST Profile定义。由于生成的SAML Assertion往往比较大,不能通过HTTP GET提交,因此通过自动提交表单完成。这样用户仍然可能会感受到短暂的跳转,如果使用Browser/Artifact Profile,那么将由SP和IDP进行沟通,用户感受会好一点,但是开发,部署均麻烦一些,SP和IDP需要双向SSL,并且opensaml没有提供这个实现,这里就不深入讨论了。
综上,大家看是不是很简单呢?J
Demo的部署和演示
这里介绍附带的几个文件
sp.war
Service Provider应用,可以直接部署在WebLogic Server上。
idp.war
Identity Provider应用。可以直接部署在WebLogic Server上。其中主要就是一个LogonServlet
samlIdentityProvider.jar
这里提供的SAML Identity Assertion Provider只是为了演示使用,如果需要应用在具体的生产环境中,则需要详尽的测试,调优,以及适当的修改。
opensaml.jar
运行OpenSAML需要的jar包,同时它使用了很多第三方的类库,因此这些第三方的jar包也需要,由于文件太大,不在附件中,大家可以去www.opensaml.org下载最新版本。
xbean.jar
包含在WLS8 ${WL_HOME}/server/lib目录下,需要在启动WLS的脚本中加入到classpath中
endorsed
OpenSAML要求使用的XML Parser,在附件中
idp.jks
使用keytool生成的KeyStore,存放了一对密钥
idp.cer
密钥对中的公钥,SP使用它校验Token是否有效
下面介绍Demo的部署过程。
- 解开附件的压缩包,将opensaml.jar以及其使用到的第三方jar包和xbean.jar放在适当地方;将endorsed目录放在适当地方,将在2步骤中使用
- 修改启动WebLogic的脚本,在CLASSPATH中增加指向opensaml.jar以及其第三方jar和xbean.jar(WebLogic8中也带有该jar)的路径,并设置系统变量-Djava.endorsed.dirs=
- 为验证SSO需要将sp.war和idp.war分别部署在两台WebLogic Server上。修改idp.war/WEB-INF/web.xml中在path参数中指明idp.jks的文件路径,以及访问keystore的口令123456(这里keystore口令和alias的主口令设置为一致,否则此处需要多设置一个配置项);修改sp.war/WEB-INF/web.xml,在redirectURL参数配置IDP网站的地址,指向SAMLLogon 这个Servlet在IDP上映射的URL。
- 在SP上配置SAML Identity Assertion Provider(IDP所在的WLS不需要配置)
- 将samlSecurityProvider.jar拷贝至 ${WL_HOME}/server/lib/mbeantypes下。如果有Managed Server并且Managed Server在另外一台物理机器上或另外一个独立的WL安装目录下,那么同样需要将samlSecurityProvider.jar拷贝至对应的mbeantypes目录下
- 重新启动全部的Server
- 通过WebLogic Console配置SAML Identity Assertion Provider,在Detail中指明idp.cer证书的路径,别名,以及口令。需要注意一点就是,需要将Base64的选择取消
- 重新启动全部的WebLogic Server
- 在SP和IDP两个系统的WebLogic Server Realm中增加一个测试用户,为sso_user并设置口令,如果SP和IDP部署在同一个WLS Domain,那在WLS Admin Console上做一次就可以了;如果有统一的用户存储,那么部署对应的Authentication Provider就可以了
至此,部署成功。
下面是演示过程:
演示一:
- 访问SP网站的受保护页面http://localhost:19001/sp/index.jsp
- 浏览器被重定向到IDP网站被要求登录
- 输入用户sso_user以及用户口令登录
- 登录成功后浏览器返回 http://localhost:19001/sp/index.jsp页面内容
演示二:
- 访问IDP网站的登录页面 http://localhost:18000/logon
- 在登录表单中输入用户名sso_user和口令
- 登录成功后,显示IDP欢迎页面以及用户登录成功的帐号
- 手工在浏览器中输入地址 http://localhost:19001/sp/index.jsp
- 浏览器显示SP页面内容而不需要再登录
演示三(图片省略):
如果IDP网站当机,那么只是丧失SSO而已,不会影响业务系统的使用,这一点对大的应用来说很重要。
- 访问SP的登录页面 http://localhost:19001/sp/logon.jsp
- 在登录表单中输入用户名sso_user和口令,进行登录
- 登录成功后,欢迎页面显示用户的帐号信息
参考资料
- http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=securityOASIS关于SAML的站点
- http://www.opensaml.orgOpenSAML的网站,它是一个叫Shibboleth项目的一部分。Shibboleth项目主要应用在校园内,以及校园间的应用系统的用户身份联合认证。也有不少商业和政府网站采用
- http://www.sourceid.org/index.htmlSourceID是非常有实力的一个开源组织,旗下有多个开源项目(SAML、Liberty Alliance,WS-Federation)。SSO是其主要目的之一,当然不仅限于此,更着重于用户身份的联合,以及安全信息的共享。比如用户在网站1的帐号是User1,在网站2的帐号是User 2,那么如何实现SSO呢?
- http://e-docs.bea.com/wls/docs81/security/index.html更多关于开发WebLogic Security Provider的信息