【ASP.NET Core 认证】认证流程

注册服务

在Startup.ConfigureServices执行services.AddAuthentication()

services.AddAuthentication()

注册如下服务,便于理解省略了部分辅助服务

services.TryAddScoped<IAuthenticationService, AuthenticationService>();
services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();

AddAuthentication(Action)重载

此重载同样会先注册上面的核心服务。然后设置初始化AuthenticationOptions的委托

 services.AddAuthentication(authenticationOptions=> {
    authenticationOptions.AddScheme<CookieAuthenticationHandler>("cookie", "显示名cookie");
    authenticationOptions.AddScheme<JwtBearerHandler>("jwt","显示名jwtToken");
    authenticationOptions.DefaultAuthenticateScheme = "cookie";
    //...其它配置
 });

AddAuthentication(string defaultScheme)重载

内部也是调用上面的方法,只是只设置了AuthenticationOptions.DefaultScheme
AddAuthentication方法始终返回AuthenticationBuilder,它允许我们通过链式调用方式来向AuthenticationOptions添加多个身份验证方案,所以更常见的方式如下:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie().AddJwtBearer();

AddCookie()等同于:

  authenticationOptions.AddScheme<CookieAuthenticationHandler>(CookieAuthenticationDefaults.AuthenticationScheme, "显示名cookie",);
  service.Configre<CookieAuthenticationOptions>(options=>{
             options.Cookie.Name = CookieAuthenticationDefaults.CookiePrefix + name;
            options.CookieManager = new ChunkingCookieManager();
             options.LoginPath = CookieAuthenticationDefaults.LoginPath;
             options.LogoutPath = CookieAuthenticationDefaults.LogoutPath;
             options.AccessDeniedPath = CookieAuthenticationDefaults.AccessDeniedPath;
       }
 });

身份验证流程

为了便于理解后续的概念,下面先以最简单常见的 【用户密码+cookie】 的身份验证方式说说核心流程

登录:

用户输入账号密码提交
服务端验证账号密码
若验证成功,则创建一个包含用户标识的票证(下面会说)
将票证加密成字符串写入cookie

携带cookie请求:

用户发起请求
身份验证中间件尝试获取并解密cookie,进而得到含用户标识的票证(下面会说)
将用户标识设置到HttpContext.User属性

注意:若身份验证中间件即使没有解析得到用户标识,请求也会继续执行,此时以匿名用户的身份在访问系统

AuthenticationMiddleware身份验证中间件

当我们调用app.UseAuthentication();将注册身份验证中间件AuthenticationMiddleware,它负责在请求阶段尝试从请求中获取用户标识,若获取到则赋值给HttpContext.User属性,否则以匿名用户身份继续执行下一个中间件。

流程大致如下:

  1. 遍历程序中所有实现IAuthenticationRequestHandler身份验证处理器,调用其HandleRequestAsync方法,此方法若返回true则终止本次请求
  2. 通过AuthenticationSchemeProvider获取默认身份验证方案
  3. 调用httpContext.AuthenticateAsync(默认方案名)尝试从请求中获取当前用户,内部从IOC容器中获取AuthenticationService并调用其同名方法
  4. 如果前一步获取到用户,则设置到httpContext.User属性上
  5. 无论是否成功获取用户请求都会进入下一个中间件继续后续执行

步骤1中允许AuthenticationHandler在特定情况下终止请求,如果我们将来自定义AuthenticationHandler时实现IAuthenticationRequestHandler可以达到这种目的

AuthenticationMiddleware源码

public class AuthenticationMiddleware
    {
        private readonly RequestDelegate _next;

        public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
        {
            _next = next;
            Schemes = schemes;
        }

        public IAuthenticationSchemeProvider Schemes { get; set; }

        public async Task Invoke(HttpContext context)
        {
            context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
            {
                OriginalPath = context.Request.Path,
                OriginalPathBase = context.Request.PathBase
            });

            // 给每个IAuthenticationRequestHandler方案一个处理请求的机会
            var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
            foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
            {
                var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
                if (handler != null && await handler.HandleRequestAsync())
                {
                    return;
                }
            }

            var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
            if (defaultAuthenticate != null)
            {
                var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
                if (result?.Principal != null)
                {
                    context.User = result.Principal;
                }
            }

            await _next(context);
        }
    }

