众所周知,在Asp.net WebAPI中,认证是通过AuthenticationFilter过滤器实现的,我们通常的做法是自定义AuthenticationFilter,实现认证逻辑,认证通过,继续管道处理,认证失败,直接返回认证失败结果,类似如下:

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var principal = await this.AuthenticateAsync(context.Request);
if (principal == null)
{
context.Request.Headers.GetCookies().Clear();
context.ErrorResult = new AuthenticationFailureResult("未授权请求", context.Request);
}
else
{
context.Principal = principal;
}
}

但在.net core中,AuthenticationFilter已经不复存在,取而代之的是认证中间件。至于理由,我想应该是微软觉得Authentication并非业务紧密相关的,放在管道中间件中更合适。那么,话说回来,在.net core中,我们应该怎么实现认证呢?如大家所愿,微软已经为我们提供了认证中间件。这里以CookieAuthenticationMiddleware中间件为例,来介绍认证的实现。

1、引用Microsoft.AspNetCore.Authentication.Cookies包。项目实践中引用的是"Microsoft.AspNetCore.Authentication.Cookies": "1.1.0"。

2、Startup中注册及配置认证、授权服务:

服务注册:

services.AddMvc(options =>
{
//添加模型绑定过滤器
options.Filters.Add(typeof(ModelValidateActionFilter)); //添加授权过滤器,以便强制执行Authentication跳转及屏蔽逻辑
//var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var policy = new AuthorizationPolicyBuilder().AddRequirements(new AuthenticationRequirement()).Build();
options.Filters.Add(new AuthorizeFilter(policy));
}); //services.AddAuthorization(options =>
//{
// options.AddPolicy("RequireAuthentication", policy => policy.AddRequirements(new AuthenticationRequirement()));
//});

大家注意,上面代码中有两处注释掉的地方。第一处注释,RequireAuthenticatedUser()是.net core预定义的授权验证,代表通过授权验证的最低要求是提供经过认证的Identity。Demo中,我的要求也是这个,只要是经过基本认证的用户即可,那为什么Demo中没有使用呢?因为这里是个坑!实际实践中,我发现,采用注释中的做法,无论如何,调用总是返回401,迫不得已,download认证及授权源码,发现该处逻辑是这样的:

var user = context.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (!userIsAnonymous)
{
context.Succeed(requirement);
}

加入断点猛调,发现IsAuthenticated永远是false!!!迫不得已,反编译查看源码,发现ClaimsIdentity的IsAuthenticated属性是这样定义的:

WTF!!!坑爹么这是!!!.net framework中, 记得 这里的逻辑是,只要Name非空,就返回true,到了.net core中成了这样,你说坑不坑。。。

那怎么办?总不能放弃吧?我想,大家第一想法应该是继承ClaimsIdentity自定义一个Identity,尤其是看到属性上那个virtual的时候,我也不例外。可继承后, 发现认证框架那儿依然不认,还是一直返回false,可能是我哪里用的不对吧。所以,Startup中第一处注释出现了。最终解决方案是自定义AuthenticationRequirement及处理器,实现要求的验证,如下:

public class AuthenticationRequirement : AuthorizationHandler<AuthenticationRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthenticationRequirement requirement)
{
var user = context.User;
var userIsAnonymous =
user?.Identity == null
|| string.IsNullOrWhiteSpace(user.Identity.Name);
if (!userIsAnonymous)
{
context.Succeed(requirement);
}
return TaskCache.CompletedTask;
}
}

上述代码红色的部分便是相对默认实现变化的部分。

