用WSE在Web服务中验证用户身份

一、Web服务安全与WS-Security   毫无疑问,SOAP和XML Web服务在交互操作和标准上已经完全改变了电子商务领域的格局。   然而直到最近,在Web服务技术领域仍然存在着一些缺陷,那就是处理消息级别的安全、认证、 加密 、数字签名、路由和附件等问题的能力。为了解决这些安全问题,像IBM、 Microsoft 和Verisign这样的公司和组织正牵头合作制定统一的Web服务安全规范,以便利用它们原有的Web服务交互操作概念和商业模型,他们推出了WS-Security等规范。可以这么说,自从SOAP规范形成以后,WS-Security规范及其后续的工作可能是Web服务技术领域的一次最重要的进步。   随着WS-Security规范的定稿,各大软件厂商开始认真地考虑为其产品提供使用相同Web服务安全语言的接口和 编程 工具箱,Web服务开发者也将能够使用这些厂商提供的工具加强他们所开发的Web服务的安全性。    二、 WSE 安全性能简介    Microsoft 推出了Web Services Enhancements 1.0 for .NET (以下简称 WSE ),它是一个类库,用于实现高级 Web 服务协议,这也是该公司的第一个使用WS-Security等规范实现SOAP消息安全的工具套件。   保护Web服务安全的一个很重要的环节就是保护其SOAP消息传递的安全。   使用 WSE 后,SOAP消息可以自己验证其完整性,并可使用定义在WS-Security规范中的机制 加密 。    WSE 1.0支持的所有WS-Security特性都是通过实现SecurityInputFilter和 SecurityOutputFilter对象的安全性输入输出过滤器实现的,它支持的安全特性有:   1. 数字签名   2. 加密   3. 使用用户名令牌签名并 加密   4. 使用X.509证书签名并 加密   5. 使用自定义二进制令牌签名并 加密    WSE 1.0不支持Security Assertion Markup Language(SAML,安全声明标注语言),但 Microsoft 公司正积极在其 .NET Server中实现SAML体系结构。当然,开发者自己可以自由的实现SAML。唯一的不足是还不能使用WSDL描述遵循WS-Security规范的Web服务的WS-Security接口。    WSE 的体系结构模型基于处理入站和出站SOAP消息的过滤器管道。它是建立在已有的SOAPExtension类的基础上的,有使用过SOAPExtension类行进压缩、 加密 、记录和其它操作经验的开发者会发现他们对 WSE 其实很熟悉。    WSE 提供了一个 Microsoft .Web.Services.SoapContext类,让我们可以处理WS-Security SOAP头和其它入站的SOAP消息头,同时可为出站的SOAP消息添加WS-Security头。 WSE 还有一个包装类为SOAP请求和响应添加SOAPContext(与HttpContext类似),同时服务器使用一个SOAPExtension类“ Microsoft .Web.Services.WebServicesExtension”,让我们可以验证入站的SOAP消息,还提供了我们可从我们的WebMethod中访问的请求和响应SoapContext。   学习使用 WSE 最大的障碍在于有时很难理解 Microsoft 的技术文档和相关文章,即使对于那些有丰富经验的高级开发人员来说也是如此,并且关于这方面的文章很少。在本文中,我将给出一个简单的例子,介绍如何使用 WSE 实现基本的用户名令牌的验证过程,以保证Web服务的安全。    三、设置 WSE 环境   为了设置基本的 WSE 环境,我们需要配置 ASP .NET 应用程序,使其能够使用 WSE SOAPExtension。最简单的方法是把所需的/configuration/system.web/webServices/soapExtensionTypes/Add元素添加到你的Web服务虚拟目录中的web.config里,如下所示: <webServices> <soapExtensionTypes> <add type=" Microsoft .Web.Services.WebServicesExtension, Microsoft .Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" /> </soapExtensionTypes> </webServices>   注意type属性必须写在一行中,但是在文中考虑到篇幅的问题需要把它分为几行,所以请读者多加注意。而且要注意,在开始使用 WSE 之前,我们必须在工程中加入对 Microsoft .Web.Services.dll的引用。    四、基本的用户名令牌认证   在我们数字签名SOAP消息之前,必须先弄清楚谁正在签名。因此,我们将探讨一下用户名令牌(UsernameToken)的概念,同时了解 WSE 如何允许我们验证用户名令牌。   为了在Web服务中使用 WSE 验证用户名/密码,我们需要知道 WSE 在这方面为我们提供了什么?WS-Security定义了一个UsernameToken元素,它提供了基本用户名/密码验证的方法。如果你有使用HTTP的经验,那么你会发现UsernameToken与Basic Authentication非常类似。有三种用户名令牌,但是通常情况下我们只对最后两种最感兴趣: <!--明文密码--> <UsernameToken> <Username>user1</Username> <Password Type="wsse:PasswordText">suangywang</Password> </UsernameToken>   这种方法使用明文密码。我们不难想象,在服务器上将进行核对数据库,验证用户名与密码,看是否有匹配的用户名/密码对这一系列验证操作。 <!--密码摘要--> <UsernameToken> <Username>user1</Username> <Password Type="wsse:PasswordDigest"> QSMAKo67+vzYnU9TcMSqOFXy14U= </Password> </UsernameToken>   这种方法发送一个密码摘要(digest)代替明文密码。使用密码摘要,密码就不会通过网络发送,这样 黑客 就不太可能算出Web服务的密码。密码摘要是用散列函数计算的。这个过程只是单向的,意味着将函数反向并找到对应于摘要的消息是不可能的,因为该过程以这样一种方式实现,所以找到散列到同一摘要的两条不同密码在计算上难以实现。但是 黑客 可以发送散列密码,然后冒充原始发送人被验证。为了避免这个问题,Web Services Security Addendum(Web服务安全补遗)已经增加一个辅助的保护措施。补遗中规定必须发送密码的摘要版本,而不仅仅发送散列密码。这个摘要信息包含一个密码散列,标识请求的唯一的Nonce和创建时间。因此绝对不会出现相同的两个密码散列。如下所示是修正后的用户名令牌UsernameToken。 <!--修正后的用户名令牌--> <wsse:UsernameToken xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="SecurityToken-59845323-5dcb-4a6b-a7fb-94a0d7357a20"> <wsse:Username>User1</wsse:Username> <wsse:Password Type="wsse:PasswordDigest"> gpBDXjx79eutcXdtlULIlcrSiRs= </wsse:Password> <wsse:Nonce> h52sI9pKV0BVRPUolQC7Cg== </wsse:Nonce> <wsu:Created>2003-6-20T21:16:50Z</wsu:Created> </wsse:UsernameToken>   虽然每个合法请求都有一个不同的散列,但是你也必须防止恶意用户把其他用户的合法请求中的整个UsernameToken拿出放入自己的非法请求中。你可以使用Timestamp(时间戳标头)来最小化这种危险。时间戳标头用来表示消息的创建时间和过期时间,指明消息的周期以及何时可以认为该消息失效。 例如,你可能想指定消息在40秒以后失效,并且超过40秒服务器就不会接收UsernameToken。但是机器之间的时钟同步问题可能会造成有效的请求被拒绝的情况。所以使用时间戳也并不是一个尽善尽美的解决方法。为了解决这个问题,Web服务可以保存一张最近收到的UsernameToken的Nonce值的表,如果收到的一个请求的Nonce值已经被使用了,那么就绝对不会接受这个请求。如果你接收几个使用相同Nonce的请求,那么你要考虑把这几条请求全部丢弃,因为很有可能先到的请求是非法请求。还要了解到Nonce核对技术并不能防止恶意用户截获合法的输入信息,并把原始信息中的UsernameToken加入自己的消息,然后发送到目的地。这时就需要为消息添加数字签名或安全证书,以保护其不受攻击。数字签名和安全证书的相关知识在本文中不会涉及,请读者查阅相应文献。   所有的散列保护都需要消息发送端和接收端知道用户的密码。在客户端,人们期望系统能够提示用户输入密码。而在服务器端,需要保存带有有效用户名/密码对的表,以供系统查找。我们下面将介绍 WSE 如何使用一个Password Provider(密码提供者)机制来解决这两个问题。    五、IPasswordProvider接口    WSE 定义了一个 Microsoft .Web.Services.Security.IPasswordProvider接口类,我们必须实现这个类来注册一个密码提供者。这个接口有一个方法GetPassword,它接收一个 Microsoft .Web.Services.Security.UsernameToken作为输入参数,该方法返回指定用户的密码。其思想是你可以使用任何你想用的机制保存有效的用户名/密码对,然后提供了一个实现IPasswordProvider接口的类,来让 WSE 访问你的特定密码存储机制。你甚至可以执行你自己的UsernameToken的摘要(Digest)和散列(Hash)的组合,甚至使用一个共享的密码,以进一步控制你的认证基础结构。   为了把你特定的Password Provider(密码提供者)告诉 WSE ,你必须配置合适的 WSE 设置。首先要添加一个 Microsoft .Web.Services元素到应用程序的配置文件中的配置元素中。还要指定可以读懂特定配置信息的 WSE 类。可以把下面的configSections添加到机器上的Machine.config或单独的

 

