65.9K
CodeProject 正在变化。 阅读更多。
Home

WCF 中的自定义身份验证和授权

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (36投票s)

2013年12月19日

CPOL

5分钟阅读

viewsIcon

185470

使用 UserNamePasswordValidator 和 IAuthorizationPolicy 对 WCF 服务进行身份验证和授权。

引言

在本文中,我想深入探讨使用 UserNamePasswordValidatorIAuthorizationPolicy 对 WCF 服务进行身份验证和授权。

本文的先决条件如下

您应该已安装以下内容

  • Windows XP 或 Windows 7
  • 已安装 IIS
  • .NET Framework 3.5 或 4.0

创建 WCF 服务

要创建 WCF 服务,您需要定义要暴露给客户端的服务契约。下面是一个服务契约接口的示例。

namespace Service
{    
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string SayHello(string value);

        [OperationContract]       
        string GetData(int value);

        [OperationContract]
        string UpdatePatientData(PatientData PatientInfo);

    }
}

这里,接口需要标记为 ServiceContract,方法应标记为 OperationContract 属性。如果接口中的方法具有要传递的参数,则参数类型应为可序列化类型或数据契约。此外,我在 IService1.cs 文件中有一个名为 PatientDataDataContract 类。

[DataContract]
public class PatientData
{
    [DataMember]
    public int Age { get; set; }

    [DataMember]
    public string Email { get; set; }

    [DataMember]
    public string Gender { get; set; }

    [DataMember]
    public string Name { get; set; }       
}

接下来,我们需要在 Service1.svc.cs 中实现 IService1

public class Service1 : IService1
{ 
    public string SayHello(string value)
    {
        return "Hello:" + value;
    } 
    public string GetData(int value)
    { 
           return string.Format("You entered: {0}", value); 
           
    } 
    public string UpdatePatientData(PatientData PatientInfo)
    { 
            return string.Format("You entered: {0} , {1} , {2} , {3}", 
            PatientInfo.Name, PatientInfo.Age, PatientInfo.Gender, PatientInfo.Email); 
    } 
}

接下来,我们需要配置包含服务地址、绑定和终结点的 web.config。打开 web.config,在 <system.servicemodel> 标签下的 <behaviours> 部分,我们将添加行为名称。

<behaviors> 
      <serviceBehaviors>
        <behavior name="customBehaviour">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/> 
        </behavior>
      </serviceBehaviors>
</behaviors>  

接下来,我们在 <system.servicemodel> 下配置 <services>

<services>      
  <service name="Service.Service1" behaviorConfiguration="customBehaviour">
    <endpoint address=""
          binding="wsHttpBinding"
          contract ="Service.IService1">
    </endpoint>
    <endpoint contract="IMetadataExchange" 
          binding="mexHttpBinding" 
          address="mex" />
  </service>
</services> 

配置完服务后,尝试访问 service.svc 文件或将其托管,您应该能够在浏览器中查看该服务并访问 WSDL。

实现 UserNamePasswordValidator

到目前为止,我们只完成了 WCF 的基础知识,即创建接口、实现接口、配置 web.config 并确保服务已启动。接下来,我们将了解如何使用 UserNamePasswordValidator验证用户身份。右键单击解决方案并添加一个新类,为该类提供一个有效/适当的名称。在本例中,我将该类命名为 UserAuthentication.cs。打开该类并继承 UserNamePasswordValidator 抽象类,然后我们将重写接受两个参数(usernamepassword)的 Validate 方法。下图显示了这一点。

public class UserAuthentication:UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        try
        {
            if (userName == "test" && password == "test123")
            {
                Console.WriteLine("Authentic User");
            }
        }
        catch (Exception ex)
        {
            throw new FaultException("Unknown Username or Incorrect Password");
        }
    } 
}

在这里,在 Validate 方法中,我只是对一些虚拟用户进行检查,并将其打印到控制台或验证用户。您可以拥有自己的实现,例如将用户与您的数据库或任何自定义逻辑进行比较。您可能想知道谁将传递用户名和密码,现在我们不必担心这个问题,因为客户端将访问服务,并在请求服务操作时发送用户名和密码。我们将在稍后部分看到这一点。

完成此操作后,需要对服务的 web.config 进行一些更改。打开配置文件并开始进行修改。我们需要对 wsHttpBinding 进行一些更改。在 <system.servicemodel> 中,添加 <bindings>