startup中第二部分注释,是注册授权策略的,注册方法也是官网文档中给出的注册方法。那为什么这里又没有采用呢?因为,如果按注释中的方法配置,我需要在每个希望认证的控制器或方法上都用Authorize标记,甚至还需要在特性上配置角色或策略,而这里我的预设是全局认证,所以,直接以全局过滤器的形式添加到了MVC处理管道中。读到这里,细心的读者应该有疑问了,你一个简单的认证,跟授权毛线关系啊,注册授权过滤器作甚!我也觉得没关系啊,这是net core认证的第二个坑,那就是,在.net core或者微软看来,认证仅仅提供Principal的生成、序列化、反序列化及重新生成Principal,它的职责确实也包括了返回401、403等各种认证失败信息,但这部分不会主动触发,必须有处理管道中其他逻辑去触发。我仔细阅读了官网文档,得出的大致结论是,.net core大概认为,认证是个多样化的过程,不光有我们目前看到的或需要的某一种认证,实际需求中很可能会多种认证并存,我们的API也可能会同时允许多种认证方式通过,所以某一种认证失败就直接返回401或403是错误的。这是实践当中第二个坑!那话说回来,添加了授权,就可以触发这个过程,这个是看源码发现的,具体流程就是,如果授权失败,过滤器会返回一个challengeResult,这个Result最终会跑到认证中间件中的对应Challenge方法,在.net core源码中表现如下:

public async Task ChallengeAsync(ChallengeContext context)
{
ChallengeCalled = true;
var handled = false;
if (ShouldHandleScheme(context.AuthenticationScheme, Options.AutomaticChallenge))
{
switch (context.Behavior)
{
case ChallengeBehavior.Automatic:
// If there is a principal already, invoke the forbidden code path
var result = await HandleAuthenticateOnceSafeAsync();
if (result?.Ticket?.Principal != null)
{
goto case ChallengeBehavior.Forbidden;
}
goto case ChallengeBehavior.Unauthorized;
case ChallengeBehavior.Unauthorized:
handled = await HandleUnauthorizedAsync(context);
Logger.AuthenticationSchemeChallenged(Options.AuthenticationScheme);
break;
case ChallengeBehavior.Forbidden:
handled = await HandleForbiddenAsync(context);
Logger.AuthenticationSchemeForbidden(Options.AuthenticationScheme);
break;
}
context.Accept();
} if (!handled && PriorHandler != null)
{
await PriorHandler.ChallengeAsync(context);
}
}

以其中HandleForbiddenAsync为例,具体又如下:

/// <summary>
/// Override this method to deal with a challenge that is forbidden.
/// </summary>
/// <param name="context"></param>
protected virtual Task<bool> HandleForbiddenAsync(ChallengeContext context)
{
Response.StatusCode = ;
return Task.FromResult(true);
}

这样,经由授权流程触发Challenge,Challenge返回相应验证结果到API调用方。

注册完了认证及授权所需相关服务,接下来注册中间件,如下:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "GuoKun",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(env.ContentRootPath))
}); app.UseMvc();

注意UseCookieAuthentication要放在UseMvc前面。大家注意其中红色部分,这里为什么要自己手动创建DataProtectionProvider呢?因为这里是要做服务集群的,如果单机或单服务实例情况下,采用默认DataProtection机制就可以了。代码中手动指定目录创建,与默认实现的区别就是,默认实现会生成一个与当前机器及应用相关的key进行数据加解密,而手动指定目录创建provider,会在指定的目录下生成一个key的xml文件。这样,服务集群部署时候,加解密key一样,加解密得到的报文也是一致的。别问我怎么知道的,踩过坑,使劲儿调试,外加看官网文档,泪流满面。。。

3、添加控制器模拟登陆及认证授权

[Route("api/[controller]")]
public class AccountController : Controller
{
[AllowAnonymous]
[HttpPost("login")]
public async Task Login([FromBody]User user)
{
IEnumerable<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.UID)
}; await HttpContext.Authentication.SignInAsync("GuoKun",
new ClaimsPrincipal(new ClaimsIdentity(claims)));
} [HttpGet("serverresponse")]
public ContentResult ServerResponse()
{
return this.Content($"来自{((Microsoft.AspNetCore.Server.Kestrel.Internal.Http.ConnectionContext)this.HttpContext.Features).LocalEndPoint.ToString()}的响应:{this.User.Identity.Name ?? "匿名"},您好");
}
}

