.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執行鑒權處理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM