net core 认证及简单集群

在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 = 403;
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:9001;
server localhost:9002;
server localhost:9003;
} server {
listen 9000;
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")));

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

  1. asp.net core 认证及简单集群

    众所周知,在Asp.net WebAPI中,认证是通过AuthenticationFilter过滤器实现的,我们通常的做法是自定义AuthenticationFilter,实现认证逻辑,认证通过,继续 ...

  2. Nginx+Tomcat简单集群

    1.软件准备下载Nginx和Tomcat解压到一个目录2.修改Tomcat的端口Tomcat1:修改Server.xmlTomcat2:修改Server.xml3.测试Tomcat是否正常运行分别访问 ...

  3. Apache httpd + tomcat 简单集群

    集群其实很简单,我们就来说一下httpd+tomcat集群都要注意哪些部分: 首先使用的东西有 apache-tomcat-8.0.32      下载地址: http://tomcat.apache ...

  4. docker进阶-利用dcoker Swarm搭建简单集群

    什么是Swarm   在介绍Swarm之前我们要说一下什么Docker三剑客? Docker-Machine:负责在多种平台上快速安装 Docker 环境. Docker-Compose:Docker ...

  5. nginx做负载均衡和tomcat简单集群

    Nginx做负载均衡和TOMCAT简单集群                1.下载安装nginx及其依赖包                                               ...

  6. Mysql---搭建简单集群,实现主从复制,读写分离

    参考博客:https://blog.csdn.net/xlgen157387/article/details/51331244 A. 准备:多台服务器,且都可以互相随意访问root用户,都可以随意进行 ...

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

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

  8. Shell脚本实现----Kubernetes单集群二进制部署

     Shell脚本实现----Kubernetes单集群二进制部署   搭建Kubernetes集群环境有以下三种方式: 1. Minikube安装方式Minikube是一个工具,可以在本地快速运行一个 ...

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

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

随机推荐

  1. Java并发模型(一)

    学习资料来自http://ifeve.com/java-concurrency-thread-directory/ 一.多线程 进程和线程的区别: 一个程序运行至少一个进程,一个进程至少包含一个线程. ...

  2. listen 71

    Creepy People Leave You Cold Jack Nicholson, playing the crazed caretaker in The Shining, makes me r ...

  3. 002-CSS基础

    1 CSS和文档 CSS 层叠样式表 元素 每个元素都会生成一个框(box) 元素 = 替换元素 + 非替换元素 替换元素 显示的内容是元素内的某个属性而不是元素本身, 如img 非替换元素 大部分类 ...

  4. CSS实现简单无缝滚动

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. BZOJ_1999_[Noip2007]Core树网的核_单调队列+树形DP

    BZOJ_1999_[Noip2007]Core树网的核_单调队列+树形DP Description 设T=(V, E, W) 是一个无圈且连通的无向图(也称为无根树),每条边带有正整数的权,我们称T ...

  6. AtCoder Regular Contest 073 E:Ball Coloring

    题目传送门:https://arc073.contest.atcoder.jp/tasks/arc073_c 题目翻译 给你\(N\)个袋子,每个袋子里有俩白球,白球上写了数字.对于每一个袋子,你需要 ...

  7. The specified named connection is either not found in the configuration, not intended to be used

    今天用EF遇到一个问题, The specified named connection is either not found in the configuration, not intended t ...

  8. win10 ObservableCollection 排序自动收缩问题

    ObservableCollection本身是没有排序Sort功能的,不过我们可以通过冒泡排序来实现,以下是扩展功能: public static void Sort<T>(this Ob ...

  9. Spring入门第十九课

    后置通知 看代码: package logan.study.aop.impl; public interface ArithmeticCalculator { int add(int i, int j ...

  10. Linux做脚本定时任务(定时清理日志)

    无论一些面试问题,还是实际应用,都会用到虚拟机的定时任务.现做定时清理日志日志做一总结. 1.查看/etc/crontab文件. linux 系统则是由 cron (crond) 这个系统服务来控制的 ...