文章《体验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,并有适当修改。
技术实现
这里描述一下前面Use Case中涉及的几个技术方面。
<filter-mapping>
<filter-name>SAMLAuthFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这里表示SP全部的页面受SAMLAuthFilter保护。
我们使用 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 -
这里的代码来自于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的实现超出了本文讨论的篇幅。这里只描述其需要实现的主要逻辑,同样来自于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没有提供这个实现,这里就不深入讨论了。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<title>Auto Submit</title>
</head>
<body onLoad="document.myform.submit()">
<form name="myform" action="">
</form>
</body>
</html>
综上,大家看是不是很简单呢?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的部署过程。
至此,部署成功。
下面是演示过程:
演示一:
演示二:
演示三(图片省略):
如果IDP网站当机,那么只是丧失SSO而已,不会影响业务系统的使用,这一点对大的应用来说很重要。
参考资料