之所以为上集,是因为我并没有解决这个问题,写这篇博文的目的是纪录一下我所遇到的问题,以免自己忘记,其实已经忘了差不多了,写的过程也是自己回顾的过程,并且之前收集有关 ASP.NET 5 身份验证的书签已经太多了,所以必须记录下来。

在前年(2014-12-10),我写了这篇博文《爱与恨的抉择:ASP.NET 5+EntityFramework 7》,背景是我当时打算用 ASP.NET 5 重写一个 Web 项目,因为那时候 ASP.NET 5 刚发布不久(之前叫 vNext),所以当时抱了很大的激情投入在上面,但最后的结果是给自己浇了一盆冷水,放弃的原因文章中已经总结了,关于为啥放弃 ASP.NET 5,就是因为身份验证的问题,现在时间过去一年多了,现在回过头来看,其实还是蛮有意思的,比如下面我说一个。

其实最后我想要的功能是不绑定 DbContext,在 ASP.NET 5 项目中,只进行判断操作,身份验证在另外服务中进行,然后在本项目中可以实现类似 FormsAuthentication.SetAuthCookie 操作就可以了,但最后做了几个 Demo 都不能实现,规定的一天时间,已经用完了,所以。。。

上面我前年想要实现的想法,其实我现在也在做这个工作,但中间已经过去一年多时间了,最后还是没有实现。

登录系统是一个独立的站点,这是一个老的项目,身份验证使用的是 Forms Authentication,因为涉及到其它站点,所以不能把登录系统的身份验证改写为 Claims-based 或者 OAuth,这就意味着你需要让其它站点的身份验证方式,来兼容 Forms Authentication,登录系统独立的好处是,其它站点不需要管理用户的登录和注销功能,只需要判断用户有没有通过身份验证即可,就像我当时说的一样,我只需要进行判断操作,但最后做了很多 Demo 研究,还是实现不了,现在回过头来看,当时如果实现了才真是见鬼了,因为 ASP.NET 5 根本就不支持 Forms Authentication(后面详细说),所以,懂得放弃也是好事,毕竟时间是宝贵的。

后来,那个 Web 项目放弃使用 ASP.NET 5 + EF 7,然后用 ASP.NET MVC 5 + EF 6 重写完成了,但心里面还是很不甘心,其实在当时我并不是很懂 ASP.NET Identity 身份验证,所以也导致浪费了很多时间,后来花了点时间重新学习了 ASP.NET Identity,也就是记录的这篇博文《跌倒了,再爬起来:ASP.NET 5 Identity》,这篇博文的主要内容是查看 ASP.NET 5 Identity 的源码,然后抛弃 ApplicationDbContext、UserManager、SignInManager 等等,直接实现用户的登录操作,并且成功实现验证,看到博文最后,你会发现 ASP.NET Identity 和之前的 Forms Authentication 还是有很多不同的,但都是基于 Cookie 加密的方式,下面看三段代码:

Forms Authentication 方式登录:

System.Web.Security.FormsAuthentication.SetAuthCookie("xishuai", false);

ASP.NET Identity 方式登录(截止 2015-01-11):

var userId = await UserManager.GetUserIdAsync(user, cancellationToken);
Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider));

ASP.NET Identity 方式登录(最新,来自 SignInManager.cs):

var userId = await UserManager.GetUserIdAsync(user);
await Context.Authentication.SignInAsync(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme, StoreTwoFactorInfo(userId, loginProvider));

首先,ASP.NET Identity 和 Forms Authentication 都是通过把用户信息加密后,放入响应头的 Cookie 中,只不过两种 Cookie 加密的方式不同(ASP.NET Identity 会更加复杂),所以如果登录方式使用的 Forms Authentication,那在 ASP.NET 5 中就没有办法判断用户验证,因为加密和解密要一一对应,如果不对应,那获取到的 Cookie 就没有办法解密成功,所以也就没有办法通过身份验证(IsAuthenticated 为 false),另外,关于 ASP.NET Identity,它不像一个技术点,有点类似于框架的概念,只不过把身份验证的内容包装了一下,比如产生了 ApplicationDbContext、UserManager、SignInManager 等等,作用就是让你使用更加方便,查看源码就知道,其实核心内容就是上面那些。

关于 SignInManager.cs 中的代码,我们发现有很大的变化,比如 SignInAsync 中的代码,Context.Authentication.SignInAsync 的实现,我们可以从 Security 项目中找到,具体在 Microsoft.AspNet.Authentication/AuthenticationHandler.cs,感觉和之前的相比变的复杂了。

回到最初的问题:在 ASP.NET 5 中,如何实现身份验证(兼容 Forms Authentication)?