<bindings>
  <wsHttpBinding>
    <binding name="ServiceBinding">
      <security mode="Message">
        <message clientCredentialType="UserName"/>
      </security>
    </binding>
  </wsHttpBinding>
</bindings> 

由于我们已经在 UserAuthentication.cs 文件中实现了身份验证逻辑,因此我们需要告诉我们的服务存在自定义身份验证,我们该如何做到这一点?再次,配置文件发挥作用。在 <serviceBehaviours> 中,添加如下所示的标记。

<behaviors> 
      <serviceBehaviors>
        <behavior name="customBehaviour">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/> 
          <!--Specify the Custom Authentication policy that will be used and add the policy location--> 
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
               customUserNamePasswordValidatorType="Service.UserAuthentication,Service"/> 
          </serviceCredentials> 
        </behavior> 
      </serviceBehaviors>
</behaviors> 

实现 IAuthorizationPolicy

在本节中,我们将为已验证的用户实现授权规则。

右键单击 App_Code 文件夹并创建一个新文件夹,并提供一个有效的名称。在此文件夹中,创建一个新类文件并命名为 AuthorizationPolicy.cs。打开该类并继承 IAuthorizationPolicy 接口。此接口位于 System.IdentityModel.Policy 下。实现接口方法。右键单击 IAuthorizationPolicy 实现。此接口有一个重要的方法名为 Evaluate。此方法检查已验证用户的身份。

注意:有关更多信息,请参阅文章 https://codeproject.org.cn/Articles/33872/Custom-Authorization-in-WCF

class AuthorizationPolicy : IAuthorizationPolicy 
{
    Guid _id = Guid.NewGuid();
    // this method gets called after the authentication stage
    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        // get the authenticated client identity
        IIdentity client = GetClientIdentity(evaluationContext);
        // set the custom principal
        evaluationContext.Properties["Principal"] = new CustomPrincipal(client); 
        return true;
    }
    private IIdentity GetClientIdentity(EvaluationContext evaluationContext)
    {
        object obj;
        if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
            throw new Exception("No Identity found");
        IList<IIdentity> identities = obj as IList<IIdentity>;
        if (identities == null || identities.Count <= 0)
            throw new Exception("No Identity found");
        return identities[0];
    } 
}

现在,在获取客户端身份后,我们需要为用户设置角色。这是通过实现另一个名为 IPrincipal 的接口来完成的。在 App_Code -> Security 文件夹中添加一个新类,并命名为 CustomPrincipal.cs。在此类中继承 IPrincipal 接口,此接口位于 System.Security.Principal 命名空间下。右键单击并实现此接口。此接口有一个重要的方法名为 IsInRole,我们可以在其中为登录用户设置角色。

public bool IsInRole(string role)
{
   if (_identity.Name == "test")
        _roles = new string[1] { "ADMIN" };
    else
        _roles = new string[1] { "USER" }; 
    return _roles.Contains(role);
} 

由于我们已经在 AuthorizationPolicy.cs 文件中实现了授权逻辑,因此我们需要告诉我们的服务存在自定义授权。请打开配置文件。

<behaviors> 
  <serviceBehaviors>
    <behavior name="customBehaviour">
      <serviceMetadata httpGetEnabled="true"/>
      <serviceDebug includeExceptionDetailInFaults="true"/>
      <!--Specify the Custom Authorization policy that will be used and add the policy location-->
      <serviceAuthorization principalPermissionMode="Custom">
        <authorizationPolicies>
          <add policyType="Service.AuthorizationPolicy, App_Code/Security" />
        </authorizationPolicies>
      </serviceAuthorization>
      <!--Specify the Custom Authentication policy that will be used and add the policy location--> 
      <serviceCredentials>
        <userNameAuthentication userNamePasswordValidationMode="Custom" 
                                customUserNamePasswordValidatorType="Service.UserAuthentication,Service"/> 
      </serviceCredentials> 
    </behavior> 
  </serviceBehaviors>
</behaviors> 

我们几乎完成了,但如果您希望解决方案能够正常工作,那么我们还需要在我​​们计算机上安装一个证书,并在 <serviceCredentials> 中提供该证书。我已经在我的计算机上创建并安装了一个名为 STSTestCert 的证书。

<serviceCertificate findValue="STSTestCert"
                                storeLocation="LocalMachine"
                                x509FindType="FindBySubjectName"
                                storeName="My"/> 

