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






4.72/5 (36投票s)
使用 UserNamePasswordValidator 和 IAuthorizationPolicy 对 WCF 服务进行身份验证和授权。
引言
在本文中,我想深入探讨使用 UserNamePasswordValidator
和 IAuthorizationPolicy
对 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 文件中有一个名为 PatientData
的 DataContract
类。
[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
抽象类,然后我们将重写接受两个参数(username
和 password
)的 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 中的安全性。