關於JWT,可以說是分散式系統下的一個利器,我在我的很多項目實踐中,認證系統的第一選擇都是JWT。它的優勢會讓你欲罷不能,就像你領優惠券一樣。 ...
1. 先上封裝後的使用效果
[Permission(Key = "/User/AddUser")] [HttpPost] public Result AddUser([FromBody] SaUser user) { //Do sth. throw new NotImplementedException(); }
[Authorize] [HttpPost] public Result<UserInfoDto> GetUserInfo() { //Do sth. }
說明:要求登錄即可,不要求特定許可權的,可以使用【Authroize】 attribute 標記,
要求 特定許可權 如 "/User/AddUser" 的 ,使用 【Permission】特性標記,使用Key指定需要的許可權。 沒有登錄的返回401, 沒有許可權的返回403.
2. 實現。主要類及介面說明:
LoginUser : 登錄用戶,包含用戶基礎信息,許可權等。可以繼承此類封裝更多信息。
namespace WebUtils { public class LoginUser { public string EnterpriseId { get; set; } public string UserName { get; set;} public string Token { get; set; } public DateTime LoginTime { get; set;} /// <summary> /// 可用許可權 /// </summary> public HashSet<string> Permissions { get; set;} } }
ITokenHelper <TUser>: 管理用戶登錄後的token,並根據token 獲取登錄用戶信息。TUser 是LoginUser 的子類。
namespace WebUtils { public interface ITokenHelper<TUser> where TUser :LoginUser { public void AddToken(string token, TUser user); public void RemoveToken(string token); public TUser GetLoginUser (string token); } }
TokenHelper 是 ITokenHelper<LoginUser> 的預設實現,LoginUser 和Token 存記憶體中,進程重啟會丟失。實際應用可以封裝自己的實現,把信息持久化到資料庫或者Redis 中。
namespace WebUtils { public class TokenHelper : ITokenHelper<LoginUser> { private Dictionary<string, LoginUser> UserDic = new Dictionary<string, LoginUser>(); public void AddToken(string token, LoginUser au) { UserDic.Add(token, au); } public LoginUser GetLoginUser(string token) { if (UserDic.ContainsKey(token)) { return UserDic[token]; } return null; } public void RemoveToken(string token) { if (UserDic.ContainsKey(token)) { UserDic.Remove(token); } } } }View Code
PermissionAuthenticationHandler:檢查請求是否攜帶token,並檢查TokenHelper 中是否包含此token.
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using System; using System.Net; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; using WebUtils; namespace WebUtils { public class PermissionAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> { private ITokenHelper<LoginUser> _tokenHelper; public PermissionAuthenticationHandler(ITokenHelper<LoginUser> tokenHelper, IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { this._tokenHelper = tokenHelper; } public static string CustomerSchemeName = "Permission"; protected override Task<AuthenticateResult> HandleAuthenticateAsync() { AuthenticateResult result; Context.Request.Headers.TryGetValue("Authorization", out StringValues values); string token = values.ToString(); if (!string.IsNullOrWhiteSpace(token)) { var loginInfo = _tokenHelper.GetLoginUser(token); if (loginInfo == null) result = AuthenticateResult.Fail("未登陸"); else { var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, loginInfo.UserName), new Claim(ClaimHelper.EnterpriseId,loginInfo.EnterpriseId), new Claim(ClaimHelper.Token, loginInfo.Token) }, CustomerSchemeName); var principal = new ClaimsPrincipal(claimsIdentity); AuthenticationTicket ticket = new AuthenticationTicket(principal, Scheme.Name); result = AuthenticateResult.Success(ticket); } } else { result = AuthenticateResult.Fail("未登陸"); } return Task.FromResult(result); } } }View Code
PermissionAttribute: 繼承自 Attribute,IFilterFactory ,返回真正的IAuthorizationFilter實例。
DonotUsePermissionFilterAttribute 繼承自 Attribute, IAuthorizationFilter 檢查是否擁有指定的許可權。
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WebUtils { [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)] public class PermissionAttribute : Attribute,IFilterFactory { public string Key { get; set; } public bool IsReusable => false; public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { var instance= serviceProvider.GetService<DonotUsePermissionFilterAttribute>(); instance.Key = this.Key; return instance; } } /// <summary> /// 防止用戶直接調用,起名DonotUse, /// </summary> public class DonotUsePermissionFilterAttribute : Attribute, IAuthorizationFilter { private ITokenHelper<LoginUser> _tokenHelper; public DonotUsePermissionFilterAttribute(ITokenHelper<LoginUser> tokenHelper) { this._tokenHelper = tokenHelper; } public string Key { get; set; } public void OnAuthorization(AuthorizationFilterContext context) { var token = context.HttpContext.User?.GetValue(ClaimHelper.Token); if (token == null) { context.Result = new ObjectResult("用戶未登錄") { StatusCode = 401 }; return; } var user = _tokenHelper.GetLoginUser(token); if (user == null) { context.Result = new ObjectResult("用戶token 已失效") { StatusCode = 401 }; return; } if (!user.Permissions.Contains(Key)) { context.Result = new ObjectResult("鑒權失敗,請聯繫管理員授權!") { StatusCode = 403 }; return; } } } }View Code
PermissionMiddleWare 把相關實例和PermissionAuthenticationHandler添加到Service 中。
using Microsoft.Extensions.DependencyInjection; namespace WebUtils { public static class PermissionMiddleWare { /// <summary> /// 基於token和permission 的許可權認證中間件 /// </summary> /// <param name="services"></param> /// <param name="TokenHelperType"></param> /// <returns></returns> public static IServiceCollection AddPermission(this IServiceCollection services,Type TokenHelperType) { services.AddSingleton(typeof(ITokenHelper<LoginUser>), TokenHelperType); services.AddTransient(typeof(PermissionAttribute)); services.AddTransient(typeof(DonotUsePermissionFilterAttribute)); services.AddAuthentication(o => { o.DefaultAuthenticateScheme = PermissionAuthenticationHandler.CustomerSchemeName; o.DefaultChallengeScheme = PermissionAuthenticationHandler.CustomerSchemeName; o.AddScheme<PermissionAuthenticationHandler>(PermissionAuthenticationHandler.CustomerSchemeName, PermissionAuthenticationHandler.CustomerSchemeName); }); return services; } } }View Code
3. 在program.cs 中調用
在原來添加AddAuthorization 的地方換成下麵這句
builder.Services.AddPermission(typeof(TokenHelper));
別忘了後面use
app.UseAuthentication();
app.UseAuthorization();
簡單就是美。 如果我的文章幫到了你,請告訴我,讓我知道。