通过允许配置一个用于口令验证的LDAP,应用程序服务器支持UsernameToken身份验证。由于其简易性和在应用程序服务器中的广泛支持,UsernameToken身份验证是非常吸引人的。
UsernameToken 身份验证
UsernameToken 身份验证与HTTP身份验证很相似,此时用户保密信息在消息的头部被传送。UsernameToken被添加到SOAP的头部,可能还会夹杂着一个散列的口令。这是一种加密的简单形式,可以避免无限制地传递口令。应该使用传输级别或者消息级别的加密来保证用户口令的保密性。
通过允许配置一个用于口令验证的LDAP,应用程序服务器支持UsernameToken身份验证。在应用程序服务器收到一个SOAP消息时,在将消息转发给应用程序之前,它会通过LDAP验证用户证书的正确性。
由于其简易性和在应用程序服务器中的广泛支持,UsernameToken身份验证是非常吸引人的。然而,在考虑将它部署到你的应用框架时,你必须清楚其限制条件:
会话状态: UsernameToken需要用户名和口令在每一个Web服务的调用上都被传递。这暗示着如果客户端经常与该服务会话,客户端必须要对口令进行缓存,而这会导致一种安全风险。
性能:应用程序服务器必须重新验证每一个Web服务调用。而且,因为口令是消息的一部分,那么即使消息并没有包含敏感信息也必须加密。
为了演示UsernameToken身份验证,下面的例子重新使用了计算器服务的例子。你必须配置应用程序服务器以支持UsernameToken的安全,并且指定一个LDAP来验证用户的证书。配置步骤是与特定服务器相关的,因此请参考你的服务器的相关文档。笔者用WebSphere 6.1和活动目录测试了这个例子。
你必须增加一个WSS4J SOAP处理程序,以支持客户端支持在报头中发送UsernameToken。下面的客户端代码在调用Web服务之前,先调用configureClientHandlers方法:
public static void main(String[] args) throws MalformedURLException {
CalculatorServiceClient sc = new CalculatorServiceClient();
Calculator calc = sc.getCalculator(UT_ENDPOINT);
configureClientHandlers(calc);
float a = 5f;
float b = 7f;
System.out.println(a + " * " + b + " = " + calc.multiply(a,b));
} |
configureClientHandlers方法创建一个新的拥有UsernameToken 属性的WSS4JoutHandler处理程序。下面的configureUsernameToken方法指定了属性:
private static void configureClientHandlers(Object svc) {
Client client=Client.getInstance(svc);
client.addOutHandler(new DOMOutHandler());
Properties properties = new Properties();
// Configure the UsernameToken properties
configureUsernameToken(properties);
client.addOutHandler(new WSS4JOutHandler(properties));
}
protected static void configureUsernameToken(Properties config)
{
// UsernameToken Action
config.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// Clear Text Password
config.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// User name to send
config.setProperty(WSHandlerConstants.USER, "johndoe");
// Callback used to retrieve password for given user.
config.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS,
PasswordHandler.class.getName());
} |
口令从PasswordHandler中重新检索。它将口令存储在一个由用户名索引的地图中。下面是示例样本的用户证书代码:
public class PasswordHandler implements CallbackHandler {
private Map passwords = new HashMap();
public PasswordHandler() {
passwords.put("johndoe", "abc123");
}
...
The following SOAP message is observed by the TCP/IP monitor after running the client:
<soap:Envelope ...>
<soap:Header>
<wsse:Security ...>
<wsse:UsernameToken ...>
<wsse:Username ...>
johndoe
</wsse:Username>
<wsse:Password ...>
abc123
</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<multiply xmlns="http://ejb.security.ws.dev.com">
<a>5.0</a>
<b>7.0</b>
</multiply>
</soap:Body>
</soap:Envelope> |
现在这个SOAP消息的头部部分包含了用户名和口令。服务器对用户证书进行验证,或者许可或者拒绝了对目标Web服务的访问。为了防止对口令的窃听,你必须使用消息级的加密或者以前描述的SSL方法。
一个.net2.0客户端也将一个UsernameToken添加到SOAP/HTTP的头部。为了支持这项功能,你必须在你的Visual Studio环境中安装WSE 3.0库及其扩展。(你可从这里下载。)你必须像在Java客户端的例子中所做的那样,从WSDL中生成一个客户端的stub。生成的stub看起来会是如下这样子:
...
public partial class CalculatorServiceWse : Microsoft.Web.Services3.WebServicesClientProtocol {
private System.Threading.SendOrPostCallback multiplyOperationCompleted;
private bool useDefaultCredentialsSetExplicitly;
... |
一旦stub被创建,客户端代码就会展现这个stub,并且指定UsernameToken被发送到服务器。下面的c#示例代码演示了如何做到:
static void Main(string[] args)
{
CalculatorServiceWse calc = new CalculatorServiceWse();
calc.Url = "http://localhost:9080/WSUTSigEncRouterWeb/services/Calculator";
//
UsernameToken token = new UsernameToken("johndoe", "abc123",
PasswordOption.SendPlainText);
calc.SetClientCredential(token);
calc.SetPolicy("usernameTokenSecurity");
float result = calc.multiply(7, 5);
Console.WriteLine("Result = " + result);
Console.ReadLine();
} |
运行客户端代码就会生成所期望的输出结果:Result = 35.0