ASP.NET Identity登录原理
什么是Claims-based(基于声明)的认证
首先这个玩意不是微软特有的,Claims-based认证和授权在国外被广泛使用,包括微软的ADFS,Google,Facebook等。 国内我就不知道了,没有使用过国内的第三方登录,有集成过QQ登录或者支付宝登录的同学可以解释一下。
Claims-based认证主要解决的问题?
对比我们传统的Windows认证和Forms认证,claims-based认证这种方式将认证和授权与登录代码分开,将认证和授权拆分成另外的web服务。活生生的例子就是我们的qq集成登录,未必qq集成登录采用的是claims-based认证这种模式,但是这种场景,千真万确就非常适合claims-based认证。
Claims-based认证的主要特点:
- 将认证与授权拆分成独立的服务
- 服务调用者(一般是网站),不需要关注你如何去认证,你用Windows认证也好,用令牌手机短信也好,与我无关。
- 如果用户成功登录的话,认证服务(假如是QQ) 会返回给我们一个令牌。
- 令牌当中包含了服务调用者所需要的信息,用户名,以及角色信息等等。
总的来说就是,我再也不用管你怎么登录,怎么样去拿你有哪些角色了,我只需要把你跳到那个登录站点上,然后它返回给我令牌信息,我从令牌上获取需要的信息来确定你是谁,你拥有什么角色就可以了。
进一步理解Claims-based 认证
为了让大家进一步理解Claims-based认证,我们从一个普通的登录场景开始说起,拿QQ集成登录来举例。
- 用户跑到我们的网站来访问一个需要登录的页面
- 我们的网站检测到用户没有登录,返回一个跳转到QQ登录页的响应(302 指向QQ登录页面的地址并加上一个返回的链接页面,通常是returnUrl=)
- 用户被跳转到指定QQ的登录页面
- 用户在QQ登录页面上输入用户名和密码,QQ会到自己的数据库中查询,一旦登录成功,会返回一个跳转到我们站点的响应(302指向我们的网站页面)
- 用户被跳转到我们网站的一个检测登录的页面,我们可以拿到用户的身份信息,建立ClaimsPrinpical和ClaimsIdentity对象,生成cookie等。
- 我们再把用户带到指定的页面,也就是returnUrl,那是用户登录前最后一次访问的页面
简单的来说,就是把登录的代码(验证用户,获取用户信息)拆分成独立的服务或组件。
ASP.NET 下的 Claims-based认证实现
说完什么是Claims-based认证之后,我们接下来就可以看看ClaimsIdentity以及ClaimsPrincipal这两个类,他们是.NET下Claims-based认证的主要基石。当然正如我们所想,他们继承了接口IIdentity和IPrincipal。
IIdentity封装用户信息
这个接口很简单,它只包含了三个最基本的用户身份信息。
using System.Runtime.InteropServices;
namespace System.Security.Principal
{
//
// 摘要:
// 定义标识对象的基本功能。
[ComVisible(true)]
public interface IIdentity
{
//
// 摘要:
// 获取所使用的身份验证的类型。
//
// 返回结果:
// 用于标识用户的身份验证的类型。
string AuthenticationType { get; }
//
// 摘要:
// 获取一个值,该值指示是否验证了用户。
//
// 返回结果:
// 如果用户已经过验证,则为 true;否则为 false。
bool IsAuthenticated { get; }
//
// 摘要:
// 获取当前用户的名称。
//
// 返回结果:
// 用户名,代码当前即以该用户的名义运行。
string Name { get; }
}
}
IPrincipal 代表着一个安全上下文
这个安全上下文对象包含了上面的identity以及一些角色和组的信息,每一个线程都会关联一个Principal的对象,但是这个对象是属性进程或者AppDomain级别的。ASP.NET自带的 RoleProvider就是基于这个对象来实现的。
CalimsIdentity和ClaimsPrincipal
在System.Security.Claims命名空间下去,我们可以发现这两个对象。下面我们来做一个小例子,这个小例子会告诉我们这两个对象是如何进行认证和授权的。我们要做的demo很简单,建一个空的mvc站点,然后加上一个HomeController,和两个Action。并且给这个HomeController打上Authroize的标签,但是注意我们没有任何登录的代码,只有这个什么也没有的Controller和两个什么也没有的Action。
using System.Web.Mvc;
namespace WebApplication.Controllers
{
[Authorize]
public class HomeController : Controller
{
[Authorize(Roles ="Users")]
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}
[Authorize(Roles = "Managers")]
public ActionResult Manager()
{
return View();
}
}
}
当然,结果也是可想而知的,我们得到了401页面,因为我们没有登录。
下面我们就来实现登录,这里的登录非常简单,我们手动去创建这个ClaimsIdentity和ClaimsPrincipal对象,然后将Principal对象指给当前的HttpContext.Current.User。
protected void Application_AuthenticateRequest()
{
var claims = new List();
claims.Add(new Claim(ClaimTypes.Name, "Bobby"));
claims.Add(new Claim(ClaimTypes.Role, "Users"));
var identity = new ClaimsIdentity(claims, "MyClaimsLogin");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
HttpContext.Current.User = principal;
}
我们在Global.asax中添加了Application_AuthenticateRequest方法,也就是每次MVC要对用户进行认证的时候都会进到我们这个方法里面,然后我们就这样神奇的把用户给登录了。
当然,我们没有Home/Manager的访问权限,因为我们上面只给了用户Users的Role。
using System.Web.Mvc;
namespace WebApplication.Controllers
{
[Authorize]
public class HomeController : Controller
{
[Authorize(Roles ="Users")]
public ActionResult Index()
{
var name = User.Identity.Name;
var isAuthenticated = User.Identity.IsAuthenticated;
ViewBag.Title = "Home Page";
return View();
}
[Authorize(Roles = "Managers")]
public ActionResult Manager()
{
return View();
}
}
}
现在大家知道ClaimsIdentity和ClaimsPrincipal是如何使用了么?这里要注意一下的是,我们没有设置IsAutheiticated为true,在.NET4.5以前,对于GenericIdentity只要设置它的Name的时候IsAutheiticated就自动设置为true了,而对于ClaimsIdentity是在它有了第一个Claim的时候。在.NET4.5以后,我们就可以灵活控制了,默认ClaimsIdentity的IsAutheiticated是false,只有当我们构造函数中指定Authentication Type,它才为true。
最后结论,我们讲了ClaimsIdentity什么的,讲了这么多和今天的主题有嘛关系?我们上面说ASPNET Identity登录有三句话,第一句话可以略过,第二句话就是我们上面讲的。
到底什么是OWIN
首先我们来简单介绍一下OWin,它是由微软ASP.NET小组成员组织成立的一个开源项目。目标是解耦服务器和应用,这里面的服务器主要是指web 服务器,比如说IIS等,全称是Open Web Interface for .Net。OWin可以说是一套定义,默认它是没有什么具体的实现的,那么在它的定义里面是如何实现服务器与应用程序的解耦的呢? 我们又该如何理解服务器与应用程序的解耦呢?
参考资料
- MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN