WebService登陸驗證四種方式
在這個WEB API橫行的時代,講WEB Service技術卻實顯得有些過時了,過時的技術並不代表無用武之地,有些地方也還是可以繼續用他的,我之所以會講解WEB Service,源於我最近面試時被問到相關問題,我這裏只是重新復習一下並總結一下,給新手們指指路,大牛們可以無視之,當然不足之處還請大家指教,謝謝!
WEB Service身份驗證,網上已有許多的相關文章,總結起來有:基於自定義SoapHeader驗證、Form驗證、集成Windows身份驗證、服務方法加入一個或幾個驗證參數;下面就不廢話了,直接分享實現的代碼吧,中間有涉及註意的地方,我會有說明文字的。
1.基於自定義SoapHeader驗證
定義服務:(註意UserValidationSoapHeader必需有無參構造函數,否則無法序列化)
//UserValidationSoapHeader: public class UserValidationSoapHeader : SoapHeader { public string UserName { get; set; } public string Password { get; set; } public UserValidationSoapHeader() { } public bool IsValid() { if (string.IsNullOrEmpty(UserName) || string.IsNullOrEmpty(Password)) { throw new Exception("用戶名及密碼不能為空!"); } if (!string.Equals(UserName, "admin") || !string.Equals(Password, "123456")) { throw new Exception("用戶名或密碼不正確!"); } return true; } } //SoapHeaderWebService: [WebService(Namespace = "http://www.zuowenjun.cn")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // 若要允許使用 ASP.NET AJAX 從腳本中調用此 Web 服務,請取消對下行的註釋。 [System.Web.Script.Services.ScriptService] public class SoapHeaderWebService : System.Web.Services.WebService { public UserValidationSoapHeader userValidation; [WebMethod(Description="問候")] [SoapHeader("userValidation")] public string HelloTo(string name) { if (userValidation.IsValid() && !string.IsNullOrEmpty(name)) { return "Hello " + name; } return "Hello World!"; } }
客戶端先通過服務地址引用服務,服務引用有兩種,分為服務引用和WEB服務引用(後面涉及到服務引用的不再重述)
服務引用:(這裏是按照WCF的模式來生成WEB服務代理類)
WEB服務引用:
(如果通過項目右鍵,則可以直接點擊“添加WEB引用”到此畫面)
客戶端使用如下:
private void CallWebServiceFromWebReference() { try { var svc = new RefWebSoapHeaderWebService.SoapHeaderWebService(); svc.UserValidationSoapHeaderValue = new RefWebSoapHeaderWebService.UserValidationSoapHeader() { UserName = "admin", Password = "123456" }; string result = svc.HelloTo(TextBox1.Text); Label1.Text = result; } catch (Exception ex) { Label1.Text = ex.Message; } } private void CallWebServiceFromServiceReference() { try { var svc = new RefSoapHeaderWebService.SoapHeaderWebServiceSoapClient(); var header = new RefSoapHeaderWebService.UserValidationSoapHeader(); header.UserName = "admin"; header.Password = "123456"; string result = svc.HelloTo(header, TextBox1.Text); Label1.Text = result; } catch (Exception ex) { Label1.Text = ex.Message; } } protected void Button1_Click(object sender, EventArgs e) { if (RadioButtonList1.SelectedValue == "1") { CallWebServiceFromServiceReference(); } else { CallWebServiceFromWebReference(); } }
上述代碼我針對兩種WEB服務引用作了不同的使用實例,關鍵看方法調用,服務引用下生成的服務方法參數中會自動加入一個soapHeader的參數,而WEB服務引用則沒有,我感覺采用WEB服務引用基於這種驗證比較方便,因為只需將soapHeader實例賦值一次就可以多次調用不同的服務方法。
2.Form驗證
定義服務:
//專門用於登錄的WEB服務: [WebService(Namespace = "http://www.zuowenjun.cn")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // 若要允許使用 ASP.NET AJAX 從腳本中調用此 Web 服務,請取消對下行的註釋。 [System.Web.Script.Services.ScriptService] public class FormAuthWebService : System.Web.Services.WebService { [WebMethod] public bool Login(string username, string password) { return UserBusiness.Login(username, password); } } //正常的WEB服務: [WebService(Namespace = "http://www.zuowenjun.cn")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // 若要允許使用 ASP.NET AJAX 從腳本中調用此 Web 服務,請取消對下行的註釋。 [System.Web.Script.Services.ScriptService] public class FormAuthHelloWebService : System.Web.Services.WebService { [WebMethod] public string HelloTo(string name) { if (!string.IsNullOrEmpty(name)) { return "Hello " + name; } return "Hello World"; } [WebMethod] public void Logout() { UserBusiness.Logout(); } } //業務輔助類: public static class UserBusiness { public static bool Login(string userName, string password) { if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) { throw new Exception("用戶名及密碼不能為空!"); } if (!string.Equals(userName, "admin") || !string.Equals(password, "123456")) { throw new Exception("用戶名或密碼不正確!"); } SaveLoginStauts(userName); return true; } public static bool IsAuthenticated() { return HttpContext.Current.Request.IsAuthenticated; } public static void Logout() { System.Web.Security.FormsAuthentication.SignOut(); } private static void SaveLoginStauts(string userName) { System.Web.Security.FormsAuthentication.SetAuthCookie(userName, false); } }
從上面的代碼可以看出,這裏采用了基於ASP.NET FORM驗證來保存登錄狀態,其它代碼與正常無異
因為采用了ASP.NET FORM驗證,所以web.config需要加入以下配置:
//在system.web節點加入以下配置,因為我將WEB服務全部放到了Services目錄下,所以僅對該目錄進行了限制 <authentication mode="Forms"> <forms name="auth.webservice" loginUrl="~/Services/FormAuthWebService.asmx/Login"> </forms> </authentication> <webServices> <protocols> <add name="HttpSoap"/> <add name="HttpPost"/> <add name="HttpGet"/> <add name="Documentation"/> </protocols> </webServices> </system.web> <location path="Services"> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </location>
註意以下2點:
1.上面loginUrl="~/Services/FormAuthWebService.asmx/Login",這裏我直接將未登錄直接轉到Login,原因是如果不加Login,那麽你將無法在WEB瀏覽器上進行登錄調試及其它服務方法的調試。
2.由於采用了FORM驗證,且禁止匿名訪問,造成無法正常引用服務,原因是服務引用也是一次HTTP請求,默認是未登錄的情況,你去引用受限的服務地址,它就會自動跳轉到登錄的服務地址,所以針對這個問題,我采用了兩種辦法,第一種是:先不要禁止匿名訪問,當服務引用成功後,再加上該配置,第二種是:不直接引用服務,采用代碼動態請求服務方法來進行相關的調用,客戶端使用代碼如下:
//第一種:采用服務引用: protected void Button2_Click(object sender, EventArgs e) { try { CookieContainer cookieContainer = new CookieContainer(); var svc = new RefFormAuthLoginWebService.FormAuthWebService(); svc.CookieContainer = cookieContainer; //共享cookie容器 if (svc.Login("admin", "123456")) { var svc2 = new RefFormAuthWebService.FormAuthHelloWebService(); svc2.CookieContainer = cookieContainer;//共享cookie容器 Label2.Text = svc2.HelloTo(TextBox2.Text); } } catch (Exception ex) { Label2.Text = ex.Message; } } //第二種:采用動態請求調用: protected void Button2_Click(object sender, EventArgs e) { try { CookieContainer cookieContainer = new CookieContainer(); var result = CallWebServiceFromHttpWebRequest("FormAuthWebService.asmx/Login", "username=admin&password=123456", cookieContainer); Label2.Text = result.SelectSingleNode("//ns:boolean", GetXmlNamespaceManager(result.NameTable)).InnerText; var result2 = CallWebServiceFromHttpWebRequest("FormAuthHelloWebService.asmx/HelloTo", "name=" + HttpUtility.UrlEncode(TextBox2.Text, Encoding.UTF8), cookieContainer); Label2.Text = result2.SelectSingleNode("//ns:string", GetXmlNamespaceManager(result2.NameTable)).InnerText; catch (Exception ex) { Label2.Text = ex.Message; }
輔助方法定義如下:
private XmlDocument CallWebServiceFromHttpWebRequest(string serviceUrl,string serviceParams,CookieContainer cookieContainer) { HttpWebRequest request =(HttpWebRequest)WebRequest.Create("http://localhost:8768/Services/" + serviceUrl); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.Credentials = CredentialCache.DefaultCredentials; byte[] data = Encoding.UTF8.GetBytes(serviceParams);//參數 request.ContentLength = data.Length; request.CookieContainer = cookieContainer; //共享cookie容器 Stream writer = request.GetRequestStream(); writer.Write(data, 0, data.Length); writer.Close(); WebResponse response = request.GetResponse(); StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); String retXml =sr.ReadToEnd(); sr.Close(); XmlDocument doc = new XmlDocument(); doc.LoadXml(retXml); return doc; } private XmlNamespaceManager GetXmlNamespaceManager(XmlNameTable table) { XmlNamespaceManager nsMgr = new XmlNamespaceManager(table); nsMgr.AddNamespace("ns", "http://www.zuowenjun.cn"); return nsMgr; }
這裏說明一下,不論是采用服務引用還是動態調用服務,均需確保引用同一個COOKIE容器,而且在動態調用服務時,還需註意最終返回的結果為XML,需要進行相應的解析才能得到指定的值,相關的註意事項,可參見這篇文章《C#操作xml SelectNodes,SelectSingleNode總是返回NULL 與 xPath 介紹》,其實看了過後就知道是XML命名空間的問題,也可以去掉命名空間,這樣解析就方便多了,如下代碼:
//這裏是得到的XML,調用去除命名空間的方法: String retXml = RemoveNamespace(sr.ReadToEnd()); private string RemoveNamespace(string xml) { var reg = new System.Text.RegularExpressions.Regex("xmlns=\".*\"", System.Text.RegularExpressions.RegexOptions.IgnoreCase); return reg.Replace(xml, ""); } protected void Button2_Click(object sender, EventArgs e) { ... var result = CallWebServiceFromHttpWebRequest("FormAuthWebService.asmx/Login", "username=admin&password=123456", cookieContainer); Label2.Text = result.SelectSingleNode("//boolean").InnerText; var result2 = CallWebServiceFromHttpWebRequest("FormAuthHelloWebService.asmx/HelloTo", "name=" + HttpUtility.UrlEncode(TextBox2.Text, Encoding.UTF8), cookieContainer); Label2.Text = result2.SelectSingleNode("//string").InnerText; ... }
3.集成Windows身份驗證,這個很簡單,首先將IIS設置成集成WINDOWS身份驗證,服務定義無特殊代碼
客戶端使用如下:
Test.WebReference.Service1 service= new Test.WebReference.Service1(); //生成web service實例 service.Credentials = new NetworkCredential("user","123456"); //user是用戶名,該用戶需要有一定的權限 lblTest.Text =service.HelloTo("zuowenjun") ; //調用web service方法
WEB頁面示例代碼:
<form id="form1" runat="server"> <fieldset> <legend>基於自定義SoapHeader驗證後調用服務</legend> <div> <asp:RadioButtonList ID="RadioButtonList1" runat="server" RepeatDirection="Horizontal"> <asp:ListItem Value="1">From Service Reference</asp:ListItem> <asp:ListItem Value="2">From Web Reference</asp:ListItem> </asp:RadioButtonList> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" /> <br /> <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label> </div> </fieldset> <p> </p> <p> </p> <p> </p> <fieldset> <legend>基於自定義Form身份驗證後調用服務</legend> <div> <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox> <asp:Button ID="Button2" runat="server" onclick="Button2_Click" Text="Button" /> <br /> <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label> </div> </fieldset> <p> </p> <p> </p> <p> </p> <fieldset> <legend>基於自定義Windows驗證後調用服務</legend> <div> <asp:TextBox ID="TextBox3" runat="server"></asp:TextBox> <asp:Button ID="Button3" runat="server" Text="Button" onclick="Button3_Click" /> <br /> <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label> </div> </fieldset> </form>
最終呈現效果截圖如下:
由於下班時間寫的,所以有些倉促,不足之處,敬請指出,謝謝!~v~
補充知識點:
我上面的代碼在驗證用戶登錄時,若驗證不通過,我是直接拋出一個Exception,WEB服務在返回到客戶端前會被包裝為SoapException類型的異常,這樣導至在客戶端無法很直接的查看錯誤內容,所以這裏可以采用這篇文章《封裝SoapException處理Webservice異常》裏面的方法來添加格式化SoapException的方法及解析SoapException的方法。
關於WEB SERIVCE在多個ASP.NET頁面中使用時共享COOKIE的辦法,實現代碼如下:
首先正常引用WEB服務,然後自定義繼承自該引用的服務類,並在其構造函數中實例化CookieContainer並保存到靜態字段中;
使用的時,不要調用引用的服務,而應該調用這些自定義的子類,從而實現共享COOKIE
public class FormAuthHelloWebServiceEx : RefFormAuthWebService.FormAuthHelloWebService { private static CookieContainer cookieContainer; static FormAuthHelloWebServiceEx() { cookieContainer = new System.Net.CookieContainer(); } public FormAuthHelloWebServiceEx(CookieContainer ckContainer) { if (cookieContainer != null) { cookieContainer = ckContainer; } this.CookieContainer = cookieContainer; } } public class FormAuthWebServiceEx : RefFormAuthLoginWebService.FormAuthWebService { private static CookieContainer cookieContainer; static FormAuthWebServiceEx() { cookieContainer = new System.Net.CookieContainer(); } public FormAuthWebServiceEx(CookieContainer ckContainer) { if (cookieContainer != null) { cookieContainer = ckContainer; } this.CookieContainer = cookieContainer; } }
WebService登陸驗證四種方式