• .NET CORE Authentication Authorization


    .NET CORE 鉴权

     

    基础信息

    1.什么是鉴权授权?
    • 鉴权是验证用户是否拥有访问系统的权利,授权是判断用户是否有权限做一些其他操作。

    2.传统的Session 和Cookie
    • 主要用于无状态请求下的的用户身份识别,只不过Session将信息存储在服务端,Cookie将信息存储在客户端。

    Session

    1. 在客户端第一次进行访问时,服务端会生成一个Session id返回到客户端

    2. 客户端将Session id存储在本地Cookie后续请求都带上这个id

    3. 服务端从接收到的请求中根据Session id在自己存储的信息中去识别客户端信息

    Cookie

    1. 在客户端访问服务器时,服务端会在响应中颁发一个Cookie

    2. 客户端会把cookie存储,当再访问服务端时会将cookie和请求一并提交

    3. 服务端会检查cookie识别客户端,并也可以根据需要修改cookie的内容


    3.存在的问题

    在分布式或集群系统中使用Session

    假设现在服务器为了更好的承载和容灾将系统做了分布式和集群,也就是有了N个服务端,那是不是每一个服务端都要具有对每一个客户端的Session或者Cookie的识别能力呢?

    其实我们可以使用Session共享的方式用于Session的识别,通常每一个分布式系统都由不同的人负责或者跨网络,做Session共享可能会存在诸多业务因素的影响,就算实现了系统可能也会越来越重?像这种我们可以使用Token校验的方式,每一个客户端登录去向统一的鉴权平台发起,由鉴权平台颁发一个Token,然后后续各个系统的请求都带上这个Token。


    4.Token
    • Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌。

    image.png

    执行步骤

    1. 用户向统一的鉴权系统发起用户名和密码的校验

    2. 校验通过后会颁发一个经过签名的Token,用户就拿着颁发的Token去访问其他三方系统

    3. 三方系统根据对应的加密方式,使用秘钥解密Token以验证合法性,也可以直接请求鉴权授权系统验证当前Token的合法性(除非自己内部系统),


    .NET Core中鉴权

    • Authentication: 鉴定身份信息,例如用户有没有登录,用户基本信息

    • Authorization: 判定用户有没有权限

    1.常规的Cookie+Filter模式
    • 1.基本思路

      1.在控制器中登录传入用户名密码,然后写入HttpContext.Response.Cookies。

      2.定义IAuthorizationFilter拦截器,用于验证是否有Cookie信息。

    • 2.实现方式

      1.在Startup中注入鉴权服务和Cookie服务

      public void ConfigureServices(IServiceCollection services)
      {
      	services.AddAuthentication().AddCookie();
      }
      

      2.实现自定义拦截器,并在控制器上方标记,以确保调用接口前被拦截,并实施鉴权

      public class CustomAuthorizationFilterAttribute : Attribute, IAuthorizationFilter
       {
           public void OnAuthorization(AuthorizationFilterContext context)
           {
                //如果控制器上被AllowAnonymousAttribute特性标记,则不检查
               if (context.ActionDescriptor.EndpointMetadata.Any(item => item is AllowAnonymousAttribute))
               {
                   return;
               }
               //获取Cookie中的用户信息
               string sUser = context.HttpContext.Request.Cookies["CurrentUser"];
               //如果没有Cookie直接跳到登录页面,否则就通过
               if (sUser == null)
               {
                   context.Result = new RedirectResult("~/Home/Login");
               }
              return;
           }
       }
      
      

      3.实现登录写入Cookie

      [AllowAnonymous]
      public IActionResult Login(string name, string password)
      {
      	//用户名密码不正确直接返回
          if (!"Admin".Equals(name) || !"123456".Equals(name))
          {
             return new JsonResult(new{ Result = false,Message = "登录失败" });
          }
      
      	//通过校验向Cookie中写入信息
      	base.HttpContext.Response.Cookies.Append("CurrentUser", "Admin", new CookieOptions()
          {
             Expires = DateTime.UtcNow.AddMinutes(30);
         	});
        	return new JsonResult(new{ Result = true,Message = "登录成功"});
      }
      
    2.NET Core中提供的鉴权基本介绍

    在.NetCore中将鉴权和授权,分别以app.UseAuthentication()和app.UseAuthorization() 这2个不同的中间件来实现,完成鉴权主要由HttpContext的扩展类AuthenticationHttpContextExtensions中提供的方法来完成的,不用自己在手动写Cookie或者Session。

    1. HttpContext.SignInAsync();
    2. HttpContext.AuthenticateAsync();
    3. HttpContext.SignOutAsync();
    4. HttpContext.ChallengeAsync();
    5. HttpContext.ForbidAsync();

    • 1.其实最终调用的是实现了IAuthenticationService提供的5个核心接口方法,我们是怎么知道的呢?

      public interface IAuthenticationService
       {
         //查询鉴权
         Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
         //登录写入鉴权凭证
         Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
         //退出登录清理凭证
         Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
         Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
         //禁止指定的身份验证方案
         Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
       }
      
    • 2.我们根据Startup类中,找到注册服务时的services.AddAuthentication()源码,查找对应源码不难发现,其实是往IOC中注册了几个接口

      注入接口名称介绍
      IAuthenticationHandlerProvider 负责对用户凭证的验证,提供IAuthenticationHandler处理器给IAuthenticationService用于处理鉴权请求,可以实现IAuthenticationHandler自定义处理器
      IAuthenticationSchemeProvider 选择标识使用的是哪种认证方式及策略,用于映射IAuthenticationHandler的选择
      IAuthenticationService 提供鉴权统一认证的5个核心业务接口
    • 3 .我们首先找到IAuthenticationService实现类AuthenticationService中的SignInAsync方法,结合IAuthenticationHandlerProvider 和IAuthenticationSchemeProvider得到一个IAuthenticationHandler。

    • 4.最终将鉴权写入和读取都由IAuthenticationHandler它的实例来完成,至于实例的选择根据用户来决定,在注入时使用AddCookie()就会注入一个CookieAuthenticationHandler,如果使用AddJwtBearer()那就会注入一个JwtBearerHandler

      甚至我们可以自定义实现IAuthenticationHandler的鉴权处理器


    3.自定义IAuthenticationHandler

    根据上面的内容,我们自己来扩展一个自己的IAuthenticationHandler,简单的理解一下

    • 1.继承接口IAuthenticationHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler 实现5个接口

      public class CustomAuthenticationHandler : 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;
          }
      
          public async Task<AuthenticateResult> AuthenticateAsync()
          {
              var cookie = Context.Request.Cookies["CustomCookie"];
              if (string.IsNullOrEmpty(cookie))
              {
                  return AuthenticateResult.NoResult();
              }
               AuthenticateResult result = AuthenticateResult.Success(Deserialize(cookie));
      		 return await Task.FromResult(result);
          }
      
          public Task ChallengeAsync(AuthenticationProperties properties)
          {
          	//跳转页面--上端返回json
              //Context.Response.Redirect("/Account/Login");
              return Task.CompletedTask;
          }
      
          public Task ForbidAsync(AuthenticationProperties properties)
          {
              Context.Response.StatusCode = 403;
              return Task.CompletedTask;
          }
      
          public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
          {
              var ticket = new AuthenticationTicket(user, properties, Scheme.Name);
              Context.Response.Cookies.Append("CustomCookie", Serialize(ticket));
              return Task.CompletedTask;
          }
      
          public Task SignOutAsync(AuthenticationProperties properties)
          {
              Context.Response.Cookies.Delete("CustomCookie");
              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);
          }
      }
      
    • 2.在Startup类的IOC容器中注册服务,并且在Scheme中加入自定义处理器,因为是自定义的IAuthenticationHandler,所以需要对应一个Scheme,以供在使用时选择以那种方式完成

      services.AddAuthentication(options => 
       {
       	options.AddScheme<CustomHandler>("CustomScheme", "AuthenticationHandlerScheme");
       }).AddCookie();
      
    • 3.然后在登录时和访问Api时分别写入鉴权信息和查询鉴权信息,顺便介绍下写入信息时的ClaimsIdentity对象

      关键字描述信息
      Claims 一项信息,例如工牌的姓名是一个Claims ,工牌号码也是一个Claims
      ClaimsIdentity 一组Claims 组成的信息,就是一个用户身份信息
      ClaimsPrincipal 一个用户有多个身份
      AuthenticationTicket 用户票据,用于包裹ClaimsPrincipal
    • 1.写入鉴权

      [AllowAnonymous]
      public IActionResult Login(string name, string password)
      {
      	//用户名密码不正确直接返回
          if (!"Admin".Equals(name) || !"123456".Equals(name))
          {
             return new JsonResult(new{ Result = false,Message = "登录失败" });
          }
      
      	var claimIdentity = new ClaimsIdentity("CustomAuthentication");
          claimIdentity.AddClaim(new Claim(ClaimTypes.Name, name));
          claimIdentity.AddClaim(new Claim(ClaimTypes.Email, "MyEmail@qq.com"));
          claimIdentity.AddClaim(new Claim(ClaimTypes.System, "EmployeeManager"));
      
      	var Properties = new AuthenticationProperties {
               ExpiresUtc = DateTime.UtcNow.AddMinutes(30),
      	};
      	
      	//写入鉴权信息
          await base.HttpContext.SignInAsync("CustomScheme", new ClaimsPrincipal(claimIdentity),Properties );
        	return new JsonResult(new{ Result = true,Message = "登录成功"});
      }
      
    • 2.查询鉴权

      //2.查询鉴权
      public async Task<IActionResult> Authentication()
      {	
      	//调用AuthenticateAsync查询鉴权信息
          var result = await base.HttpContext.AuthenticateAsync("CustomScheme");
          if (result?.Principal != null)
          {
              base.HttpContext.User = result.Principal;
              return new JsonResult(new{ Result = true,Message = $"认证成功,包含用户{base.HttpContext.User.Identity.Name}"});
          }
         return new JsonResult(new{Result = true,Message = $"认证失败,用户未登录"});
      }
      
    • 3.清除鉴权信息

      //3.退出清除
      public async Task<IActionResult> Logout()
      {	
      	 //退出登录时清除鉴权信息
           await base.HttpContext.SignOutAsync("CustomScheme");
           return new JsonResult(new{ Result = true,Message = "退出成功"});
      }
      
    4.使用框架提供的Cookie鉴权方式
    • 1.首先在服务容器注入鉴权服务和Cookie服务支持

      services.AddAuthentication(options =>
      {
          options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;//不能少
          options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
          options.DefaultChallengeScheme = "Cookie/Login";
      })
      .AddCookie(options =>{});
      
    • 2.注册鉴权和授权中间件,用于在管道中调用拦截校验鉴权和授权

      app.UseAuthentication();
      app.UseAuthorization();         
      
    • 3.在控制器引入特性 [Authorize] ,调用登录接口时使用HttpContext.SignInAsync()写入鉴权信息

      [AllowAnonymous]
      public IActionResult Login(string name, string password)
      {
      	//用户名密码不正确直接返回
          if (!"Admin".Equals(name) || !"123456".Equals(name))
          {
             return new JsonResult(new{ Result = false,Message = "登录失败" });
          }
      	
      	var claimIdentity = new ClaimsIdentity("Cookie");
          claimIdentity.AddClaim(new Claim(ClaimTypes.Name, name));
          claimIdentity.AddClaim(new Claim(ClaimTypes.Email, "MyEmail@qq.com"));
          claimIdentity.AddClaim(new Claim(ClaimTypes.System, "EmployeeManager"));
      
      	var Properties = new AuthenticationProperties {
               ExpiresUtc = DateTime.UtcNow.AddMinutes(30),
      	};
      	
      	//写入鉴权信息
          await base.HttpContext.SignInAsync(new ClaimsPrincipal(claimIdentity),Properties );
        	return new JsonResult(new{ Result = true,Message = "登录成功"});
      }
      
    • 4.因为调用HttpContext.AuthenticateAsync()获取鉴权的步骤,由第二部注册的中间件AuthenticationMiddleware已经替我们完成,所以可以直接在控制器内部获取HttpContext.User信息,系统提供的相对于自己实现的,框架帮我们封装了获取鉴权信息,并把它加入管道中,而不用每次在控制器中手动获取鉴权信息。

       public async Task<IActionResult> Authentication()
       {
       	 //这里由中间件管道已经实现了鉴权信息取值
           var CookiesInfo = base.HttpContext.User;
           if (CookiesInfo != null)
           {
               return new JsonResult(new { Result = true, Message = $"鉴权认证成功,用户已登录" });
           }
           return new JsonResult(new { Result = true, Message = $"鉴权认证失败,用户未登录" });
       }
      
    5.Cookie鉴权的扩展


    主要介绍CookieAuthenticationHandler和CookieAuthenticationOptions中的Events 和 ITicketStore

    1.CookieAuthenticationHandler
    • 1.CookieAuthenticationHandler处理器是在.NetCore鉴权系统中,用来处理Cookie鉴权模式的核心处理方法,在上面部分已经简单介绍过,它是由AuthenticationBuilder的扩展类CookieExtensions注册服务AddCookie()来提供的.

    • 2.在最终AddCookie()的重载方法中,我们注册了实现自IAuthenticationHandler的CookieAuthenticationHandler并且将CookieAuthenticationOptions委托传入,而CookieAuthenticationOptions提供的扩展功能,能使用户能最大限度的实现个性化定制和配置。

    2.Events
    • 1.Events 是一个CookieAuthenticationEvents类型的属性,在他身上定义了委托用于给用户在鉴权的过程中扩展自己的业务

    • 2.扩展Events

      public Task ExtentionEvent(CookieAuthenticationOptions cookieAuthenticationOptions)
      {
      	cookieAuthenticationOptions.Event = new CookieAuthenticationEvents()
      	{
      			OnSignedIn = async context =>
                  {
                        Console.WriteLine($"{context.Request.Path} is OnSignedIn");
                        await Task.CompletedTask;
                  },
                 OnSigningIn = async context =>
                  {
                        Console.WriteLine($"{context.Request.Path} is OnSigningIn");
                        await Task.CompletedTask;
                  },
                 OnSigningOut = async context =>
                 {
                        Console.WriteLine($"{context.Request.Path} is OnSigningOut");
                        await Task.CompletedTask;
                 }
      	}
      }
      
    • 3.注册到IOC容器

       services.AddCookie(cookieAuthenticationOptions=>{
       	ExtentionEvent(cookieAuthenticationOptions);
       })
      
    3.ITicketStore
    • 1.ITicketStore主要用于持久化Cookie,它能根据用户自己定制选择Cookie的存储方式,使用ITicketStore会将完整的Cookie存储在服务端, 然后返回一个Cookie id到客户端,客户端访问带上id,经过ITicketStore来得到完整的Cookie信息,跟Seession的方式有点类似,但是他并不是,知识实现策略相同而已。

    • 2.扩展ITicketStore,实现将Cookie存储在内存中,当然这个存储介质,可以是内存,也可以是Redis

      public class MemoryCacheTicketStore : ITicketStore
      {
          private const string Prefix = "Extentions-";
         
          private IMemoryCache _cache;
      
          public MemoryCacheTicketStore(IMemoryCache memoryCache)
          {
              _cache = memoryCache;
          }
      
          public async Task<string> StoreAsync(AuthenticationTicket ticket)
          {
              var key = KeyPrefix + Guid.NewGuid().ToString("N");
              await RenewAsync(key, ticket);
              return key;
          }
      
          public Task RenewAsync(string key, AuthenticationTicket ticket)
          {
              var options = new MemoryCacheEntryOptions();
              var expiresUtc = ticket.Properties.ExpiresUtc;
              if (expiresUtc.HasValue)
              {
                  options.SetAbsoluteExpiration(expiresUtc.Value);
              }
              options.SetSlidingExpiration(TimeSpan.FromHours(1));
              _cache.Set(key, ticket, options);
              return Task.CompletedTask;
          }
      
          public Task<AuthenticationTicket> RetrieveAsync(string key)
          {
              _cache.TryGetValue(key, out AuthenticationTicket ticket);
              return Task.FromResult(ticket);
          }
      
          public Task RemoveAsync(string key)
          {
              _cache.Remove(key);
              return Task.CompletedTask;
          }
      }
      
    • 3.在Ioc容器中注册

      //将MemoryCacheTicketStore注册到容器
      services.AddScoped<ITicketStore, MemoryCacheTicketStore>();
      //注册内存缓存
      services.AddMemoryCache();
      
      services.AddCookie(cookieAuthenticationOptions=>{
       	cookieAuthenticationOptions.SessionStore = services.BuildServiceProvider().GetService<ITicketStore>();;
       })
      
    4.总结

    在.NET Core框架提供的鉴权模块中,首先IOC注册IAuthenticationService,IAuthenticationSchemeProvider ,IAuthenticationHandlerProvider 服务,然后由IAuthenticationService服务,进行鉴权的核心业务处理,由IAuthenticationSchemeProvider根据scheme负责分配Handler,由IAuthenticationHandlerProvider 构建具体的处理handler ,最终使用具体的handler执行鉴权处理。

     
    分类: .NET
  • 相关阅读:
    管理ceph缓存池
    Ceph更换OSD磁盘
    crushmap磁盘智能分组
    Angular 初体验
    音视频开发-FFmpeg
    开源项目OEIP 游戏引擎与音视频多媒体(UE4/Unity3D)
    Yolov3代码分析与训练自己数据集
    整合Yolov3到UE4/Unity3D
    CUDA版Grabcut的实现
    CUDA加opencv复现导向滤波算法
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/16246095.html
Copyright © 2020-2023  润新知