xfire 使用用户名/密码进行身份认证

对SOAP报文进行身份认证的方式很多,不过都是通过在SOAP报文头中添加一些安全凭证(Security Token)信息来完成的,主要包括以下一些身份凭证:
 用户名/密码;
  X.509证书;
  Kerberos票据和认证者;
  SIM卡的移动设备安全性凭证。
其中用户名/密码是最简单的身份认证方式,它不需要**、数字证书,所以也就不需要CA,部署实施简单易行。下面我们就通过例子讲解如何进行基于用户名/密码的SOAP认证。这个实例让客户端提供用户名/密码,服务端验证客户端的身份,而客户端按正常方式接收SOAP响应报文。
服务端
服务端创建一个applicationContext-ws-security.xml,让BbtForumService拥有用户名/密码的认证功能。

xfire 使用用户名/密码进行身份认证
xfire 使用用户名/密码进行身份认证

由于需要对SOAP进行前置处理,所以必须将STAX流模型的SOAP转换为DOM模型,这通过在② 处指定一个DOMInHandler来完成。在③处注册了一个处理SOAP报文的WSS4JInHandler,用于对接收的SOAP报文进行前置处理,与其对应的是WSS4J OutHandler,它对SOAP报文进行后置处理。两者都具有极高的定制性,可以通过配置定制各种加工逻辑,这可以通过合理设置properties 属性达到各种加工处理的要求。
在④处,我们定义了一个action属性,它定义了需要处理的动作,UsernameToken表示进行用户名/密码认证的操作。其他的动作包括Encrypt、Signature、Timestamp、SamlTokenUnsigned等,可以同时设置多个动作,多个动作之间用空格分隔,如<prop key="action">Encrypt Signature</prop>。
不同的动作通过设置配套的其他属性提供相应的操作,我们通过passwordCallback Class属性指定一个密码回调实现类来处理UsernameToken密码查询和认证的工作。
passwordCallbackClass指定的类必须实现javax.security.auth.callback.CallbackHandler接口,其代码如代码清单16-11所示:

xfire 使用用户名/密码进行身份认证

PasswordHandle负责根据用户名查询正确的密码,这里,我们通过一个pwMockDB模拟存储用户名密码的数据库,如①所示。如果客户端以明文形式发送密码,UtPasswordHandler可以直接判断密码的正确性,如②-4所示。反之,如果以摘要的方式发送密码,UtPasswordHandler必须将正确的密码设置到callback中,以便WSS4JInHandler进行判断,如②-5所示。这里为明文和密文的密码分别提供了处理逻辑,但在一般情况下,密码是以明文还是密文发送是交互双方已经约定好的。
用户也可以通过扩展XFire的AbstractHandler定义自己的Handler,回调接口会传入MessageContext实例,可以通过MessageContext访问到SOAP中用户名/密码的信息进行认证。
客户端
现在服务端的Web Service服务已经需要对请求SOAP报文进行用户名/密码的认证了,客户端当然要进行相应的调整,以便在发送SOAP请求报文时添加用户名/密码的信息。

xfire 使用用户名/密码进行身份认证 
xfire 使用用户名/密码进行身份认证
xfire 使用用户名/密码进行身份认证

服务端在接收添加WS-Security信息的SOAP请求报文前必须进行前置处理,和服务端相对应,客户端在发送SOAP请求报文前进行前置处理,添加WS-Security信息。因此使用DOMOutHandler将DOM转换为STAX的流模型,此外通过定义一个WSS4JOutHandler完成在SOAP头添加用户名/密码信息的操作。
在④-2处,定义了密码的类型,WSConstants.PW_DIGEST表示密码以摘要方式发送,这可以保证密码的传输安全。设置为WSConstants.PW_TEXT表示密码以明文方式发送。④-3指定了一个用户名client,一般情况下,用户的密码保存在文件中或数据库中,因此通过指定一个回调类 UtPasswordHandler完成用户密码的获取操作,如④-4,回调类必须实现 javax.security.auth.callback.CallbackHandler接口,UtPasswordHandler的代码如代码清单 16-13所示:

xfire 使用用户名/密码进行身份认证

运行代码清单16-12,通过类似于TcpTrace的工具截取SOAP请求报文,用户将可以看到带WS-Security报文头的SOAP报文:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Header>
<wsse:Security soap:mustUnderstand="1"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401
-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>client</wsse:Username>
<wsse:Password>wOtW+umoWdf/7s7pyJrlFAp2xJo=</wsse:Password>
<wsse:Nonce>zJlCTQNCa+jHur2JdJF8Fw==</wsse:Nonce>
<wsu:Created>2007-04-09T09:11:05.593Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<getRefinedTopicCount
xmlns="http://server.xfire.baobaotao.com">
<in0 xmlns="http://server.xfire.baobaotao.com">20</in0>
</getRefinedTopicCount>
</soap:Body>
</soap:Envelope>

报文中粗体的部分为WS-Security的信息,为了简洁,我们特意删除了报文中一些命名空间的内容。
<wsse:UsernameToken> 元素被导入到SOAP报头中以携带身份信息。wsse:Username用于指定一个用户名,而<wsse:Password>则用于指定密码。有两种类型的密码:PasswordText和PasswordDigest,PasswordText以明文格式表示密码,而 PasswordDigest则被定义为Base64编码的SHA-1散列值。还有两个可选元素<wsse:Nonce> 和<wsu:Created>:前者是发送方创建的一个随机值,后者则是一个时间戳。如果<wsse:Nonce> 和<wsu:Created>两个元素中至少出现了一个,计算PasswordDigest的算法如下(每次PasswordDigest的值都是不一样的):
PasswordDigest = Base64(SHA-1(nonce+created+password))
如果没有使用其他的安全机制,PasswordDigest是通过非保密渠道发送用户名和口令的最佳方法。即使使用XML加密对<wsse:Password>元素进行加密,PasswordText依然可以使用。