【问题标题】:Securing a web service?保护网络服务?
【发布时间】:2010-10-20 08:18:51
【问题描述】:

问题:我有一个文档管理系统,我正在为数据库构建一个 Web 服务接口。

到目前为止一切正常,只是现在,它完全不安全,每个人都可以访问它。

如何合并密码或私钥-公钥身份验证?

我只能找到“最佳实践”并使用“Windows 用户”或护照身份验证。 但我需要对存储在数据库中的用户和密码进行身份验证,或者更好地为数据库中每个 Web 服务用户存储的 RSA 私钥...

编辑:
我必须在 ASP.NET 环境中使用 .NET Framework 2.0

【问题讨论】:

    标签: c# vb.net web-services security


    【解决方案1】:

    解决方案是编写一个自己的 http 模块,混合 MSDN 和 CodeProject 提供的代码。包括自己修复的 MS 错误,然后将这个自定义的 soap 头添加到 web 服务。

    <SoapHeader("Authentication", Required:=True)>
    

    这是模块:

    Imports System.Web
    Imports System.Web.Services.Protocols
    
    
    ' http://msdn.microsoft.com/en-us/library/9z52by6a.aspx
    ' http://msdn.microsoft.com/en-us/library/9z52by6a(VS.80).aspx
    
    
    
    
    ' http://www.codeproject.com/KB/cpp/authforwebservices.aspx
    
    
    ' http://aleemkhan.wordpress.com/2007/09/18/using-wse-30-for-web-service-authentication/
    ' http://www.codeproject.com/KB/WCF/CustomUserNamePassAuth2.aspx
    ' http://www.codeproject.com/KB/WCF/CustomUserNamePassAuth2.aspx
    ' http://www.codeproject.com/KB/webservices/WS-Security.aspx
    
    
    
    
    'Public NotInheritable Class WebServiceAuthenticationModule
    Public Class WebServiceAuthenticationModule
        Implements System.Web.IHttpModule
    
        Protected Delegate Sub WebServiceAuthenticationEventHandler(ByVal sender As [Object], ByVal e As WebServiceAuthenticationEvent)
        Protected _eventHandler As WebServiceAuthenticationEventHandler = Nothing
    
    
    
        Protected Custom Event Authenticate As WebServiceAuthenticationEventHandler
            AddHandler(ByVal value As WebServiceAuthenticationEventHandler)
                _eventHandler = value
            End AddHandler
            RemoveHandler(ByVal value As WebServiceAuthenticationEventHandler)
                _eventHandler = value
            End RemoveHandler
            RaiseEvent(ByVal sender As Object,
                    ByVal e As WebServiceAuthenticationEvent)
            End RaiseEvent
        End Event
    
    
        Protected app As HttpApplication
    
    
        Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
            app = context
    
            context.Context.Response.Write("<h1>Test</h1>")
    
            AddHandler app.AuthenticateRequest, AddressOf Me.OnEnter
        End Sub
    
    
        Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
            ' add clean-up code here if required
        End Sub
    
    
        Protected Sub OnAuthenticate(ByVal e As WebServiceAuthenticationEvent)
            If _eventHandler Is Nothing Then
                Return
            End If
            _eventHandler(Me, e)
            If Not (e.User Is Nothing) Then
                e.Context.User = e.Principal
            End If
    
        End Sub 'OnAuthenticate 
    
    
        Public ReadOnly Property ModuleName() As String
            Get
                Return "WebServiceAuthentication"
            End Get
        End Property
    
    
        Sub OnEnter(ByVal [source] As [Object], ByVal eventArgs As EventArgs)
            'Dim app As HttpApplication = CType([source], HttpApplication)
            'app = CType([source], HttpApplication)
            Dim context As HttpContext = app.Context
            Dim HttpStream As System.IO.Stream = context.Request.InputStream
    
            ' Save the current position of stream.
            Dim posStream As Long = HttpStream.Position
    
            ' If the request contains an HTTP_SOAPACTION 
            ' header, look at this message.
    
            'For Each str As String In context.Request.ServerVariables.AllKeys
    
            'If context.Request.ServerVariables(Str) IsNot Nothing Then
            'context.Response.Write("<h1>" + Str() + "= " + context.Request.ServerVariables(Str) + "</h1>")
            'End If
            'Next
            If context.Request.ServerVariables("HTTP_SOAPACTION") Is Nothing Then
                'context.Response.End()
                Return
                'Else
                'MsgBox(New System.IO.StreamReader(context.Request.InputStream).ReadToEnd())
            End If
    
    
            ' Load the body of the HTTP message
            ' into an XML document.
            Dim dom As New System.Xml.XmlDocument()
            Dim soapUser As String
            Dim soapPassword As String
    
            Try
                dom.Load(HttpStream)
    
                'dom.Save("C:\Users\Administrator\Desktop\SoapRequest.xml")
                ' Reset the stream position.
                HttpStream.Position = posStream
    
                ' Bind to the Authentication header.
                soapUser = dom.GetElementsByTagName("Username").Item(0).InnerText
                soapPassword = dom.GetElementsByTagName("Password").Item(0).InnerText
            Catch e As Exception
                ' Reset the position of stream.
                HttpStream.Position = posStream
    
                ' Throw a SOAP exception.
                Dim name As New System.Xml.XmlQualifiedName("Load")
                Dim ssoapException As New SoapException("Unable to read SOAP request", name, e)
                context.Response.StatusCode = System.Net.HttpStatusCode.Unauthorized
                context.Response.StatusDescription = "Access denied."
    
                ' context.Response.Write(ssoapException.ToString())
                'Dim x As New System.Xml.Serialization.XmlSerializer(GetType(SoapException))
                'context.Response.ContentType = "text/xml"
                'x.Serialize(context.Response.OutputStream, ssoapException)
    
    
                'Throw ssoapException
    
                context.Response.End()
            End Try
    
            ' Raise the custom global.asax event.
            OnAuthenticate(New WebServiceAuthenticationEvent(context, soapUser, soapPassword))
            Return
        End Sub 'OnEnter
    
    
    End Class ' WebServiceAuthenticationModule
    

    【讨论】:

      【解决方案2】:

      如果您仍在使用 ASP.NET SOAP Web 服务,那么满足 IMO 要求的最简单方法是将 ASP.NET Forms 身份验证与 Membership DB 一起使用。如果您刚开始,我建议您使用 WCF - 如果您不能/或不会这样做,这篇文章适用于“经典” ASP.NET SOAP Web 服务。

      将表单身份验证添加到 Web 服务:

      1. 像对任何其他网站一样进行配置,但将其设置为允许所有人访问:

        <authorization>
            <allow users="*"/>
        </authorization>
        
      2. 实现 Login/Logout 方法并在 Login 方法中发出身份验证票。然后,对 Web 服务的进一步请求可以使用已发布的身份验证票证。

      3. 您想要保护的所有其他网络方法都可以使用

        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]

      如果客户端未通过身份验证,这些方法现在将引发安全异常。

      受保护方法的示例:

      [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
      [WebMethod(Description = "Your protected method")]
      public string Foo()
      {
          return "bar";
      }
      

      登录方式示例:

      [WebMethod(Description = "Login to start a session")]
      public bool Login(string userName, string password)
      {
          if (!Membership.Provider.ValidateUser(userName, password))
              return false;
      
          FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
                 1,
                 userName,
                 DateTime.Now,
                 DateTime.Now.AddMinutes(500),
                 false,
                 FormsAuthentication.FormsCookiePath);// Path cookie valid for
      
          // Encrypt the cookie using the machine key for secure transport
          string hash = FormsAuthentication.Encrypt(ticket);
          HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, // Name of auth cookie
                                             hash); // Hashed ticket
      
          // Set the cookie's expiration time to the tickets expiration time
          if (ticket.IsPersistent)
              cookie.Expires = ticket.Expiration;
      
          // Add the cookie to the list for outgoing response
          if(HttpContext.Current !=null)
              HttpContext.Current.Response.Cookies.Add(cookie);
      
          FormsAuthentication.SetAuthCookie(userName, true);
          return true;
      }
      

      【讨论】:

      • 然后每次你想使用网络服务时都必须调用登录和注销,使用黑盒传输系统,如果客户端或服务器在两者之​​间崩溃,则行为未定义。如果您忘记将 PrincipalPermission 添加到单个方法中... Nono,任何想要认真的东西都必须默认拒绝访问,并且只需将登录数据作为服务参数,而不是作为方法。
      • 这与常规的 UI 表单身份验证没有什么不同,但仍然更容易解决提到的缺点而不是自己动手。此外,您还可以免费获得角色/会员功能。不过默认情况下采取安全点。
      【解决方案3】:

      如果您正在使用 WCF,有一种使用 X509 证书实现安全性的简单方法。使用安全模式“Message”和 clientCredentialType“Username”实现绑定,可以自动保证这种安全性。

      可以通过覆盖方法 Validate 的类进行验证。

      http://msdn.microsoft.com/en-us/library/aa702565.aspx

      【讨论】:

      • +1,但不幸的是,我仅限于使用框架 2.0 的 ASP.NET,所以没有 WCF。
      • Ops,我在那种情况下迷路了,我知道传输级别的通信也可以使用 X509 证书完成。也许针对 SQL Server 的验证过程将更加“手动”。无论如何,您可以在以下位置找到更多信息:msdn.microsoft.com/en-us/library/ms996415.aspx
      【解决方案4】:

      如果你的 WS 是通过 SOAP 协议来消费的,你可以通过 SOAP Header 来实现 Security:

      using System.Web.Services;
      using System.Web.Services.Protocols;
      
      namespace Domain.WS
      {
          [Serializable]
          public class SoapWSHeader : System.Web.Services.Protocols.SoapHeader, ISoapWSHeader
          {
              public string UserId { get; set; }
              public string ServiceKey { get; set; }
              public ApplicationCode ApplicationCode { get; set; }        
          }    
      
          [WebService(Namespace = "http://domain.some.unique/")]        
          public class MyServices : System.Web.Services.WebService
          {
              public SoapWSHeader WSHeader;
              private ServicesLogicContext _logicServices;
      
              public MyServices() { _logicServices = new ServicesLogicContext(new LogicInfo() {...}); }
      
              [WebMethod, SoapHeader("WSHeader", Direction = SoapHeaderDirection.InOut)]
              public Result WSMethod1(Int32 idSuperior)
              {
                  _logicServices.ThrowIfNotAuthenticate(WSHeader); 
                  return _logicServices.WSMethod1(idSuperior) as Result;
              }
          }
      }
      
      namespace Domain.Logic 
      {
          [Serializable]    
          public class ServicesLogicContext : ServicesLogicContextBase
          {
              protected ISoapWSHeader SoapWSHeader { get; set; }
              public ServicesLogicContext(LogicInfo info) : base(info) {}
      
              public IResult WSMethod1(Int32 idSuperior)
              {
                  IResult result = null; 
                  //-- method implementation here...
                  return result;
              }
      
              public void ThrowIfNotAuthenticate(ISoapWSHeader soapWSHeader) {
                  this.SoapWSHeader = soapWSHeader;
                  if (SoapWSHeader != null)
                  {
                      if (!ValidateCredentials(soapWSHeader))
                      {
                          throw new System.Security.SecurityException(Resources.ValidationErrorWrongCredentials);
                      }
                  }
                  else { throw new System.Security.SecurityException(Resources.ValidationErrorWrongWSHeader); }
              }
              private bool ValidateCredentials(ISoapWSHeader soapWSHeader) {   
                  return (SoapWSHeader.UserId.Equals("USER_ID") && SoapWSHeader.ServiceKey.Equals("PSW_1"));
              }
          }
      }
      

      注意:此代码不完整,仅描述如何使用 SOAP Header 的主要方面。

      【讨论】:

      • 如果你注意了,你就会意识到这正是我所做的。只是我已经将身份验证从合并到每个 WebMethod(即 bs)中移到了所述 HTTP 模块中。额外的好处是它拒绝访问每个没有标头的 SOAP 调用。
      • 好的,这意味着你已经解决了你的问题。问候,我们正在应用该技术来保护某些 WS,此外,根据您的应用程序,您可以通过 HTTPS 使用某些证书。
      【解决方案5】:

      在您推出自己的身份验证之前,您可能需要查看Web Services Enhancements (WSE) 2.0 SP3 for Microsoft .NET。它是 .net 的 WS-Security 规范的实现。

      谷歌wse 2.0 或WS-Security 获取更多链接。

      【讨论】:

      • 与 WSE 的链接已损坏 - 作者能否更正?
      猜你喜欢
      • 2016-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-05
      • 1970-01-01
      • 2017-12-19
      相关资源
      最近更新 更多