[NextPage]  

 

 

[NextPage]

 

ctions> <section name="microsoft.web.services" type=" Microsoft .Web.Services.Configuration.WebServicesConfiguration, Microsoft .Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections>   在本例中,我们将使用Northwind数据库Employees表的一个修改版本来进行查询任务。因为PasswordProvider接口需要返回一个与UsernameToken对象的密码部分匹配的实际密码,所以通常,我们只需要使用 WSE 加密 我们的用户名和密码,然后再通过网络传送给Web服务。   如果你在Solution Explorer中选中你的工程并在其上点击右键,你将看到在底部增加了一个新的菜单“ WSE Settings”,你可以在其中设置所有重要的配置和其它使用 WSE 的配置:   这可让我们很容易的设置Password Provider Implementation(密码提供者实现)元素,Decryption Key Provider Implementation(解密钥提供者)元素,X.509 Certificate(X.509 证书)设置,甚至是我们希望使用的Binary Security Tokens(二进制安全令牌)。此外,其他的选项卡还可以配置用于 WSE 管道的输入输出过滤器,配置路由,启动诊断功能等等。虽然它不能做我们想做的每件事,但这是 WSE 易用化的一个良好的开端。   PasswordProvider安全元素是web.config中的<configuration>父元素的一个子元素,它告诉 WSE 你使用哪个类来实现PasswordProvider接口: <microsoft.web.services>  <security>   <!-- NAMESPACE . CLASSNAME , ASSEMBLYNAME -->   <passwordProvider type=" WSE Security. WSE PasswordProvider, WSE Security" />  </security> </microsoft.web.services>   让我们看看在本例中如何实现它: namespace WSE Security {  public class WSE PasswordProvider : IPasswordProvider  {   public string GetPassword(UsernameToken token)   {    try    {     SqlConnection cn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["SqlConn"].ToString());     cn.Open();     SqlCommand cmd = new SqlCommand("SELECT Username, password from Employees where username =" + token.Username + "",cn);     SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);     dr.Read();     return dr["password"].ToString();    }    catch(Exception ex)    {     throw new Exception (ex.Message);    }   }  } }   上面我们给出的代码可以完全实现IPasswordProvider接口,通过用户名/密码来验证一个用户,当然了,还可以把它做得更复杂一些,这请读者们自己去完成。实际上,我们在 编程 的过程中基本没有写太多用户验证的代码,大部分工作都由 WSE 暗中处理了。    六、编写一个使用WS-Security的WebMethod   现在我们需要创建一个使用WS-Security的WebMethod。 这里,我实现了一个简单的方法,它运行Northwind数据库的CustOrderHist存储过程,接收一个字串UserID作为唯一的参数,并返回一个DataSet。如果调用Web服务的客户端可以通过消息级UsernameToken验证,那么就可以取回DataSet。如果不能通过验证的话,客户端将得到一个异常,告知它不能通过验证。 WSE 的优点在于你只要付出一点点劳动就可以了,大部分的工作已经由 WSE 在暗中为你完成了,所以你可以把大部分时间花费在构建Web服务的内容上,而不是为了构建一个安全的Web服务机制而疲于奔命。 [WebMethod] public DataSet CustOrderHist(string CustId) {  // 只接受 SOAP格式的请求  SoapContext requestContext = HttpSoapContext.RequestContext;  if(requestContext==null)  {   throw new ApplicationException("Non-SOAP request!");  }  bool valid=false;  try  {   foreach(SecurityToken tkn in requestContext.Security.Tokens)   {    if(tkn is UsernameToken)    valid=true;   }  }  catch(Exception ex)  {   throw new Exception( ex.Message + ": " + ex.InnerException.Message);  }  if (valid==false)   throw new ApplicationException("Invalid or Missing Security Token.");  SqlConnection cn;  SqlDataAdapter da;  DataSet ds;  cn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["SqlConn"].ToString());  cn.Open();  da = new SqlDataAdapter("custorderHist " +CustId + "", cn);  ds = new DataSet();  da.Fill(ds, "CustOrderHist");  return ds; }   使用上面的WebMethod,我们就可在服务器上实现验证用户名/密码的操作。WebMethod必须引用 Microsoft .Web.Services和 Microsoft .Web.Services.Security域名空间。现在,我们要构建一个 ASP .NET 客户端,这个客户端能够发送验证所需的SOAP头,并可调用我们的Web服务方法。    七、构建 WSE ASP .NET 客户端   对于客户端, WSE 提供了继承于System.Web.Services.Protocols.SoapHttpClientProtocol类的 Microsoft .Web.Services.WebServicesClientProtocol类。当你在 Visual Studio.NET 中选择“Add Web Reference”选项的时候,或者使用WSDL.exe程序创建基于WSDL的客户端代码时,你需要使用SoapHttpClientProtocol类。你可做的就是使用 Visual Studio.NET 中的“Add Web Reference”选项或者WSDL.exe程序为你的客户端生成代理类,然后把代理类从继承于SoapHttpClientProtocol改为继承于WebServicesClientProtocol。这样代理类就有了RequestSoapContext和ResponseSoapContext属性,你可以使用它们访问你发送或接收的WS-Security头。在 C# 工程中,如果你已经使用了“Add Web Reference”选项,你可以点击Solution Explorer中的“Show All Files”按钮,点击这个按钮就可在Solution Explorer的Web References结点中显示Reference.cs文件,让你可以编辑这个文件。   为了创建正确的UsernameToken和在消息级调用Web服务的代理方法,需要使用下面的代码: private void Button1_Click(object sender, System.EventArgs e) {  localhost.SecurityService Wse wse =new localhost.SecurityService Wse ();  UsernameToken tkn = new UsernameToken(txtUsername.Text,txtPassword.Text,PasswordOption.SendHashed);   wse .RequestSoapContext.Security.Tokens.Add (tkn);  try  {   DataSet ds= wse .CustOrderHist(txtCustID.Text);   DataGrid1.DataSource=ds;   DataGrid1.DataBind();  }  catch(Exception ex)  {   DataGrid1.Visible=false;   lblMessages.Text=ex.Message;  } }   我们要做的就是从客户端的两个文本输入框txtUsername和txtPassword中取得输入字串,然后使用PasswordOption.SendHashed把它们结合起来创建一个有效的UserNameToken。当调用Web服务时, WSE SOAP扩展验证请求的一般格式,然后核对密码散列并从我们的PasswordProvider方法中取得的密码。如果两者匹配,我们就可调用Web服务方法,客户端返回数据集,显示在一个网格中。   我们现在已经创建了一个完整的使用 WSE 配合数据库验证SHA1摘要散列用户名/密码的Web服务,希望读者们能通过本文了解到使用 WSE 保证Web服务安全的基本措施和方法,并能在实际工作中合理的去应用。   在文章的最后,我们给出修改Northwind数据库Employees表的SQL脚本,给这个表添加了所需的username和password列,同时在这个表中插

 

