【ASP.NET Core 认证】Cookie认证
添加身份认证中间件
app.UseRouting();
// 身份认证中间件
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
//省略...
});
UseAuthentication
一定要放在UseRouting
和UseEndpoints
之间
添加认证服务
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也被持久化。