用于 WCF 服务的 ASP.NET Identity 2.0 身份验证和授权
真实世界 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 MembershipProvider
和 RoleProvider
的文章,但是,很少有关于使用 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">
正如您所看到的,与 MembershipProvider
和 RoleProvider
的用法相比,代码和配置变得更短更优雅。
调整 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;
}
}
}
}
正如您所看到的,比较 IdentityModel
上 IdentifyValidator
类的 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 的起点。