AuthenticationService.AuthenticateAsync
大致执行如下步骤:

  1. 通过AuthenticationHandlerProvider获取指定或默认的身份验证方案名获取关联的身份验证处理器AuthenticationHandler
  2. 调用AuthenticationHandler.AuthenticateAsync()尝试从请求中获取用户
  3. 返回前尝试将用户标识转换成另一个用户标识

步骤2针对Cookie身份验证来说最终体现为CookieAuthenticationHandler.HandleAuthenticateAsync()
步骤3在将用户标识设置到httpContext.User之前允许我们添加修改用户标识的某些数据。实现方式是定义类实现IClaimsTransformation并将其(推荐单例)注册到依赖注入容器。

CookieAuthenticationHandler.HandleAuthenticateAsync
大致流程如下:

  1. 通过Options.CookieManager获取cookie
  2. 若Options.SessionStore不为空则从其获取票证,否则直接解密cookie得到票证
  3. 处理票证过期(如Option设置了滑动过期,在过期时响应时重新将票证写入cookie)
  4. 回调Events.ValidatePrincipal允许我们修改或替换用户标识

将票证存储到自定义的存储:services.AddAuthentication().AddCookie(opt=> opt.SessionStore = new MySessionStore());
在前一步骤AuthenticationService.AuthenticateAsync的步骤3中允许我们的代码替换/修改用户标识,在这里的步骤4也允许做类似的事,区别在于前者是针对所有身份验证方案有效的,如默认身份验证方案为JwtBeaerToken时也有效。而后者只是针对cookie身份验证有效。

CookieAuthenticationHandler.HandleChallengeAsync
默认大致流程如下:

  1. 组织登录页地址:Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, 当前地址);
  2. 回调Events.RedirectToLogin,若是ajax请求则返回401状态码,否则跳转到登录页

默认登录页地址"/Account/Login"
默认处理流程如下:

public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToLogin { get; set; } = context =>
{
      if (IsAjaxRequest(context.Request))
      {
           context.Response.Headers[HeaderNames.Location] = context.RedirectUri;
           context.Response.StatusCode = 401;
      }
      else
      {
           context.Response.Redirect(context.RedirectUri);
      }
      return Task.CompletedTask;
};

我们也可以通过在应用启动时通过选项对象来修改。

services.AddAuthentication().AddCookie(opt =>
{
     opt.LoginPath = "account/mylogin";
     opt.Events.OnRedirectToLogin = context =>
     {
           context.Response.Redirect(context.RedirectUri);
           return Task.CompletedTask;
     };
});

CookieAuthenticationHandler.HandleSignInAsync(登录)
大致步骤如下:

  1. 准备一个CookieSigningInContext(=当前上下文+当前身份验证方案+当前身份验证方案关联的选项对象+需要登录用户的标识+cookie选项+额外数据)
  2. 处理过期时间等属性
  3. 回调Events.SigningIn,允许我们在写入cookie之前做调整
  4. 准备票证(=用户 + 身份验证相关属性 + 身份验证方案名 )
  5. 若此身份验证处理器对应的选项配置了Options.SessionStore则使用此存储,否则加密票证写入cookie
  6. 回调Events.SignedIn
  7. 最后回调Events.RedirectToReturnUrl尝试调整会最初的访问页面

CookieAuthenticationHandler.HandleSignOutAsync(注销)
在Action中调用Context.SingOutAsync可以实现注销,在为传入方案名的情况下使用默认身份验证方案进行注销,假设默认是cookie身份验证,则CookieAuthenticationHandler.HandleSignOutAsync将被执行。大致流程如下:
若选项对象配置了Options.SessionStore则从其清空用户标识
回调Events.SigningOut
清除cookie(可能是含sessionKey或包含用户标识的cookie)
通过回调Events.RedirectToReturnUrl跳转到Options.ReturnUrlParameter页面

posted @ 2019-12-19 11:06  .Neterr  阅读(1586)  评论(0编辑  收藏  举报