对于web应用(包括web站点及web服务)的安全,我们首先想到的和见到的是,让客户提供凭据(最常见的是用户名和密码),然后服务端对客户提供的凭据进行验证,验证通过后,在具体的方法调用或页面请求时,根据验证通过的客户身份进行授权检查,授权通过,则执行客户的请求;反之则拒绝客户的请求。这就是一般验证及授权的思路。
 
如果这样还不能安全要求,那只好再启用传输层加密,即SSL了。实际上在WCF中,验证方式也被分为两个层次:Transport和Message。在传输层加密数据,在消息层提供凭据验证及授权。本文就准备介绍客户端在消息层传入用户凭据,服务端通过MembershipProvider进行验证,通过RoleProvider进行授权,然后辅以传输层加密的方案。
 
这个方案针对于WCF服务,为了便于客户调用,而又加强传输层安全,所以选择了SSL,另外调用WCF服务又需要提供身份验证及授权,尤其针对凭据信息自定义的要求,客户端凭据选择UserName类型。
这个方案的好处是什么呢?TransportWithMessageCredential确保了传输上的安全,并提供了客户端凭据。而对客户端凭据的验证和授权支持自定义方式,所以不管用什么实现rbac,都可以使用这套方案来实现权限控制。另外,在代码中实现权限控制也比较简单,即使用PrincipalPermissionAttribute。
 
还是让我们开始看看demo吧。
首先然我们定义一个ServiceContract:
[ServiceContract]    
public interface ITestService
{
    [OperationContract]
    string GetData(int value);
}
 
我们给这个ITestService一个简单的实现:
public class TestService : ITestService
{
    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }
}
 
接下来,让我们提供配置信息:
1)Binding配置
<wsHttpBinding>
    <binding name="wsHttpBinding1">
        <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" realm="" />
            <message clientCredentialType="UserName" />
        </security>
    </binding>
</wsHttpBinding>
 
2)Service配置
<service name="TestService.TestService" behaviorConfiguration="securityBehavior">
    <endpoint address=""
                  binding="wsHttpBinding"
                  bindingConfiguration ="wsHttpBinding1"
                  contract ="TestBase.ITestService"
                  name ="testService" />
    <endpoint address="mex"
                  binding="mexHttpsBinding"
                  contract="IMetadataExchange" />
</service>
 
3)MembershipProvider及RoleProvider配置
<system.web>
    <roleManager enabled="true" defaultProvider ="MyProvider">
        <providers>
            <add name="MyRoleProvider" type="TestBase.MyRoleProvider,TestBase"/>
        </providers>
    </roleManager>

    <membership>
        <providers>
            <add name="MyMembershipProvider" type="TestBase.MyMembershipProvider,TestBase"/>
        </providers>
    </membership>
</system.web>
 
4)Behavior配置
<serviceBehaviors>
    <behavior name="securityBehavior">
        <serviceCredentials>
            <userNameAuthentication  userNamePasswordValidationMode="MembershipProvider"  membershipProviderName="MyMembershipProvider"/>
        </serviceCredentials>
        <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="MyRoleProvider" />
    </behavior>
</serviceBehaviors>
好了,需要的类及配置都已准备好了,然后在IIS中,确保目标Web Site中包含https绑定,并且https绑定指向一个证书。在该Web Site中建立一个Application: TestService。浏览https://localhost/TestService/TestService.svc,可以正常浏览。
接下来,实现客户端调用代码:
try
{
    TestServiceReference.TestServiceClient testServiceClient = new TestServiceReference.TestServiceClient ("testService");

    testServiceClient .ClientCredentials.UserName.UserName = "userName";
    testServiceClient .ClientCredentials.UserName.Password = "password";

    ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) =>
    {
        return true;
    };

    string result = testServiceClient .GetData(3456);
    Console.WriteLine("result : {0}", result);
}
catch (MessageSecurityException ex)
{
    //authenticated failed
    Console.WriteLine(ex.Message);
}
catch(SecurityAccessDeniedException ex)
{
    //authorized failed
    Console.WriteLine(ex.Message);
}
catch(FaultException ex)
{
    Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

Console.ReadLine();
运行,服务可以正常访问。但是权限控制还没有加到服务端代码上,在TestService的GetData方法上加上PrincipalPermissionAttribute:
public class TestService : ITestService
{
    [PrincipalPermission(SecurityAction.Demand, Role = "getData")]
    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }
}
此外,MyMembershipProvider以及MyRoleProvider的实现,在此就不提供了。在客户端调用代码中,通过提供错误的账户或密码,有getData权限的用户,以及无getData权限的用户,该方案将能够得到验证。
此外,PrincipalPermissionAttribute可以放到TestService.GetData上,也可以放到OperationContract的内部实现逻辑的类或方法上,那么在客户端异常处理是有所区别的。其中,MembershipProvider验证不通过,客户端将可以捕获到MessageSecurityException,而RoleProvider权限验证不通过,客户端将捕获到SecurityAccessDeniedException。如果你在你的MembershipProvider以及RoleProvider中定义自己的业务异常类,那么这些异常被抛到OperationContract方法中没有被处理的话,将会封装到FaultException抛到客户端。
通过MembershipProvider+RoleProvider+PrincipalPermissionAttribute,从而可以实现权限控制对具体代码的低依赖。而且验证和授权可以根据自己的需要很容易做出调整。代码中使用PrincipalPermissionAttribute这种declarative的方式,实现起来比较简洁,对已有业务代码的侵入性比较低。这种简洁的验证及授权方式,实现成本不高,而收到的效果却相当明显。

作者: Bright Zhang 发表于 2011-05-28 00:17 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架