关于WEB Service&WCF&WebApi实现身份验证之WEB Service篇
在这个WEB API横行的时代,讲WEB Service技术却实显得有些过时了,过时的技术并不代表无用武之地,有些地方也还是可以继续用他的,我之所以会讲解WEB Service,源于我最近面试时被问到相关问题,我这里只是重新复习一下并总结一下,给新手们指指路,大牛们可以无视之,当然不足之处还请大家指教,谢谢!
WEB Service身份验证,网上已有许多的相关文章,总结起来有:基于自定义SoapHeader验证、Form验证、集成Windows身份验证、服务方法加入一个或几个验证参数;下面就不废话了,直接分享实现的代码吧,中间有涉及注意的地方,我会有说明文字的。
1.基于自定义SoapHeader验证
定义服务:(注意UserValidationSoapHeader必需有无参构造函数,否则无法序列化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //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引用”到此画面)
客户端使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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验证
定义服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | //专门用于登录的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需要加入以下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //在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请求,默认是未登录的情况,你去引用受限的服务地址,它就会自动跳转到登录的服务地址,所以针对这个问题,我采用了两种办法,第一种是:先不要禁止匿名访问,当服务引用成功后,再加上该配置,第二种是:不直接引用服务,采用代码动态请求服务方法来进行相关的调用,客户端使用代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | //第一种:采用服务引用: 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; } |
辅助方法定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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命名空间的问题,也可以去掉命名空间,这样解析就方便多了,如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //这里是得到的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身份验证,服务定义无特殊代码
客户端使用如下:
1 2 3 | 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页面示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | < 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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; } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具