用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規範中的機制加密

  WSE1.0支持的所有WS-Security特性都是通過實現SecurityInputFilter和 SecurityOutputFilter對象的安全性輸入輸出過濾器實現的,它支持的安全特性有:

  1. 數字簽名

  2. 加密

  3. 使用用戶名令牌簽名並加密

  4. 使用X.509證書籤名並加密

  5. 使用自定義二進制令牌簽名並加密

  WSE1.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或單獨的Web.config中。

<configSections>
<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="WSESecurity.WSEPasswordProvider, WSESecurity" />
 </security>
</microsoft.web.services>

  讓我們看看在本例中如何實現它:

namespace WSESecurity
{
 public class WSEPasswordProvider : 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.SecurityServiceWse wse=new localhost.SecurityServiceWse();
 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列,同時在這個表中插入了一條新紀錄,其Firstname、Lastname、Username、Password和roles字段分別爲“User”, “One”,“user1”,“pass1”和“user”。

USE NORTHWIND
GO
ALTER TABLE [dbo].[Employees]
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

 

發佈了57 篇原創文章 · 獲贊 2 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章