【ASP.NET Core 认证】Cookie认证

添加身份认证中间件

        app.UseRouting();
    
        // 身份认证中间件
        app.UseAuthentication();
    
        app.UseEndpoints(endpoints =>
        {
            //省略...
        });

UseAuthentication一定要放在UseRoutingUseEndpoints之间

添加认证服务

        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)//指定默认身份认证方式
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            {
                // 在这里对该方案进行详细配置
            });

CookieAuthenticationDefaults是一个静态类,定义了一些常用的默认值:

public static class CookieAuthenticationDefaults
{
    // 认证方案名
    public const string AuthenticationScheme = "Cookies";

    // Cookie名字的前缀
    public static readonly string CookiePrefix = ".AspNetCore.";
    
    // 登录路径
    public static readonly PathString LoginPath = new PathString("/Account/Login");

    // 注销路径
    public static readonly PathString LogoutPath = new PathString("/Account/Logout");

    // 访问拒绝路径
    public static readonly PathString AccessDeniedPath = new PathString("/Account/AccessDenied");

    // return url 的参数名
    public static readonly string ReturnUrlParameter = "ReturnUrl";
}

AddCookie方法第二个参数是CookieAuthenticationOptions,可以针对登录、注销、Cookie等方面进行详细配置。
由于在针对选项进行配置时,需要依赖DI容器中的服务,所以不得不将选项的配置从AddCookie扩展方法中提出来。
请查看以下代码:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IDataProtectionProvider>((options, dp) =>
            {
                options.LoginPath = new PathString("/Account/Login");
                options.LogoutPath = new PathString("/Account/Logout");
                options.AccessDeniedPath = new PathString("/Account/AccessDenied");
                options.ReturnUrlParameter = "returnUrl";

                options.ExpireTimeSpan = TimeSpan.FromDays(14);
                //options.Cookie.Expiration = TimeSpan.FromMinutes(30);
                //options.Cookie.MaxAge = TimeSpan.FromDays(14);
                options.SlidingExpiration = true;
                
                options.Cookie.Name = "auth";
                //options.Cookie.Domain = ".xxx.cn";
                options.Cookie.Path = "/";
                options.Cookie.SameSite = SameSiteMode.Lax;
                options.Cookie.HttpOnly = true;
                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                options.Cookie.IsEssential = true;
                options.CookieManager = new ChunkingCookieManager();
                
                options.DataProtectionProvider ??= dp;
                var dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", CookieAuthenticationDefaults.AuthenticationScheme, "v2");
                options.TicketDataFormat = new TicketDataFormat(dataProtector);

                options.Events.OnSigningIn = context =>
                {
                    Console.WriteLine($"{context.Principal.Identity.Name} 正在登录...");
                    return Task.CompletedTask;
                };

                options.Events.OnSignedIn = context =>
                {
                    Console.WriteLine($"{context.Principal.Identity.Name} 已登录");
                    return Task.CompletedTask;
                };
                
                options.Events.OnSigningOut = context =>
                {
                    Console.WriteLine($"{context.HttpContext.User.Identity.Name} 注销");
                    return Task.CompletedTask;
                };

                options.Events.OnValidatePrincipal += context =>
                {
                    Console.WriteLine($"{context.Principal.Identity.Name} 验证 Principal");
                    return Task.CompletedTask;
                };
            });
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

