WebService的安全性讨论【身份识别】

来源:互联网 发布:app原型设计工具 mac 编辑:程序博客网 时间:2024/05/18 04:55

http://www.aspxcs.net/HTML/0942423489.html#

相信很多开发者都用过WebService来实现程序的面向服务,本文主要介绍WebService的身份识别实现方式,当然本文会提供一个不是很完善的例子,权当抱砖引玉了.

首先我们来介绍webservice下的两种验证方式,

一.通过集成windows身份验证

通过集成windows方式解决webservice的安全问题是一个很简洁,并且行之有效的解决方案,该方案的优 点是比较安全,性能较好,当然因为与windows紧密的结合到了一起,缺点自然也很明显了,第一,不便于移植,第二,要进行相关的配置部署工作(当然我 们也可以用代码来操作IIS,只不过比较麻烦,最近一直做自动化部署,所以一讲到配置马上就会联想到怎么去自动部署)

具体怎么做呢?

服务器端:配置IIS虚拟目录为集成windows身份验证

客户端:

  1. Service1 wr = new Service1(); //web service实例   
  2.  
  3. wr.Credentials = new NetworkCredential("administrator","123"); //用户名密码   
  4.  
  5. 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的代码,多余的方法将会在后面用到

客户端

  1. class Program 
  2.     { 
  3.         static void Main(string[] args) 
  4.         { 
  5.             Service1 ws = new Service1(); 
  6.             ServiceCredential mycredential = new ServiceCredential(); 
  7.  
  8.             mycredential.User = "gazi"
  9.             mycredential.Password="gazi"
  10.             ws.ServiceCredentialValue = mycredential; 
  11.             string  mystr=ws.SayHello();             
  12.         } 
  13.     } 

服务器端

  1. public class Service1 : System.Web.Services.WebService 
  2.     { 
  3.         public ServiceCredential myCredential; 
  4.  
  5.         [WebMethod] 
  6.         [SoapHeader("myCredential", Direction = SoapHeaderDirection.In)] 
  7.         public string  SayHello()  
  8.         {  
  9.             return "hello"
  10.         } 
  11.     } 
  12.  
  13.  
  14.  
  15. public class ServiceCredential : SoapHeader 
  16.     { 
  17.         public string User; 
  18.         public string Password; 
  19.         public static bool ValideUser(string User,string Password) 
  20.         { 
  21.             return true
  22.         } 
  23.         public static void CheckUser(Object sender, WebServiceAuthenticationEvent e) 
  24.         { 
  25.             if (ValideUser(e.User, e.Password)) 
  26.             { 
  27.                 return
  28.             } 
  29.             else 
  30.             { 
  31.                 WebServiceAuthenticationModule module = sender as WebServiceAuthenticationModule; 
  32.                 module.Result.AddRule("验证错误""不能确认您的身份,请检查用户名和密码"); 
  33.             } 
  34.         } 
  35.     } 

 

当我们拥有很多个类的时候,要添加一个或者删除一个验证方式(假设需要进行多种认证)是非常麻烦的,我们不可能跑到 每个方法里面去加一个方法调用,这是灾难性的工作,当然我们也可以用AOP来实现,Aop的话需要额外增加很多代码或者直接引入第三方来做,但是我们可不 可以有更简便的方法呢?

OK,答案就是使用HttpModule,我们集成IHttpModule写一个处理模块,那么它的原理是什么呢?具体进行了哪些操作呢?我们的思路如下:

  1. HTTP Module 分析 HTTP 消息以检查它们是不是 SOAP 消息。
  2. 如果 HTTP Module 检测到 SOAP 消息,它会读取 SOAP 标头。
  3. 如果 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事件上,前面提到的增加和删除多种认证方式就是通过这种方法实现的.

  1. protected void Application_Start(object sender, EventArgs e) 
  2.        { 
  3.            WebServiceAuthenticationModule.Authenticate += ServiceCredential.CheckUser; 
  4.        } 

 

我们在ServiceCredential.ValideUser方法设置了返回false,这是针对测试的一个配置,实际情况下我们可以和数据库结合起来写一个认证
运行上面讲解SoapHeader的那段代码,你会发现我们的认证已经有效了.关于文章中用到的Result类改天在用一篇文章记录一下,这是一个非常好的记录错误的方案.