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

用于 WCF 服务的 ASP.NET Identity 2.0 身份验证和授权

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (24投票s)

2014年7月31日

CPOL

4分钟阅读

viewsIcon

85774

downloadIcon

1923

真实世界 WCF 项目结构,身份验证和授权

引言

这是我之前文章"真实世界 WCF,非 Hello World,第二部分"的扩展,重点是基于 ASP.NET Identity 2.x 的身份验证。

背景

"真实世界 WCF,非 Hello World" 和 "真实世界 WCF,非 Hello World,第二部分" 已经展示了如何通过团队合作,在企业环境中高效且有效地管理 WCF 服务开发,从而快速开发和交付体面的 Web 服务。

由于身份验证和授权对于 Web 服务的基本安全性至关重要,因此最好经常运行自动集成测试,以确保这种基本安全性不会被破坏。

必备组件

  • Visual Studio 2013/2015
  • 通过 NuGet 获取 xUnit
  • 通过 NuGet 获取适用于 Visual Studio 的 xUnit 运行器
  • 通过 NuGet 获取 Fonlow Testing

提示

NuGet 包的 DLL 文件包含在仓库中。

使用 ASP.NET Identity 2.0 进行身份验证和授权

截至 2014 年 7 月的今天,已经发表了很多关于在 WCF 服务中使用 ASP.NET MembershipProviderRoleProvider 的文章,但是,很少有关于使用 ASP.NET Identity 2.0 的文章。

ASP.NET 和 WCF 提供了丰富的安全功能集,并且身份验证和授权有多种选择。 在这里,我想介绍使用用户名和密码的简单方法,因为这是 ASP.NET MVC 和 Web API 中的默认方式。

如果您正在构建使用 Identity 2.0 的 ASP.NET MVC 5 应用程序以用于 B2C 用例,并且将为 B2B 用例向外部开发人员提供相关的 Web 服务,那么您可能希望在您的多层体系结构中为相关的 WCF 服务共享相同的机制。

将角色分配给操作

通过声明式编程,您可以为每个服务实现分配一些角色

    public class Service1 : IService1 
    {
        public string GetData(int value)
        {
            System.Diagnostics.Debug.WriteLine("GetDataCalled");
            if (value == 666)
                throw new FaultException<Evil666Error>(new Evil666Error() 
                                      { Message = "Hey, this is 666." });

            return string.Format("You entered: {0}", value);
        }

        [PrincipalPermission(SecurityAction.Demand, Role="Customer")]       
        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {

如果您已经使用 RoleManager 进行了 WCF 授权,您将看到代码库与 PrincipalPermissionAttribute 相同。 换句话说,您依赖于 RoleManager 的旧代码无需更改。

在代码中调整 Identity 模型

using System;
using System.IdentityModel.Selectors;
using System.ServiceModel;
using System.Security.Principal;
using System.Diagnostics;

//It is better to put the classes into a shared component
namespace Fonlow.Web.Identity
{
    public class IdentityValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            Debug.WriteLine("Check user name");
            if ((userName != "test") || (password != "tttttttt"))
            {
                var msg = String.Format("Unknown Username {0} or incorrect password {1}", 
                                         userName, password);
                Trace.TraceWarning(msg);
                throw new FaultException(msg);//the client actually will 
                       //receive MessageSecurityException. But if I throw MessageSecurityException, 
                       //the runtime will give FaultException to client without clear message.
            }
        }
    }

    public class RoleAuthorizationManager : ServiceAuthorizationManager
    {
        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            // Find out the roleNames from the user database, for example, 
            // var roleNames = userManager.GetRoles(user.Id).ToArray();

            var roleNames = new string[] { "Customer" };

            operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = 
             new GenericPrincipal(operationContext.ServiceSecurityContext.PrimaryIdentity, roleNames);
            return true;
        }
    }
}

如果您有自己的用户和角色表,那么您可以轻松替换实现,甚至可以利用 ASP.NET Identity 2.0。