参数说明:

  • LoginPath:登录页路径,指向一个Action。默认/Account/Login。当服务端不允许匿名访问而需要确认用户信息时,跳转到该页面进行登录。另外,登录方法通常会有一个参数,叫作return url,用来当用户登录成功时,自动跳转回之前访问的页面。这个参数也会自动传递给该Action,下方会详细说明。
  • LogoutPath:注销路径,指向一个Action。默认/Account/Logout
  • AccessDeniedPath:访问拒绝页路径,指向一个Action。默认/Account/AccessDenied。当出现Http状态码 403 时,会跳转到该页面。
  • ReturnUrlParameter:上面提到的return url的参数名,参数值会通过 query 的方式传递到该参数中。默认ReturnUrl。
  • ExpireTimeSpan:认证票据(authentication ticket)的有效期,默认 14 天。认证票据在代码中表现为类型为AuthenticationTicket的对象。认证票据存储在Cookie中,它的有效期与所在Cookie的有效期是独立的,如果Cookie没有过期,但是认证票据过期了,也无法通过认证。
  • Cookie.Expiration:Cookie的过期时间,用于持久化Cookie。对应Cookie中的Expires属性,是一个明确地时间点。(禁用?)
  • Cookie.MaxAge:Cookie的过期时间,对应Cookie中的Max-Age属性,是一个时间范围。如果Cookie的Max-Age和Expires同时设置,则以Max-Age为准
  • SlidingExpiration:指示Cookie的过期方式是否为滑动过期。默认true。若为滑动过期,服务端收到请求后,如果发现Cookie的生存期已经超过了一半,那么服务端会重新颁发一个全新的Cookie,Cookie的过期时间和认证票据的过期时间都会被重置。
  • Cookie.Name:该Cookie的名字,默认是.AspNetCore.Cookies。
  • Cookie.Domain:该Cookie所属的域,对应Cookie的Domain属性。一般以“.”开头,允许subdomain都可以访问。默认为请求Url的域。
  • Cookie.Path:该Cookie所属的路径,对应Cookie的Path属性。默认/。
  • Cookie.SameSite:设置通过浏览器跨站发送请求时决定是否携带Cookie的模式,共有三种,分别是None、Lax和Strict。
    • SameSiteMode.Unspecified:使用浏览器的默认模式。
    • SameSiteMode.None:不作限制,通过浏览器发送同站或跨站请求时,都会携带Cookie。这是非常不建议的模式,容易受到CSRF攻击
    • SameSiteMode.Lax:默认值。通过浏览器发送同站请求或跨站的部分GET请求时,可以携带Cookie。
    • SameSiteMode.Strict:只有通过浏览器发送同站请求时,才会携带Cookie。
  • Cookie.HttpOnly:指示该Cookie能否被客户端脚本(如js)访问。默认为true,即禁止客户端脚本访问,这可以有效防止XSS攻击。
  • Cookie.SecurePolicy:设置Cookie的安全策略,对应于Cookie的Secure属性(SameAsRequest、Always、None)。
    • CookieSecurePolicy.Always:设置Secure=true,当发送登录请求和后续请求均为Https时,浏览器才将Cookie发送给服务端。
    • CookieSecurePolicy.None:不设置Secure,即发送Http请求和Https请求时,浏览器都会将Cookie发送给服务端。
    • CookieSecurePolicy.SameAsRequest:默认值。视情况而定,如果登录接口是Https请求,则设置Secure=true,否则,不设置。
  • Cookie.IsEssential:是否强制存储cookie,也就是当用户不同意使用cookie的时候,你也可以通过设置这个属性为true把cookie强制存储.
  • CookieManager:Cookie管理器,用于添加响应Cookie、查询请求Cookie或删除Cookie。默认是ChunkingCookieManager。
  • DataProtectionProvider:认证票据加密解密提供器,可以按需提供相应的加密解密工具。默认是KeyRingBasedDataProtector。有关数据保护相关的知识,请参考官方文档-ASP.NET Core数据保护。
  • TicketDataFormat:认证票据的数据格式,内部通过DataProtectionProvider提供的加密解密工具进行认证票据的加密和解密。默认是TicketDataFormat。

以下是部分事件回调:

  • Events.OnSigningIn:登录前回调
  • Events.OnSignedIn:登录后回调
  • Events.OnSigningOut:注销时回调
  • Events.OnValidatePrincipal:验证 Principal 时回调

登录