上面的问题虽然看起来很简单,但是有个首要前提:ASP.NET 5 不支持 Forms Authentication,那么这个问题就变得复杂了,但我们可以拆分下:

  1. 了解现阶段 ASP.NET 5 身份验证的实现方式。
  2. 在 ASP.NET 5 中,解密 Cookie(通过 Forms Authentication 加密)。

我们先研究第一问题,首先,我们不使用 ASP.NET 5 Identity,而是直接登录进行身份验证,为什么要这么做?因为登录系统不能重写,所以我们使用 ASP.NET 5 Identity 也没有什么意义,况且多了一大堆不必要的东西(UserManager、SignInManager 等),会让问题变的复杂,在之前的博文最后,有一个简单示例,如下:

//app.UseIdentity();
app.UseCookieAuthentication((cookieOptions) =>
{
cookieOptions.AuthenticationType = IdentityOptions.ApplicationCookieAuthenticationType;
cookieOptions.AuthenticationMode = AuthenticationMode.Active;
cookieOptions.CookieHttpOnly = true;
cookieOptions.CookieName = ".CookieName";
cookieOptions.LoginPath = new PathString("/Account/Login");
cookieOptions.CookieDomain = ".mysite.com";
}, "AccountAuthorize"); [AllowAnonymous]
public IActionResult Login(string returnUrl = null)
{
var userId = "xishuai";
var identity = new ClaimsIdentity(IdentityOptions.ApplicationCookieAuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userId));
Response.SignIn(identity);
return Redirect(returnUrl);
}

上面是一年前的代码,一年后变成了这样:

//app.UseIdentity();
app.UseCookieAuthentication((cookieOptions) =>
{
cookieOptions.AutomaticAuthenticate = true;
cookieOptions.AutomaticChallenge = true;
cookieOptions.CookieHttpOnly = true;
cookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(43200);
cookieOptions.LoginPath = new PathString("/account/login");
cookieOptions.CookieName = ".CNBlogsCookie";
cookieOptions.CookiePath = "/";
}); public async Task<IActionResult> Login(string returnUrl = null)
{
var userId = "xishuai";
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, userId));
await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
return Redirect(returnUrl);
}

上面看似没问题的代码,但实际使用中遇到了很多的问题,比如生成 Cookie 的 Expires 为 Session,也就是我们设置的 ExpireTimeSpan 没有作用,解决方式:SignInAsync 需要传递一个 new AuthenticationProperties() { IsPersistent = true } 参数,另外还有其它问题,我现在已经记不得了,不过记录了一个 Issue:HttpContext.Authentication.SignInAsync not working,再贴一下 project.json 中程序包版本,后来测试很多次,可能是版本不一致引起的:

"dependencies": {
"Microsoft.AspNet.Authentication.Cookies": "1.0.0-rc2-16160",
"Microsoft.AspNet.DataProtection.Extensions": "1.0.0-rc2-15874",
"Microsoft.AspNet.Diagnostics": "1.0.0-rc2-16303",
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc2-15994",
"Microsoft.AspNet.Mvc": "6.0.0-rc2-16614",
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc2-16614",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc2-16156",
"Microsoft.AspNet.StaticFiles": "1.0.0-rc2-16036",
"Microsoft.AspNet.Tooling.Razor": "1.0.0-rc2-15994",
"Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc2-15905",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-15905",
"Microsoft.Extensions.Logging": "1.0.0-rc2-15907",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-15907",
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-15907",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc2-16142"
}

后来折腾了很久,测试可以使用了,但发布到服务器的时候,又出现了问题,因为站点使用的是负载均衡,需要把程序发布到两台服务器上,当两台服务器同时在跑的时候,比如登录请求到一台服务器,验证刚好请求到另一台服务器,这时候身份验证就没有效果,然后跳转到登录页面,这个问题折腾我很久,自己怎么配置都不行,后来没有办法,向微软提了一个 Issue:Multiple web servers CookieAuthentication does not work,问题提出后,很快有人回复了,问题原因是需要提供一个 key,这个有点像 Forms Authentication 方式中 Web.config 的 MachineKey,我们需要将身份验证的配置,修改如下:

