.Net Core权限认证基于Cookie的认证&授权.Scheme、Policy扩展
在身份认证中,如果某个Action需要权限才能访问,最开始的想法就是,哪个Action需要权限才能访问,我们写个特性标注到上面即可,[TypeFilter(typeof(CustomAuthorizeActionFilterAttribute))]
/// <summary> /// 这是一个Action的Filter` 但是用作权限验证 /// </summary> public class CustomAuthorizeActionFilterAttribute : Attribute, IActionFilter { private ILogger<CustomAuthorizeActionFilterAttribute> _logger = null; public CustomAuthorizeActionFilterAttribute(ILogger<CustomAuthorizeActionFilterAttribute> logger) { this._logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { //取出Session var strUser = context.HttpContext.Session.GetString("CurrentUser"); if (!string.IsNullOrWhiteSpace(strUser)) { CurrentUser currentUser = Newtonsoft.Json.JsonConvert.DeserializeObject<CurrentUser>(strUser); _logger.LogDebug($"userName is {currentUser.Name}"); } else { context.Result = new RedirectResult("~/Fourth/Login"); } } public void OnActionExecuted(ActionExecutedContext context) { //context.HttpContext.Response.WriteAsync("ActionFilter Executed!"); Console.WriteLine("ActionFilter Executed!"); //this._logger.LogDebug("ActionFilter Executed!"); } }
当然了,要先在服务里面使用Session的服务==》services.AddSession();
但是这样不好。.Net Core框架下,有一个特性Authorize,当我们需要使用的时候,在某个Action上面标注即可
[Authorize] public IActionResult Center() { return Content("Center"); }
我们来运行看一下,会报异常
因为我们没有使用服务,在.Net Core下面,是默认不启用授权过滤器的。这也是.Net Core框架的一个好处,我们需要的时候才进行使用。框架做的少,更轻。
下面我们在服务里面使用授权过滤器的服务
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme). AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { o.LoginPath = new PathString("/Home/Login"); });
再次浏览刚才的页面,这样就会请求到登录页面,会把刚才请求的页面当做一个参数
当然也要使用app.UseAuthentication();这个中间件。
在.Net Core里面,保存登录状态,也是通过Cookie的方式。使用ClaimsIdentity与ClaimsPrincipal
public ActionResult Login(string name, string password) { this._ilogger.LogDebug($"{name} {password} 登陆系统"); #region 这里应该是要到数据库中查询验证的 CurrentUser currentUser = new CurrentUser() { Id = 123, Name = "Bingle", Account = "Administrator", Password = "123456", Email = "415473422@qq.com", LoginTime = DateTime.Now, Role = name.Equals("Bingle") ? "Admin" : "User" }; #endregion #region cookie { ////就很像一个CurrentUser,转成一个claimIdentity var claimIdentity = new ClaimsIdentity("Cookie"); claimIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, currentUser.Id.ToString())); claimIdentity.AddClaim(new Claim(ClaimTypes.Name, currentUser.Name)); claimIdentity.AddClaim(new Claim(ClaimTypes.Email, currentUser.Email)); claimIdentity.AddClaim(new Claim(ClaimTypes.Role, currentUser.Role)); claimIdentity.AddClaim(new Claim(ClaimTypes.Sid, currentUser.Id.ToString())); var claimsPrincipal = new ClaimsPrincipal(claimIdentity); base.HttpContext.SignInAsync(claimsPrincipal).Wait();//不就是写到cookie } #endregion return View(); }
再次进行登录,我们就可以看到这样一个Cookie
在这之后,我们再去访问Genter页面,发现还是和之前返回的结果一样,还是访问不到。这是为什么呢?是因为我们在Action上面打的标签[Authorize],什么都没给,我们做下修改
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] public IActionResult Center() { return Content("Center"); }
现在我们再次进行访问,发现就可以访问成功了
通过User.FindFirstValue(ClaimTypes.Sid);这种方式,可以获取到我们存入的值。
Scheme、Policy扩展
Scheme
#region 设置自己的schema的handler services.AddAuthenticationCore(options => options.AddScheme<MyHandler>("myScheme", "demo myScheme")); #endregion #region Schame 验证 services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;// "Richard";// }) .AddCookie(options => { options.LoginPath = new PathString("/Fourth/Login");// 这里指定如果验证不通过就跳转到这个页面中去 options.ClaimsIssuer = "Cookie"; });
MyHandler类:
/// <summary> /// 自定义的handler /// 通常会提供一个统一的认证中心,负责证书的颁发及销毁(登入和登出),而其它服务只用来验证证书,并用不到SingIn/SingOut。 /// </summary> public class MyHandler : IAuthenticationHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler { public AuthenticationScheme Scheme { get; private set; } protected HttpContext Context { get; private set; } public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { Scheme = scheme; Context = context; return Task.CompletedTask; } /// <summary> /// 认证 /// </summary> /// <returns></returns> public async Task<AuthenticateResult> AuthenticateAsync() { var cookie = Context.Request.Cookies["myCookie"]; if (string.IsNullOrEmpty(cookie)) { return AuthenticateResult.NoResult(); } return AuthenticateResult.Success(this.Deserialize(cookie)); } /// <summary> /// 没有登录 要求 登录 /// </summary> /// <param name="properties"></param> /// <returns></returns> public Task ChallengeAsync(AuthenticationProperties properties) { Context.Response.Redirect("/login"); return Task.CompletedTask; } /// <summary> /// 没权限 /// </summary> /// <param name="properties"></param> /// <returns></returns> public Task ForbidAsync(AuthenticationProperties properties) { Context.Response.StatusCode = 403; return Task.CompletedTask; } /// <summary> /// 登录 /// </summary> /// <param name="user"></param> /// <param name="properties"></param> /// <returns></returns> public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) { var ticket = new AuthenticationTicket(user, properties, Scheme.Name); Context.Response.Cookies.Append("myCookie", this.Serialize(ticket)); return Task.CompletedTask; } /// <summary> /// 退出 /// </summary> /// <param name="properties"></param> /// <returns></returns> public Task SignOutAsync(AuthenticationProperties properties) { Context.Response.Cookies.Delete("myCookie"); return Task.CompletedTask; } private AuthenticationTicket Deserialize(string content) { byte[] byteTicket = System.Text.Encoding.Default.GetBytes(content); return TicketSerializer.Default.Deserialize(byteTicket); } private string Serialize(AuthenticationTicket ticket) { //需要引入 Microsoft.AspNetCore.Authentication byte[] byteTicket = TicketSerializer.Default.Serialize(ticket); return Encoding.Default.GetString(byteTicket); } }
Policy
#region 支持 policy 认证授权的服务 // 指定通过策略验证的策略列 services.AddSingleton<IAuthorizationHandler, AdvancedRequirement>(); services.AddAuthorization(options => { //AdvancedRequirement可以理解为一个别名 options.AddPolicy("AdvancedRequirement", policy => { policy.AddRequirements(new NameAuthorizationRequirement("1")); }); }).AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(options => { options.LoginPath = new PathString("/Fourth/Login"); options.ClaimsIssuer = "Cookie"; }); #endregion
AdvancedRequirement类:
/// <summary> /// Policy 的策略 或者是规则 /// </summary> public class AdvancedRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement) { // 这里可以把用户信息获取到以后通过数据库进行验证 // 这里就可以做一个规则验证 // 也可以通过配置文件来验证 if (context.User != null && context.User.HasClaim(c => c.Type == ClaimTypes.Sid)) { string sid = context.User.FindFirst(c => c.Type == ClaimTypes.Sid).Value; if (!sid.Equals(requirement.RequiredName)) { context.Succeed(requirement); } } return Task.CompletedTask; } }
还需要在Configure方法中对中间件进行使用
app.UseSession(); app.UseCookiePolicy(); // app.UseAuthentication(); // 标识在当前系统中使用这个权限认证
总结:
在.Net Framwork环境授权一般来说是这个样子的,在登录的时候写入Session,在需要控制权限的方法上标机一个权限特性,实现在方法执行前对Session进行判断,如果有Session,就有权限。但是这种方式比较局限。
.Net Core下的权限认证,来自于AuthenticationHttpContextExtensions扩展。
6大方法,可以自行扩展这6个方法:需要自定义一个handler,handler需要继承实现IAuthenticationHandler,IAuthenticationSignInHandler,IAuthenticationSignOutHandler。分别实现6个方法,需要制定在Core中使用。services.AddAuthenticationCore(options => options.AddScheme<MyHandler>("myScheme", "demo myScheme"));
如果使用了Sechme验证,验证不通过的时候,就默认跳转到Account/Login?ReturnUrl=......。权限验证来自于IAuthentizeData:AuthenticationSchemes Policy Roles。权限验证支持Action、控制器、全局三种注册方式。