public class AccountController : Controller
{
    [HttpGet]
    public IActionResult Login([FromQuery] string returnUrl = null)
    {
        ViewBag.ReturnUrl = returnUrl;

        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Login([FromForm] LoginViewModel input)
    {
        ViewBag.ReturnUrl = input.ReturnUrl;

        // 用户名密码相同视为登录成功
        if (input.UserName != input.Password)
        {
            ModelState.AddModelError("UserNameOrPasswordError", "无效的用户名或密码");
        }

        if (!ModelState.IsValid)
        {
            return View();
        }

        var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
        identity.AddClaims(new[]
        {
            new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString("N")),
            new Claim(ClaimTypes.Name, input.UserName)
        });

        var principal = new ClaimsPrincipal(identity);

        // 登录
        var properties = new AuthenticationProperties
        {
            IsPersistent = input.RememberMe,
            ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(60),
            AllowRefresh = true
        };
        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, properties);

        if (Url.IsLocalUrl(input.ReturnUrl))
        {
            return Redirect(input.ReturnUrl);
        }

        return Redirect("/");
    }
}

在SignInAsync扩展方法中,我们可以针对认证进行一些配置,通过AuthenticationProperties。
IsPersistent:票据是否持久化,即票据所在的Cookie是否持久化。如果持久化,则会将下方ExpiresUtc的值设置为Cookie的Expires属性。默认为false。
ExpiresUtc:票据的过期时间,默认为null,如果为null,则CookieAuthenticationHandler会在HandleSignInAsync方法中将Cookie认证方案配置中的CookieAuthenticationOptions.ExpireTimeSpan + AuthenticationProperties.IssuedUtc的结果赋值给该属性。
AllowRefresh:上面提到过,在Cookie的认证方案配置中,可以将过期方式配置为滑动过期,满足条件时,会重新颁发Cookie。实际上,要实现这个效果,还要将AllowRefresh设置为null或者true才可以。默认为null。
IssuedUtc:票据颁发时间,默认为null。一般无需手动赋值,为null时,CookieAuthenticationHandler会在HandleSignInAsync方法中将当前时间赋值给该属性。
这里针对认证票据的有效期详细说明一下:
通过上面我们已经得知,认证票据的有效期是通过AuthenticationProperties.ExpiresUtc来设置的,它是一个明确的时间点,如果我们没有手动赋值给该属性,那么Cookie的认证处理器CookieAuthenticationHandler会将Cookie认证方案配置中的CookieAuthenticationOptions.ExpireTimeSpan + AuthenticationProperties.IssuedUtc的结果赋值给该属性。
而我们又知道,在配置Cookie认证方案时,Cookie.Expiration属性表示的是Cookie的Expires属性,但是它被禁用了,如果强行使用它,我们会得到这样一段选项验证错误信息:

Cookie.Expiration is ignored, use ExpireTimeSpan instead.

可是ExpireTimeSpan属性,注释明确地说它指的不是Cookie的Expires属性,而是票据的有效期,这又是咋回事呢?其实,你可以想象一下以下场景:该Cookie的Expires和Max-Age都没有被设置(程序允许它们为空),那么该Cookie的有效期就是当前会话,但是,你通过设置AuthenticationProperties.IsPersistent = true来表明该Cookie是持久化的,这就产生了歧义,实际上Cookie并没有持久化,但是代码却认为它持久化了。所以,为了解决这个歧义,Cookie.Expiration就被禁用了,而新增了一个ExpireTimeSpan属性,它除了可以作为票据的有效期外,还能在Cookie的Expires和Max-Age都没有被设置但AuthenticationProperties.IsPersistent = true的情况下,将值设置为Cookie的Expires属性,使得Cookie也被持久化。

参考:
https://www.cnblogs.com/xiaoxiaotank/p/15811749.html

posted @ 2020-03-02 15:01  .Neterr  阅读(298)  评论(0编辑  收藏  举报