因为授权现在是全局的,所以在登陆方法上用AllowAnonymous标记,跳过认证及授权。

在ServerResponse方法中,返回当前服务实例绑定的IP及端口号。由于本Demo是采用ANCM寄宿在IIS中的,所以具体服务实例绑定的端口是动态的。

4、部署。具体在IIS中的部署如下:

三个站点的端口分别为9001,9002,9003,具体运行时,ANCM会将IIS的请求代理到KestrlServer。

5、Nginx负载均衡配置:

upstream guokun    {
server localhost:;
server localhost:;
server localhost:;
} server {
listen ;
server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / {
root html;
index index.html index.htm;
proxy_pass http://guokun;
}

这个比较简单,不废话。

6、运行效果:

这里采用Postman模拟请求。当未调用登录API,直接请求api/Account/serverresponse时,如下:

可以看到,直接401了,而且,响应标头中,有个Location,这个是challenge中默认实现的,告诉我们需要去登录认证,认证完了会跳转到当前请求资源url(在MVC中尤其有用)。

接下来,登录:

我们可以看到,登录成功,而且,服务端返回了加密及序列化后的凭证。接下来,我们再请求api/Account/serverresponse:

看到没,请求成功。那么多请求几次,分别得到如下结果:

可以看见,请求已经被负载到了不同的服务实例。

有人会问,为什么不部署在多台不同服务器上啊,搞一台机器在那儿模拟。哥没那么多钱整那么多台机器啊,而且,装虚拟机,配置撑不了,望大神勿喷勿吐槽。

如此,一个简易的基于asp.net core,带认证,具有集群负载的后端,便实现了。

补充说明:

之前,由于网络原因,ClaimsIdentity部分没有下载源码,而是直接反编译的方式查看,导致得出ClaimsIdentity.IsAuthenticated总是返回false的结论,在此更正,并特别感谢Savorboard大神的特别指正。经过翻阅Github上源码,该属性是这样定义的:

/// <summary>
/// Gets a value that indicates if the user has been authenticated.
/// </summary>
public virtual bool IsAuthenticated
{
get { return !string.IsNullOrEmpty(_authenticationType); }
}

之前一直返回false,则是由于登录成功构建ClaimsIdentity时没有指定AuthenticationType。弄清楚了这个,那么对应授权策略的注册,就可以采用如下方式了:

 services.AddMvc(options =>
{
//添加模型绑定过滤器
options.Filters.Add(typeof(ModelValidateActionFilter)); //添加授权过滤器,以便强制执行Authentication跳转及屏蔽逻辑
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
//var policy = new AuthorizationPolicyBuilder().AddRequirements(new AuthenticationRequirement()).Build();
options.Filters.Add(new AuthorizeFilter(policy));
});

相应地,在登录成功后,构建ClaimsIdentity时指定其AuthenticationType:

await HttpContext.Authentication.SignInAsync("GuoKun",
new ClaimsPrincipal(new ClaimsIdentity(claims, "GuoKun")));

asp.net core 认证及简单集群的更多相关文章

  1. net core 认证及简单集群

    net core 认证及简单集群 在Asp.net WebAPI中,认证是通过AuthenticationFilter过滤器实现的,我们通常的做法是自定义AuthenticationFilter,实现 ...

  2. asp.net core 从单机到集群

    asp.net core 从单机到集群 Intro 这篇文章主要以我的活动室预约的项目作为示例,看一下一个 asp.net core 应用从单机应用到分布式应用需要做什么. 示例项目 活动室预约提供了 ...

  3. ASP.NET Core 认证与授权[2]:Cookie认证

    由于HTTP协议是无状态的,但对于认证来说,必然要通过一种机制来保存用户状态,而最常用,也最简单的就是Cookie了,它由浏览器自动保存并在发送请求时自动附加到请求头中.尽管在现代Web应用中,Coo ...

  4. Kubernetes初探[1]:部署你的第一个ASP.NET Core应用到k8s集群

    Kubernetes简介 Kubernetes是Google基于Borg开源的容器编排调度引擎,作为CNCF(Cloud Native Computing Foundation)最重要的组件之一,它的 ...

  5. ASP.NET Core 认证与授权[1]:初识认证

    在ASP.NET 4.X 中,我们最常用的是Forms认证,它既可以用于局域网环境,也可用于互联网环境,有着非常广泛的使用.但是它很难进行扩展,更无法与第三方认证集成,因此,在 ASP.NET Cor ...

  6. ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证

    在上一章中,我们了解到,Cookie认证是一种本地认证方式,通常认证与授权都在同一个服务中,也可以使用Cookie共享的方式分开部署,但局限性较大,而如今随着微服务的流行,更加偏向于将以前的单体应用拆 ...

  7. ASP.NET Core 认证与授权[4]:JwtBearer认证

    在现代Web应用程序中,通常会使用Web, WebApp, NativeApp等多种呈现方式,而后端也由以前的Razor渲染HTML,转变为Stateless的RESTFulAPI,因此,我们需要一种 ...

  8. ASP.NET Core 认证与授权[5]:初识授权

    经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段.在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥 ...

  9. ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?

    在上一章中,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上[Authorize]特性,并指定要执行的策略名称即可,但是,授权 ...

随机推荐

  1. csdn博客又開始更新了

    csdn博客经过两年多的沉寂又開始更新了,这两年偶尔在http://www.cnblogs.com/JerryWang1991/ 写一些博文,写的也比較少,如今工作一年多了,又開始回到csdn上更新. ...

  2. [转]HTML5 classList API

    Having thrust myself into the world of JavaScript and JavaScript Libraries, I've often wondered: Whe ...

  3. 多媒体应用-swift

    照片选择主要是通过UIImagePickerController控制器实例化一个对象,然后通过self.PresentViewController方法推出界面显示.需要实现代理UIImagePicke ...

  4. jQuery动态实现title的修改 失效问题

    最近做了一个网站,一切都很顺利,在上线的时候,突然发现一个严重的问题,开始面对这个问题,完全不知所措(在goole.火狐.IE9及其以上都没得问题:IE8及其低版本都失效)只是浏览器弹出一个bug,但 ...

  5. TCP/IP协议原理与应用笔记15:网络连接设备

    1. 网络连接设备: (1)转发器 Repeater/ 集线器 Hub (2)网桥 Bridge / 交换机 Switch (3)路由器 Router (4)网关 Gateway 2. 从通信角度看待 ...

  6. Android(java)学习笔记149:Android线程形态之 AsyncTask (异步任务)

    1. AsyncTask和Handler的优缺点比较: 1)AsyncTask实现的原理和适用的优缺点        AsyncTask是Android提供的轻量级的异步类,可以直接继承AsyncTa ...

  7. c++匿名类—指针

    1 摘自网上 2代码实例 #include <iostream> #include <list> #include <iterator> #include < ...

  8. 移动终端学习一:css3 Media Queries简介

    移动终端学习之一 css3 Media Queries简介 1.简介 别人写过的我就不重复了,来个链接:http://www.w3cplus.com/content/css3-media-querie ...

  9. html5标签兼容ie6,7,8

    注册博客园已经三年了,这三年一直在忙,没时间写博文.也许是忙,也许是懒吧!当然这三年发生了很多事,我也从开发人员转变为前端人员. 是时候对所学的,所用的知识做一下沉淀了.就从这一篇开始吧! html5 ...

  10. WPF Command命令模式

    //定义接口 public interface IView { bool IsChanged { get; set; } void SetBinding(); void Clear(); } //定义 ...