本部分我们将对安全和Web Service予以介绍并加以评论。这个话题是有争议的,因为开发人员是第一次采用Web Service技术,安全问题就成了它们经常提到的话题。首先,我们回顾一些基本的概念,然后再看一看一些公共安全技术,例如SSL(Secure Socket Layer)。然后,我们将分析鉴别和授权所涉及的过程,还有数据保密性。
Web Services 安全简介
首先,我们将简要地介绍一些概念和体系结构,这些概念和体系结构是Web Service 安全的基础。
非对称密码技术——私钥和公钥
第二次世界大战期间,美国海军发现无线传输加密的最好形式之一就是用Najavo Indian语言进行交流。这种语言和其他语言天生就没有关联。这一简单的技术从来没失败过,甚至胜过更复杂的代码书和计算机制。
从那时起,现代密码技术就得到飞速发展。为了便于在此进行讨论,介绍两种基本的密码术算法:对称(常规)和非对称(也就是所熟知的公开-秘密密钥密码技术)。在对称密码技术中,对文件或者报文进行加密的密钥(或者编码)与对文件或者报文进行解密的密钥是完全相同的。而在非对称密码技术中,对文件或者报文进行加密和解密所使用的密钥是不同的。从算术角度看,这两个密钥是相关联的,但是由其中的一个密钥导出另一个密钥却很难实现。一个用户可以将公开密钥分配给其他用户,并对这些用户发送给自己的报文进行加密。这个用户将对私钥严格保密并利用它对使用公钥发送的报文进行解密。
从计算复杂性角度来看,使用对称加密技术传送报文比使用计算复杂性更强的非对称技术效率更高。由于可以自由分配公开密钥,非对称技术在大型团体中更具优势。
身份认证
身份认证是实现Web service集中工作的关键。现在大多数Web Service处于单独工作状态,但是大家都在谈论将Web Service聚集在一起,创建更加强大且更加复杂的业务服务。然而,Web Service 的集中要求服务能够共享信息,这一点在我们开始考虑在服务中添加安全时显得尤为重要。
请考虑大多数Web Service是如何实现安全的?提供安全Web Service的每一种业务都维护有授权用户列表,其中每一位用户使用userid/password(用户ID/口令)来进行身份鉴别。但是这对于复杂的服务来说并没有太多意义。而且这也是我们之所以听到象微软这样的大公司宣布Passport(护照)将使用Kerberos V5.0支持联合鉴别,还有Sun 启动Liberty Alliance (自由联盟工程)的理由。
对等的通信实体进行鉴别期间将使用身份特性。它将辨别、授权和审核等考虑在内。当前最常用的方法就是每一种安全身份特性将具有名称、私钥和X.509证书。X.509 证书包含特定身份特性的公共特征(aka credentials) ,这其中包括公钥。
数据签名
数据签名只能校对数据来源。如果使用私钥对数据加密,那么能够正确解密的任何人都可以很容易的校对数据的来源:正确解密就是说数据是使用相应的私钥加密的。由于私钥总是需要保密,而且只有生成私钥的主人唯一占有它,正确的数据解密当然就证明了它的来源。
不幸的是,非对称算法速度太慢,因此要使用特定的散列函数(例如MD5或者 SHA)。这些特定的散列函数首先将计算数据的手印(例如, MD5是16 字节而SHA是20 字节)加以符号表示。接收方使用相同的函数计算手印,使用公开密钥对采用符号表示的手印进行解密,最后将计算所得的手印和解密所得的手印进行比较。如果两者能够完全匹配,那么数据的来源就得到了证实。
认证证书
我们首先来看看证书是如何创建的。首先,生成密钥对(公钥和私钥)。接下来,创建一个所谓的证书签名请求Certificate Signing Request (CSR)。CSR只不过是一个数据集,其中包含了证书中所包括的全部信息(包括公开密钥) 并且使用所生成的私钥对其进行签名。下一步, CSR将被发送到证明授权机构(Certification Authority)由其根据CSR创建证书并用私钥签署证书。通过签署证书,证明授权机构(Certification Authority)将核实证书包含的有效数据。信任这个证明授权机构的任何人都可以使用它的证书(更准确的公开密钥)并可以核实所签署的证书。将证书存储到一个专门的可信赖的存储之处能够表明我们信任某一身份特性这一事实。证书核实可以扩展到更高的级别。因此,可以使用某一身份特性签署证书,而这一身份特性是由另一身份特性签署的。如果我们信任这一串中的所有证书,这就意味着我们也信任这一串中其它更高级别的证书。
Web Services和安全 API 机制
因特网上常用的传统安全技术对Web Service来说是远远不够的。主要问题在它们的传输存在依赖性。例如,最广泛使用的安全技术SSL (Secure Socket Layer)受限于网络通信端点。更准确的说,身份特性只能指定给常被多个Web Service共享的通信端点。
其他的安全技术,也就是说GSS-API (Generic Security Service Application Programmers Interface) 和基于GSS-API的安全机制(诸如SPKM (Simple Public Key Mechanism) 和Kerberos), 主要是为松散连接的体系结构而设计的。GSS-API 独立于传输和安全机制(安全机制独立性意味着密码技术、身份表示和数据签名等潜在的技术被完全封装了)。当今Web Service中最常用的安全机制仍然是SSL,但是SPKM和 Kerberos的使用也在不断增长。SPKM 是基于上面介绍的非对称密码技术原理的。所以它非常适合Web Service的分散环境。; Kerberos使用对称密码技术。如果您关心安全问题,支持这些高级安全选项的Web Services 产品和技术就是您所关心问题的最好答案。
运行中的安全:鉴别、授权和数据保密
鉴别、授权和数据保密是安全体系结构的三个主要基础。我们来看一看解释这些概念的一个简单示例吧。首先我们将看一看银行帐户功能的简单实现。请检查AccountImpl Java class的代码。
/*
* AccountImpl.java
*
* Created on December 13, 2001, 9:25 AM
*/
package com.systinet.demos.bank;
// imports of WASP security
import org.idoox.security.AuthResult;
import org.idoox.security.Credentials;
import org.idoox.security.PrincipalAuthenticator;
import org.idoox.security.server.Current;
import org.idoox.webservice.server.Initializable;
import org.idoox.webservice.server.WebServiceContext;
/**
* Account implementation
*/
public class AccountImpl
implements Account, Initializable
{
private double balance = 0;
private String number = "";
public AccountImpl()
{
this.number = ""+System.currentTimeMillis();
}
public void init(WebServiceContext context)
{
authenticate();
}
public void destroy()
{
// do nothing here
}
/**
* Deposits to the account
* @param amount amount of many to deposit
* @throws AuthenticationException if authentication fails
*/
synchronized public void deposit(double amount)
throws AuthenticationException
{
checkAuth();
this.balance += amount;
}
/**
* Withdraw from the account
* @param amount amount to withdraw
* @throws UnsufficientFundsException thrown if account doesn't hava enough funds
* @throws AuthenticationException if authentication fails
*/
synchronized public void withdraw(double amount)
throws UnsufficientFundsException, AuthenticationException
{
checkAuth();
if(amount < this.balance) {
this.balance = this.balance - amount;
}
else {
throw new UnsufficientFundsException("The withdrawal of " + amount +
" was requested but the balance is
only " +
this.balance+" .");
}
}
/**
* Returns the account balance
* @return the actual balance of the account
* @throws AuthenticationException if authentication fails
*/
synchronized public double getBalance()
throws AuthenticationException
{
checkAuth();
return this.balance;
}
/**
* Sets the account balance
* @param amount the actual balance of the account
* @throws AuthenticationException if authentication fails
*/
synchronized public void setBalance(double amount)
throws AuthenticationException
{
checkAuth();
this.balance = amount;
}
/**
* Returns the account number
* @return account number
* @throws AuthenticationException if authentication fails
*/
public String getAccountNumber()
throws AuthenticationException
{
checkAuth();
return this.number;
}
/**
* Sets the account number
* @param accountNumber account number
* @throws AuthenticationException if authentication fails
*/
public void setAccountNumber(String accountNumber)
throws AuthenticationException
{
checkAuth();
this.number = accountNumber;
}
/**
* Close the account
* @throws AuthenticationException if authentication fails
*/
public void close()
throws AuthenticationException
{
checkAuth();
org.idoox.webservice.server.WebServiceContext context =
org.idoox.webservice.server.WebServiceContext.getInstance();
org.idoox.webservice.server.LifeCycleService lc =
context.getLifeCycleService();
lc.disposeServiceInstance(this);
}
/**
* Creates and sets the security identity credentials if they are
* not alread set
*/
private synchronized void authenticate()
{
Current current = Current.getInstance();
if (current.getCredentials() == null) {
PrincipalAuthenticator auth = current.getAuthenticator();
AuthResult result = auth.authenticate("bank-server",
"password".getBytes());
if (result.resultCode != AuthResult.AUTH_STATUS_SUCCESS) {
System.err.println("Unable to authenticate");
}
current.setCredentials(new Credentials[] { result.creds });
}
}
/**
* Performs very simple authorization based on the hardcoded
* identity name, which is able to manipulate the account.
*
* @throws AuthenticationException if the client is not authorized
*/
private void checkAuth()
throws AuthenticationException
{
Current current = Current.getInstance();
Credentials credentials = current.getReceivedCredentials();
if(credentials != null) {
String caller = credentials.getName();
if(caller == null || !caller.equals("john")) {
throw new AuthenticationException("Access denied.");
}
}
}
}
|
图1: 统计web service的执行(AccountImpl.java)
与安全相关的全部代码出现在图1结束之处的两个方法authenticate 和checkAuth. 中。方法authenticate 打开本地证书存储器并索取受口令保护的公开信任级别(由身份秘密密钥和X.509证书表示),随后这将用于服务的鉴别。客户端代码几乎是一致的。请检查BankClient Java class 源代码,它在main 方法的开始之处进行鉴别。
/*
* This is a WASP client.
* BankClient.java
* Created on December 13, 2001, 10:41 AM
*/
package com.systinet.demos.bank;
import org.idoox.webservice.client.WebServiceLookup;
import org.idoox.wasp.Context;
// imports of WASP security
import org.idoox.security.AuthResult;
import org.idoox.security.Credentials;
import org.idoox.security.PrincipalAuthenticator;
import org.idoox.security.client.Current;
/**
* Bank client application
*/
public class BankClient
extends Object
{
/**
* Runs the demo on the client-side
* @param args the command line arguments
*/
public static void main (String args[])
throws Exception
{
String username = args[0];
String password = args[1];
if(username == null)
username = "john";
if(password == null)
password = "password";
System.out.println("Authenticating user "+username);
// init the lookup
WebServiceLookup lookup = (WebServiceLookup)
Context.getInstance(Context.WEBSERVICE_LOOKUP);
// obtain and set the crededentials
Current current = Current.getInstance();
PrincipalAuthenticator auth = current.getAuthenticator();
AuthResult result = auth.authenticate(username, password.getBytes());
if (result.resultCode != AuthResult.AUTH_STATUS_SUCCESS) {
System.err.println("Unable to authenticate");
return;
}
current.setCredentials(new Credentials[] { result.creds });
// get the proxy to the Web Service from the lookup
Account account = (Account)
lookup.lookup("http://localhost:6060/AccountImpl/", Account.class);
// now, call the methods on your Web Service interface
System.out.println("Account #" + account.getAccountNumber() + "
created.");
System.out.println("Putting $10 000 on account #" +
account.getAccountNumber() + " .");
account.deposit(10000);
System.out.println("Getting $7 000 from account #" +
account.getAccountNumber() + " .");
account.withdraw(7000);
System.out.println("Account #" + account.getAccountNumber()
+ " balance is "
+ account.getBalance());
account.close();
}
}
|
图2:银行的客户端执行(BankClient.java)
在客户端代码调用服务之后,将执行客户端和服务器方共有的鉴别。基本来说,公开信用(证书)将会被交换而且如果双方相互信任远程对方的证书,安全通信就可以开始进行了。
checkAuth 将执行基本的鉴别。首先,从到来的调用中得到调用方的信用度。我们的示例中,将检查客户端的名称而且如果它与'john'相匹配,鉴别就成功完成了。否则,鉴别就失败了。这种代码固化的有效性确认是最简单的情况。但是除非你的所有银行合作者都叫John,这仅仅只是适合于演示目的。实际情况中, Johns, Jacks和 Janes都存在, 像JAAS (Java 鉴别和授权服务)之类的方法应该可以用来将调用方的身份信息传送给Java代码并执行授权检查(通常是在一个策略文件中)。方法JAAS 支持Web Services 和 J2EE 应用服务器之间进行无缝的安全综合与集成。J2EE 应用服务器已经将方法JAAS用于鉴别和授权。
为了使用WASP 安全API的JAAS集成, Credentials接口会具有方法getSubject() ,它可以获得JAAS javax.security.auth.Subject 实例。
数据保密是Web Services安全体系结构的最后一个功能,但是这决不等于说它最不重要。数据保密保证收到的数据在传输过程中没有被恶意篡改并且它也十分关注数据的机密性。通常,数据是使用一些对称的密码加密的(例如, Tripple DES 或者 RC5),这是因为这种方法的速度比非对称方法要快得多。通常,非对称密码仅用于可靠的对称密钥交换。如果您看看我们实例中交换的报文,您应该可以看到完全加密的通信。在一些情况中,尤其是在复杂的报文路由情况中, 这也是很重要的,即只对报文的特定部分进行加密以便通信中介有权访问报文报头部分。类似地,只对要求身份核实的特定报文元素加以数据签名。 |