在 Web.Config 中连接身份验证和授权

    <behaviors>
      <serviceBehaviors>
        <behavior name="authBehavior">
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom" 
             customUserNamePasswordValidatorType="Fonlow.Web.Identity.IdentityValidator,
             Fonlow.RealWorldImp" />
          </serviceCredentials>
          <serviceAuthorization principalPermissionMode="Custom" 
           serviceAuthorizationManagerType="Fonlow.Web.Identity.RoleAuthorizationManager,
           Fonlow.RealWorldImp">

          </serviceAuthorization>

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

此行将行为连接到服务。

      <service name="Fonlow.Demo.RealWorldService.Service1" 
      behaviorConfiguration="authBehavior">

正如您所看到的,与 MembershipProviderRoleProvider 的用法相比,代码和配置变得更短更优雅。

调整 ASP.NET Identity 2.0

我假设您具有使用 ASP.NET Identity 2.0 和 Entity Framework 的 ASP.NET MVC 5 的一些技能,因此我不会重复您已经知道的内容以及许多文章中已有的内容。

    public class IdentityValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            using (var context = new IdentityDbContext())
            {
                using (var userManager = new UserManager<ApplicationUser>
                                    (new UserStore<ApplicationUser>(context)))
                {
                    var user = userManager.Find(userName, password);
                    if (user == null)
                    {
                        var msg = String.Format("Unknown Username {0} 
                                  or incorrect password {1}", userName, password);
                        Trace.TraceWarning(msg);
                        throw new FaultException(msg);//the client actually will receive 
                            // MessageSecurityException. But if I throw MessageSecurityException, 
                            // the runtime will give FaultException to client without clear message.
                    }
                }
            }
        }
    }

    public class RoleAuthorizationManager : ServiceAuthorizationManager
    {
        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            using (var context = new IdentityDbContext())
            using (var userStore = new UserStore<ApplicationUser>(context))
            {
                using (var userManager = new UserManager<ApplicationUser>(userStore))
                {
                    var identity =operationContext.ServiceSecurityContext.PrimaryIdentity;
                    var user = userManager.FindByName(identity.Name);
                    if (user == null)
                    {
                        var msg = String.Format("Unknown Username {0} .", user.UserName);
                        Trace.TraceWarning(msg);
                        throw new FaultException(msg);
                    }

                    //Assign roles to the Principal property for runtime to match with 
                    //PrincipalPermissionAttributes decorated on the service operation.
                    var roleNames = userManager.GetRoles(user.Id).ToArray();//users without 
                            //any role assigned should then call operations not decorated 
                            //by PrincipalPermissionAttributes
                    operationContext.ServiceSecurityContext.AuthorizationContext.Properties
                         ["Principal"] = new GenericPrincipal
                         (operationContext.ServiceSecurityContext.PrimaryIdentity, roleNames);

                    return true;
                }
            }
        }
    }

正如您所看到的,比较 IdentityModelIdentifyValidator 类的 2 种不同实现,整个结构与使用或不使用 Identity 2.0 相同,

您可能已经认识到 IdentityDbContext 是 VS IDE 在创建 MVC 5 项目时生成的 DbContext 类。 您的 WCF 项目依赖于 MVC 5 项目听起来并不优雅。 因此,我会将相应的模型和 DbContext 从 MVC 5 项目移到一个名为 MyCompany.Security.Identity 的项目中。 然后,MVC 5 项目和 WCF 项目可以共享相同的身份验证机制。

备注

您不会在本文章附带的演示代码中看到上述带有 Identity 2.0 的代码,因为对 Identity 2.0,Entity Framework 和 SQL 数据库的额外依赖项应该在您的 MVC 5 项目中可用,并且最好保持演示代码小巧以用作演示。

下载并运行该项目后,您可能会看到有关 SSL 证书的警告消息,我会让您 Google 或 SO 来找到解决方案。 :)

关注点

WCF 和 MVC 非常复杂、全面和成熟,我们应该更深入地研究,而不是编写笨拙的代码,这些代码可以工作但会产生巨大的技术债务。

Entity Framework 和 Identity 2.0 使事情变得更好,因此您可以轻松摆脱旧提供程序的笨拙之处。

显然,通过每个请求查询数据库上下文来验证用户名和密码效率不高,尤其是在流量很大时。 在现实世界中,可能有许多方法来保护 WCF 服务,而本文只是为您提供在 WCF 中使用 Identity 2.0 的起点。

© . All rights reserved.