var dataProtection = new Microsoft.AspNet.DataProtection.DataProtectionProvider(new DirectoryInfo(@"c:\shared-auth-ticket-keys\"));

app.UseCookieAuthentication((cookieOptions) =>
{
cookieOptions.AutomaticAuthenticate = true;
cookieOptions.AutomaticChallenge = true;
cookieOptions.CookieHttpOnly = true;
cookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(43200);
cookieOptions.LoginPath = new PathString("/account/login");
cookieOptions.CookieName = ".CNBlogsCookie";
cookieOptions.CookiePath = "/";
cookieOptions.DataProtectionProvider = dataProtection;
});

后来重新发布,测试还是出现问题,和之前的问题一样,跳转到登录页面,然后我尝试把一台服务器生成在 c:\shared-auth-ticket-keys 目录下的 key 文件,拷贝到另外一台服务器中,但还是没用,过了很多天,有人回复了:

You need to point the key directory to a shared directory which both applications can access. Putting it in c:\shared-auth-ticket-keys\ isn't enough in multiple server scenarios, as it's still going to create a key ring local to each machine.

You need to create an UNC share somewhere that both applications can access, and use that, for example \keystore\keystore

Or you implement a key store yourself suitable to your architecture, for example, using SQL Server.

大致意思是,虽然是同一个目录,但会在不同服务器生成不同的 key 文件,所以身份验证就不通过,解决方式是使用 key 共享文件,这样让不同服务器都能访问同一个 key 文件,另外一种方式是将 key 存储在一个地方,比如 SQL Server 中,但我不是很了解 key 的读取和存储方式,所以,我最后尝试用第一种方式解决,只需要我们将目录更改为共享目录:

var dataProtection = new Microsoft.AspNet.DataProtection.DataProtectionProvider(new DirectoryInfo(@"\\10.10.10.10\shared-auth-ticket-keys\"));

后来再重新发布,还是出现了问题,比如共享文件放在一台服务器上,这台服务器访问没用什么问题,但另一台服务器却不能访问,文件资源管理器可以访问此共享文件,这个问题也折腾我很久,但不和 ASP.NET 5 相关,主要问题是不了解 ASP.NET 如何访问共享文件,后来找资料解决了,记录了一篇博文:ASP.NET 访问共享文件夹

目前的情况:第一个问题已经实现,但是比较简陋,开始考虑并实现第二个问题。

一开始的时候,我提了一个 Issue:Share ASP.NET MVC 5 Forms authentication?

这个 Issue 我觉得很有价值,它让我了解了很多东西,比如 ASP.NET 5 不支持 Forms Authentication,ASP.NET 5 和 Forms Authentication 的 Cookie 加密方式不同,ASP.NET 5 会更加复杂,因为登录系统不能被重写,并且 ASP.NET 5 不支持 Forms Authentication,那么摆在我面前的只有一条路,在 ASP.NET 5 中,解密 Cookie(通过 Forms Authentication 加密),针对这个问题,我的一些想法:

其实看起来这个问题好像不是很复杂,通过 Key 加密生成 Cookie(Forms Authentication),然后通过下面方式获取 Cookie(ASP.NET 5):

var cookies = Request.Cookies.First(x => x.Key == ".CNBlogsCookie").Value;

然后通过某些手段解密生成 IdentityUser 对象,对,没错,就这么简单。

我们先不住 ASP.NET 5 中实现下,很简单:

var cookies = "";
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(cookies);
string[] roles = authTicket.UserData.Split(new char[] { ';' });
var user = new GenericPrincipal(User.Identity, roles);

这段代码是执行成功的,但我们需要在 Web.config 中,配置如下代码:

这段代码必须要和登录站点中的配置一样,原因是加密和解密的方式要一一对应,接下来的工作,我们需要在 ASP.NET 5 中实现上面的代码,但你会发现找不到 FormsAuthentication.Decrypt 了,这么办呢?只能查看源码,然后把相关代码贴出来编译一下,如果成功了(我尝试了很多次,因为涉及的代码太多,实现起来非常困难),这是第一步,第二步我们将编译通过的代码,放在 ASP.NET 5 中再编译一次,这个工作我还没做,不过看起来并不是那么简单,因为运行时和基础类库都发生变化了。

如果重写这部分代码,我贴一下需要的一些资源(后面再尝试下):

后来,上面那个 Issue 有人回复如下:

看到这,有点想哭的赶脚,但不管怎样,还是要尝试下,希望下集是一个成功的博文记录,未完待续。。。

最后,贴一下这段时间累积的有关资料:

坎坷路:ASP.NET 5 Identity 身份验证(上集)的更多相关文章

  1. 坎坷路:ASP.NET Core 1.0 Identity 身份验证(中集)

    上一篇:<坎坷路:ASP.NET 5 Identity 身份验证(上集)> ASP.NET Core 1.0 什么鬼?它是 ASP.NET vNext,也是 ASP.NET 5,以后也可能 ...

  2. ASP.NET MVC 随想录——探索ASP.NET Identity 身份验证和基于角色的授权,中级篇

    在前一篇文章中,我介绍了ASP.NET Identity 基本API的运用并创建了若干用户账号.那么在本篇文章中,我将继续ASP.NET Identity 之旅,向您展示如何运用ASP.NET Ide ...

  3. ASP.NET Identity 身份验证和基于角色的授权

    ASP.NET Identity 身份验证和基于角色的授权 阅读目录 探索身份验证与授权 使用ASP.NET Identity 身份验证 使用角色进行授权 初始化数据,Seeding 数据库 小结 在 ...

  4. 【asp.net core 系列】13 Identity 身份验证入门

    0. 前言 通过前两篇我们实现了如何在Service层如何访问数据,以及如何运用简单的加密算法对数据加密.这一篇我们将探索如何实现asp.net core的身份验证. 1. 身份验证 asp.net ...

  5. 采用Asp.Net的Forms身份验证时,非持久Cookie的过期时间会自动扩展

    问题描述 之前没有使用Forms身份验证时,如果在登陆过程中把HttpOnly的Cookie过期时间设为半个小时,总会收到很多用户的抱怨,说登陆一会就过期了. 所以总是会把Cookie过期时间设的长一 ...

  6. 也谈Asp.net 中的身份验证

    钱李峰 的这篇博文<Asp.net中的认证与授权>已对Asp.net 中的身份验证进行了不错实践.而我这篇博文,是从初学者的角度补充了一些基础的概念,以便能有个清晰的认识. 一.配置安全身 ...

  7. asp.net的forms身份验证 单用户身份验证

    asp.net的forms身份验证  单用户身份验证 首先要配置Web.config文件 <system.web> <authentication mode="Forms& ...

  8. 使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)——第1部分

    原文:使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)--第1部分 原文链接:https://www.codeproject.com/Articles/5160941/ASP- ...

  9. 采用Asp.Net的Forms身份验证时,持久Cookie的过期时间会自动扩展

    原文:http://www.cnblogs.com/sanshi/archive/2012/06/22/2558476.html 若是持久Cookie,Cookie的有效期Expiration属性有当 ...

随机推荐

  1. 水印第三版 ~ 变态水印(这次用Magick.NET来实现,附需求分析和源码)

    技能 汇总:http://www.cnblogs.com/dunitian/p/4822808.html#skill 以前的水印,只是简单走起,用的是原生态的方法.现在各种变态水印,于是就不再用原生态 ...

  2. MVC Core 网站开发(Ninesky) 2、栏目

    栏目是网站的常用功能,按照惯例栏目分常规栏目,单页栏目,链接栏目三种类型,这次主要做添加栏目控制器和栏目模型两个内容,控制器这里会用到特性路由,模型放入业务逻辑层中(网站计划分数据访问.业务逻辑和We ...

  3. 用scikit-learn学习DBSCAN聚类

    在DBSCAN密度聚类算法中,我们对DBSCAN聚类算法的原理做了总结,本文就对如何用scikit-learn来学习DBSCAN聚类做一个总结,重点讲述参数的意义和需要调参的参数. 1. scikit ...

  4. angular2系列教程(九)Jsonp、URLSearchParams、中断选择数据流

    大家好,今天我们要讲的是http模块的第二部分,主要学习ng2中Jsonp.URLSearchParams.observable中断选择数据流的用法. 例子

  5. Oracle碎碎念~2

    1. 如何查看表的列名及类型 SQL> select column_name,data_type,data_length from all_tab_columns where owner='SC ...

  6. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  7. 快递Api接口 & 微信公众号开发流程

    之前的文章,已经分析过快递Api接口可能被使用的需求及场景:今天呢,简单给大家介绍一下微信公众号中怎么来使用快递Api接口,来完成我们的需求和业务场景. 开发语言:Nodejs,其中用到了Neo4j图 ...

  8. 分页插件--根据Bootstrap Paginator改写的js插件

    刚刚出来实习,之前实习的公司有一个分页插件,和后端的数据字典约定好了的,基本上是看不到内部是怎么实现的,新公司是做WPF的,好像对于ASP.NET的东西不多,导师扔了一个小系统给我和另一个同事,指了两 ...

  9. Linux 利用Google Authenticator实现ssh登录双因素认证

    1.介绍 双因素认证:双因素身份认证就是通过你所知道再加上你所能拥有的这二个要素组合到一起才能发挥作用的身份认证系统.双因素认证是一种采用时间同步技术的系统,采用了基于时间.事件和密钥三变量而产生的一 ...

  10. 针对Linux ASP.NET MVC网站中 httpHandlers配置无效的解决方案

    近期有Linux ASP.NET用户反映,在MVC网站的Web.config中添加 httpHandlers 配置用于处理自定义类型,但是在运行中并没有产生预期的效果,服务器返回了404(找不到网页) ...