asp.net core 一种基于token 和 Permission 的权限管理Filter 过滤器
asp.net core webapi 下面,想做一个过滤权限的Filter,配合token,对api做一个较为细粒度的权限控制,
该filter (PermissionFilter) 的作用是用户LoginUser.Permissions 列表中有 Key指定的权限才可以访问,没有则返回403 错误码。
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); } } } }
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); } } }
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; } } } }
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; } } }
3. 在program.cs 中调用
在原来添加AddAuthorization 的地方换成下面这句
builder.Services.AddPermission(typeof(TokenHelper));
别忘了后面use
app.UseAuthentication();
app.UseAuthorization();