相信很多開發者都用過WebService來實現程序的面向服務,本文主要介紹WebService的身份識別實現方式,當然本文會提供一個不是很完善的例子,權當抱磚引玉了.
首先我們來介紹webservice下的兩種驗證方式,
一.通過集成windows身份驗證
通過集成windows方式解決webservice的安全問題是一個很簡潔,並且行之有效的解決方案,該方案的優點是比較安全,性能較好,當然因為與windows緊密的結合到了一起,缺點自然也很明顯了,第一,不便於移植,第二,要進行相關的配置部署工作(當然我們也可以用代碼來操作IIS,只不過比較麻煩,最近一直做自動化部署,所以一講到配置馬上就會聯想到怎么去自動部署)
具體怎么做呢?
服務器端:配置IIS虛擬目錄為集成windows身份驗證
客戶端:
Service1 wr = new Service1(); //web service實例
wr.Credentials = new NetworkCredential("administrator","123"); //用戶名密碼
lblTest.Text = wr.Add(2,2).ToString(); //調用Add的 web service方法
二.使用 SoapHeader(SOAP 標頭)自定義身份驗證
SoapHeader 多數情況下用來傳遞用戶身份驗證信息,當然它的作用遠不止如此,有待於在實際應用中發掘,體可以實現哪些東西大家有想法可以留言一起交流.
SoapHeader 使用步驟:
(1) 創建繼承自 System.Web.WebServices.SoapHeader 的自定義 SoapHeader 類型。
(2) 在 WebService 中創建擁有 public 訪問權限的自定義 SoapHeader 字段。
(3) 在需要使用 SoapHeader 的 WebMethod 上添加 SoapHeaderAttribute 訪問特性。SoapHeaderAttribute 構造必須指定 memberName 參數,就是我們在第二步中申明的字段名稱。
(4) 生成器會自動為客戶端生成同名的自定義 SoapHeader 類型,只不過比起我們在 WebService 端創建的要復雜一些。同時還會為代理類型添加一個 soapheaderValue 屬性。
下面展示一段SoapHeader的代碼,多余的方法將會在后面用到
客戶端:
class Program { static void Main(string[] args) { Service1 ws = new Service1(); ServiceCredential mycredential = new ServiceCredential(); mycredential.User = "gazi"; mycredential.Password="gazi"; ws.ServiceCredentialValue = mycredential; string mystr=ws.SayHello(); } }
服務端
public class Service1 : System.Web.Services.WebService { public ServiceCredential myCredential; [WebMethod] [SoapHeader("myCredential", Direction = SoapHeaderDirection.In)] public string SayHello() { return "hello"; } } public class ServiceCredential : SoapHeader { public string User; public string Password; public static bool ValideUser(string User,string Password) { return true; } public static void CheckUser(Object sender, WebServiceAuthenticationEvent e) { if (ValideUser(e.User, e.Password)) { return; } else { WebServiceAuthenticationModule module = sender as WebServiceAuthenticationModule; module.Result.AddRule("驗證錯誤", "不能確認您的身份,請檢查用戶名和密碼"); } } }
當我們擁有很多個類的時候,要添加一個或者刪除一個驗證方式(假設需要進行多種認證)是非常麻煩的,我們不可能跑到每個方法里面去加一個方法調用,這是災難性的工作,當然我們也可以用AOP來實現,Aop的話需要額外增加很多代碼或者直接引入第三方來做,但是我們可不可以有更簡便的方法呢?
OK,答案就是使用HttpModule,我們集成IHttpModule寫一個處理模塊,那么它的原理是什么呢?具體進行了哪些操作呢?我們的思路如下:
- HTTP Module 分析 HTTP 消息以檢查它們是不是 SOAP 消息。
- 如果 HTTP Module 檢測到 SOAP 消息,它會讀取 SOAP 標頭。
- 如果 SOAP 消息的 SOAP 標頭中有身份驗證憑據,HTTP Module 將引發一個自定義 global.asax 事件。
下面來看看我們的Module代碼
1 public class WebServiceAuthenticationModule : IHttpModule 2 { 3 private static WebServiceAuthenticationEventHandler 4 _eventHandler = null; 5 /// <summary> 6 /// 驗證事件.綁定到此事件可進行對用戶身份的識別 7 /// </summary> 8 public static event WebServiceAuthenticationEventHandler Authenticate 9 { 10 add { _eventHandler = value; } 11 remove { _eventHandler -= value; } 12 } 13 public Result Result = new Result(); 14 15 public void Dispose() 16 { 17 } 18 public void Init(HttpApplication app) 19 { 20 app.AuthenticateRequest = new 21 EventHandler(this.OnEnter); 22 Result.EndValid = new 23 EventHandler(this.OnCheckError); 24 } 25 26 /// <summary> 27 /// 驗證用戶身份 28 /// </summary> 29 /// <param name="e"></param> 30 private void OnAuthenticate(WebServiceAuthenticationEvent e) 31 { 32 if (_eventHandler == null) 33 return; 34 35 _eventHandler(this, e); 36 if (e.User != null) 37 e.Context.User = e.Principal; 38 } 39 40 public string ModuleName 41 { 42 get { return "WebServiceAuthentication"; } 43 } 44 45 void OnEnter(Object source, EventArgs eventArgs) 46 { 47 HttpApplication app = (HttpApplication)source; 48 HttpContext context = app.Context; 49 Stream HttpStream = context.Request.InputStream; 50 51 // Save the current position of stream. 52 long posStream = HttpStream.Position; 53 54 // If the request contains an HTTP_SOAPACTION 55 // header, look at this message.HTTP_SOAPACTION 56 if (context.Request.ServerVariables["HTTP_SOAPACTION"] == null) 57 return; 58 59 // Load the body of the HTTP message 60 // into an XML document. 61 XmlDocument dom = new XmlDocument(); 62 string soapUser; 63 string soapPassword; 64 65 try 66 { 67 dom.Load(HttpStream); 68 69 // Reset the stream position. 70 HttpStream.Position = posStream; 71 72 // Bind to the Authentication header. 73 soapUser = 74 dom.GetElementsByTagName("User").Item(0).InnerText; 75 soapPassword = 76 dom.GetElementsByTagName("Password").Item(0).InnerText; 77 } 78 catch (Exception e) 79 { 80 // Reset the position of stream. 81 HttpStream.Position = posStream; 82 83 // Throw a SOAP exception. 84 XmlQualifiedName name = new 85 XmlQualifiedName("Load"); 86 SoapException soapException = new SoapException( 87 "SOAP請求沒有包含必須的身份識別信息", name, e); 88 throw soapException; 89 } 90 // 觸發全局事件 91 OnAuthenticate(new WebServiceAuthenticationEvent 92 (context, soapUser, soapPassword)); 93 Result.OnEndValid(); 94 return; 95 } 96 void OnCheckError(Object sender, EventArgs e) 97 { 98 if (Result.BrokenRules.Count == 0) 99 { 100 return; 101 } 102 else 103 { 104 HttpApplication app = HttpContext.Current.ApplicationInstance; 105 app.CompleteRequest(); 106 app.Context.Response.Write(Result.Error); 107 } 108 } 109 }
Authenticate事件是一個靜態的變量,這樣我們可以在程序的外部來訂閱和取消訂閱事件(非靜態的public 事件在外部也是不能進行訂閱和取消訂閱事件的,這也是事件和委托的一個區別之一)
下面是我們的事件參數以及委托
1 public delegate void WebServiceAuthenticationEventHandler(Object sender, WebServiceAuthenticationEvent e); 2 3 /// <summary> 4 /// 封裝的事件參數 5 /// </summary> 6 public class WebServiceAuthenticationEvent : EventArgs 7 { 8 private IPrincipal _IPrincipalUser; 9 private HttpContext _Context; 10 private string _User; 11 private string _Password; 12 13 public WebServiceAuthenticationEvent(HttpContext context) 14 { 15 _Context = context; 16 } 17 18 public WebServiceAuthenticationEvent(HttpContext context, 19 string user, string password) 20 { 21 _Context = context; 22 _User = user; 23 _Password = password; 24 } 25 public HttpContext Context 26 { 27 get { return _Context; } 28 } 29 public IPrincipal Principal 30 { 31 get { return _IPrincipalUser; } 32 set { _IPrincipalUser = value; } 33 } 34 public void Authenticate() 35 { 36 GenericIdentity i = new GenericIdentity(User); 37 this.Principal = new GenericPrincipal(i, new String[0]); 38 } 39 public void Authenticate(string[] roles) 40 { 41 GenericIdentity i = new GenericIdentity(User); 42 this.Principal = new GenericPrincipal(i, roles); 43 } 44 public string User 45 { 46 get { return _User; } 47 set { _User = value; } 48 } 49 public string Password 50 { 51 get { return _Password; } 52 set { _Password = value; } 53 } 54 public bool HasCredentials 55 { 56 get 57 { 58 if ((_User == null) || (_Password == null)) 59 return false; 60 return true; 61 } 62 } 63 }
我們在Global.asax的Application_Start方法里面把前面介紹的靜態方法ServiceCredential.CheckUser訂閱到我們Authenticate事件上,前面提到的增加和刪除多種認證方式就是通過這種方法實現的.
protected void Application_Start(object sender, EventArgs e) { WebServiceAuthenticationModule.Authenticate += ServiceCredential.CheckUser; }
我們在ServiceCredential.ValideUser方法設置了返回false,這是針對測試的一個配置,實際情況下我們可以和數據庫結合起來寫一個認證 運行上面講解SoapHeader的那段代碼,你會發現我們的認證已經有效了.