[NextPage]  

 

 

[NextPage]

 

了一条新纪录,其Firstname、Lastname、Username、Password和roles字段分别为“User”,“One”,“user1”,“pass1”和“user”。 USE NORTHWIND GO ALTER TABLE [dbo]. ADD [Username] [varchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL , [Password] [varchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL , [roles] [varchar] (250) COLLATE SQL_Latin1_General_CP1_CI_AS NULL GO INSERT INTO EMPLOYEES (Firstname, Lastname,Username, [Password], roles) VALUES(User,One, user1, pass1, user) GO

 

  

 

 

[NextPage]

 

ctions> <section name="microsoft.web.services" type=" Microsoft .Web.Services.Configuration.WebServicesConfiguration, Microsoft .Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections>   在本例中,我们将使用Northwind数据库Employees表的一个修改版本来进行查询任务。因为PasswordProvider接口需要返回一个与UsernameToken对象的密码部分匹配的实际密码,所以通常,我们只需要使用 WSE 加密 我们的用户名和密码,然后再通过网络传送给Web服务。   如果你在Solution Explorer中选中你的工程并在其上点击右键,你将看到在底部增加了一个新的菜单“ WSE Settings”,你可以在其中设置所有重要的配置和其它使用 WSE 的配置:   这可让我们很容易的设置Password Provider Implementation(密码提供者实现)元素,Decryption Key Provider Implementation(解密钥提供者)元素,X.509 Certificate(X.509 证书)设置,甚至是我们希望使用的Binary Security Tokens(二进制安全令牌)。此外,其他的选项卡还可以配置用于 WSE 管道的输入输出过滤器,配置路由,启动诊断功能等等。虽然它不能做我们想做的每件事,但这是 WSE 易用化的一个良好的开端。   PasswordProvider安全元素是web.config中的<configuration>父元素的一个子元素,它告诉 WSE 你使用哪个类来实现PasswordProvider接口: <microsoft.web.services>  <security>   <!-- NAMESPACE . CLASSNAME , ASSEMBLYNAME -->   <passwordProvider type=" WSE Security. WSE PasswordProvider, WSE Security" />  </security> </microsoft.web.services>   让我们看看在本例中如何实现它: namespace WSE Security {  public class WSE PasswordProvider : IPasswordProvider  {   public string GetPassword(UsernameToken token)   {    try    {     SqlConnection cn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["SqlConn"].ToString());     cn.Open();     SqlCommand cmd = new SqlCommand("SELECT Username, password from Employees where username =" + token.Username + "",cn);     SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);     dr.Read();     return dr["password"].ToString();    }    catch(Exception ex)    {     throw new Exception (ex.Message);    }   }  } }   上面我们给出的代码可以完全实现IPasswordProvider接口,通过用户名/密码来验证一个用户,当然了,还可以把它做得更复杂一些,这请读者们自己去完成。实际上,我们在 编程 的过程中基本没有写太多用户验证的代码,大部分工作都由 WSE 暗中处理了。    六、编写一个使用WS-Security的WebMethod   现在我们需要创建一个使用WS-Security的WebMethod。 这里,我实现了一个简单的方法,它运行Northwind数据库的CustOrderHist存储过程,接收一个字串UserID作为唯一的参数,并返回一个DataSet。如果调用Web服务的客户端可以通过消息级UsernameToken验证,那么就可以取回DataSet。如果不能通过验证的话,客户端将得到一个异常,告知它不能通过验证。 WSE 的优点在于你只要付出一点点劳动就可以了,大部分的工作已经由 WSE 在暗中为你完成了,所以你可以把大部分时间花费在构建Web服务的内容上,而不是为了构建一个安全的Web服务机制而疲于奔命。 [WebMethod] public DataSet CustOrderHist(string CustId) {  // 只接受 SOAP格式的请求  SoapContext requestContext = HttpSoapContext.RequestContext;  if(requestContext==null)  {   throw new ApplicationException("Non-SOAP request!");  }  bool valid=false;  try  {   foreach(SecurityToken tkn in requestContext.Security.Tokens)   {    if(tkn is UsernameToken)    valid=true;   }  }  catch(Exception ex)  {   throw new Exception( ex.Message + ": " + ex.InnerException.Message);  }  if (valid==false)   throw new ApplicationException("Invalid or Missing Security Token.");  SqlConnection cn;  SqlDataAdapter da;  DataSet ds;  cn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["SqlConn"].ToString());  cn.Open();  da = new SqlDataAdapter("custorderHist " +CustId + "", cn);  ds = new DataSet();  da.Fill(ds, "CustOrderHist");  return ds; }   使用上面的WebMethod,我们就可在服务器上实现验证用户名/密码的操作。WebMethod必须引用 Microsoft .Web.Services和 Microsoft .Web.Services.Security域名空间。现在,我们要构建一个 ASP .NET 客户端,这个客户端能够发送验证所需的SOAP头,并可调用我们的Web服务方法。    七、构建 WSE ASP .NET 客户端   对于客户端, WSE 提供了继承于System.Web.Services.Protocols.SoapHttpClientProtocol类的 Microsoft .Web.Services.WebServicesClientProtocol类。当你在 Visual Studio.NET 中选择“Add Web Reference”选项的时候,或者使用WSDL.exe程序创建基于WSDL的客户端代码时,你需要使用SoapHttpClientProtocol类。你可做的就是使用 Visual Studio.NET 中的“Add Web Reference”选项或者WSDL.exe程序为你的客户端生成代理类,然后把代理类从继承于SoapHttpClientProtocol改为继承于WebServicesClientProtocol。这样代理类就有了RequestSoapContext和ResponseSoapContext属性,你可以使用它们访问你发送或接收的WS-Security头。在 C# 工程中,如果你已经使用了“Add Web Reference”选项,你可以点击Solution Explorer中的“Show All Files”按钮,点击这个按钮就可在Solution Explorer的Web References结点中显示Reference.cs文件,让你可以编辑这个文件。   为了创建正确的UsernameToken和在消息级调用Web服务的代理方法,需要使用下面的代码: private void Button1_Click(object sender, System.EventArgs e) {  localhost.SecurityService Wse wse =new

(txtUsername.Text,txtPassword.Text,PasswordOption.SendHashed);   wse .RequestSoapContext.Security.Tokens.Add (tkn);  try  {   DataSet ds= wse .CustOrderHist(txtCustID.Text);   DataGrid1.DataSource=ds;   DataGrid1.DataBind();  }  catch(Exception ex)  {   DataGrid1.Visible=false;   lblMessages.Text=ex.Message;  } }   我们要做的就是从客户端的两个文本输入框txtUsername和txtPassword中取得输入字串,然后使用PasswordOption.SendHashed把它们结合起来创建一个有效的UserNameToken。当调用Web服务时, WSE SOAP扩展验证请求的一般格式,然后核对密码散列并从我们的PasswordProvider方法中取得的密码。如果两者匹配,我们就可调用Web服务方法,客户端返回数据集,显示在一个网格中。   我们现在已经创建了一个完整的使用 WSE 配合数据库验证SHA1摘要散列用户名/密码的Web服务,希望读者们能通过本文了解到使用 WSE 保证Web服务安全的基本措施和方法,并能在实际工作中合理的去应用。   在文章的最后,我们给出修改Northwind数据库Employees表的SQL脚本,给这个表添加了所需的username和password列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值