最终的 web.config 配置将如下所示:

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.serviceModel>

    <services>
      <service name="Service.Service1" behaviorConfiguration="customBehaviour">
        <endpoint address=""
                  binding="wsHttpBinding"
                  contract ="Service.IService1"
                  bindingConfiguration="ServiceBinding"
                  behaviorConfiguration="MyEndPointBehavior" >
        </endpoint>
        <endpoint contract="IMetadataExchange"
                  binding="mexHttpBinding"
                  address="mex" />
      </service>
    </services>

    <bindings>
      <wsHttpBinding>
        <binding name="ServiceBinding">
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

    <behaviors>
      <endpointBehaviors>
        <behavior name="MyEndPointBehavior">
        </behavior>
      </endpointBehaviors>

      <serviceBehaviors>
        <behavior name="customBehaviour">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>

          <!--Specify the Custom Authorization policy that will be used and add the policy location-->
          <serviceAuthorization principalPermissionMode="Custom">
            <authorizationPolicies>
              <add policyType="Service.AuthorizationPolicy, App_Code/Security" />
            </authorizationPolicies>
          </serviceAuthorization>

          <!--Specify the Custom Authentication policy that will be used and add the policy location-->
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
                                    customUserNamePasswordValidatorType="Service.UserAuthentication,Service"/>

            <!--Specify the Certificate-->
            <serviceCertificate findValue="STSTestCert"
                                storeLocation="LocalMachine"
                                x509FindType="FindBySubjectName"
                                storeName="My"/>
          </serviceCredentials>

        </behavior>
      </serviceBehaviors>
    </behaviors>

  </system.serviceModel>

</configuration> 

创建客户端

向您的解决方案添加一个新的控制台客户端并添加服务引用。创建代理并尝试访问服务操作。由于我们实现了身份验证逻辑,因此我们需要传递用户凭据才能访问服务。此时,一旦用户通过身份验证,我们就可以访问这两个服务操作。

class Program
{
    static void Main(string[] args)
    {
        ServiceReference1.Service1Client serviceProxy = new ServiceReference1.Service1Client();
        serviceProxy.ClientCredentials.UserName.UserName = "test";
        serviceProxy.ClientCredentials.UserName.Password = "test123";

        PatientData objData = new PatientData();
        objData.Name = "test";
        objData.Gender = "Male";
        objData.Email = "v@g.com";
        objData.Age = 20;
        Console.WriteLine(serviceProxy.UpdatePatientData(objData));
        Console.ReadLine();
       string message= serviceProxy.GetData(5);
       Console.WriteLine(message);
        Console.ReadLine();
    }
}

我们也实现了授权逻辑,但是在这里客户端可以访问这两个方法,那么我们如何并且如何限制对方法的访问呢?这很简单,因为我们需要在 Service1.svc.cs 中的方法顶部使用 PrincipalPermission 属性。进行如下所示的更改。

[PrincipalPermission(SecurityAction.Demand, Role = "ADMIN")]
public string GetData(int value)
{
    try
    {
        return string.Format("You entered: {0}", value);
    }
    catch (Exception exp)
    {
        MyFaultException theFault = new MyFaultException();
        theFault.Reason = "Some Error " + exp.Message.ToString();
        throw new FaultException<MyFaultException>(theFault);
    }
}
[PrincipalPermission(SecurityAction.Demand, Role = "ADMIN")]
public string UpdatePatientData(PatientData PatientInfo)
{
    try
    {
        return string.Format("You entered: {0} , {1} , {2} , {3}", 
        PatientInfo.Name, PatientInfo.Age, PatientInfo.Gender, PatientInfo.Email);
    }
    catch(Exception exp)
    {
        MyFaultException theFault = new MyFaultException();
        theFault.Reason = "Some Error " + exp.Message.ToString();
        throw new FaultException<MyFaultException>(theFault); 
    }
} 

因此,即使用户已通过身份验证,我也已配置 PrincipalPermission 属性来接受角色为 admin 的用户。要访问该操作,用户需要是机器的管理员。在这里,角色也可以设置为。您需要在您的计算机上创建组并将用户添加到该组,以便这些操作可供该特定组中的用户访问。我还实现了故障异常,以便服务端捕获的任何异常都会发送到客户端应用程序。

希望本文能帮助您更好地了解如何通过使用 UserNamePasswordValidator 进行身份验证和使用 IAuthorizationPolicy 进行授权来实现 WCF 中的安全性。

© . All rights reserved.