系列目錄:【NET CORE微服務一條龍應用】開始篇與目錄html
在微服務的應用中,統一的認證受權是必不可少的組件,本文將介紹微服務中網關和子服務如何使用統一的權限認證mysql
主要介紹內容爲:git
一、子服務如何實現和網關相同的鑑權方式github
二、接口權限如何動態配置與修改sql
三、先後端分離模式下通用的後臺管理系統(用戶、權限、菜單、平臺)數據庫
需提早了解知識點:後端
一、Jwt (JSON Web Token)api
二、ClaimsPrincipal數組
三、Microsoft.AspNetCore.Authorization、AuthorizationPolicy、AuthorizationHandlerapp
首先咱們須要瞭解一下Ocelot網關權限的使用方式,直接上代碼
配置
"AuthenticationOptions": { "AuthenticationProviderKey": "TestKey", "AllowedScopes": ["admin","user"] }
認證
var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); context.HttpContext.User = result.Principal; if (context.HttpContext.User.Identity.IsAuthenticated) {await _next.Invoke(context); }
權限驗證
var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes);
從之前的代碼咱們能夠看出認證受權的邏輯
一、HttpContext.AuthenticateAsync來進行認證驗證,即驗證Jwt token的有效可用性,其中AuthenticationProviderKey爲身份驗證提供程序標識,例如
public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; services.AddAuthentication().AddJwtBearer(authenticationProviderKey, x => { }); }
二、當1驗證經過後,咱們能夠經過context.HttpContext.User獲取key爲scope的Claim數組信息(因此token生成要帶上此參數),而後與配置的AllowedScopes的數組進行交集驗證,當交集大於0時即爲有權限訪問
因此子服務若是須要實現和網關相同的權限驗證就須要實現以上的方式,用過net core默認的權限認證時會發現,權限的驗證都須要體現設定好接口的可訪問角色等參數,這不符合咱們的需求因此咱們須要實現一個自定義的權限認證AuthorizationHandler,直接上代碼:
1 public class PermissionHandler : AuthorizationHandler<JwtAuthorizationRequirement> 2 { 3 /// <summary> 4 /// authentication scheme provider 5 /// </summary> 6 readonly IAuthenticationSchemeProvider _schemes; 7 /// <summary> 8 /// validate permission 9 /// </summary> 10 readonly IPermissionAuthoriser _permissionAuthoriser; 11 /// <summary> 12 /// ctor 13 /// </summary> 14 /// <param name="schemes"></param> 15 public PermissionHandler(IAuthenticationSchemeProvider schemes, IPermissionAuthoriser permissionAuthoriser) 16 { 17 _schemes = schemes; 18 _permissionAuthoriser = permissionAuthoriser; 19 } 20 /// <summary> 21 /// handle requirement 22 /// </summary> 23 /// <param name="context">authorization handler context</param> 24 /// <param name="jwtAuthorizationRequirement">jwt authorization requirement</param> 25 /// <returns></returns> 26 protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, JwtAuthorizationRequirement jwtAuthorizationRequirement) 27 { 28 //convert AuthorizationHandlerContext to HttpContext 29 var httpContext = context.Resource.GetType().GetProperty("HttpContext").GetValue(context.Resource) as HttpContext; 30 31 var defaultAuthenticate = await _schemes.GetDefaultAuthenticateSchemeAsync(); 32 if (defaultAuthenticate != null) 33 { 34 var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); 35 if (result?.Principal != null) 36 { 37 var invockResult = _permissionAuthoriser.Authorise(httpContext); 38 if (invockResult) 39 { 40 context.Succeed(jwtAuthorizationRequirement); 41 } 42 else 43 { 44 context.Fail(); 45 } 46 } 47 else 48 { 49 httpContext.Response.Headers.Add("error", "authenticate fail"); 50 context.Fail(); 51 } 52 } 53 else 54 { 55 httpContext.Response.Headers.Add("error", "can't find authenticate"); 56 context.Fail(); 57 } 58 } 59 }
其中_permissionAuthoriser.Authorise爲權限驗證方法,繼續往下看實現邏輯
1 public class ScopesAuthoriser : IPermissionAuthoriser 2 { 3 private readonly IPermissionRepository _permissionRepository; 4 private readonly IClaimsParser _claimsParser; 5 private readonly string _scope = "scope"; 6 private bool _loaded = false; 7 public ScopesAuthoriser(IPermissionRepository permissionRepository, IClaimsParser claimsParser) 8 { 9 _permissionRepository = permissionRepository; 10 _claimsParser = claimsParser; 11 } 12 13 public bool Authorise(HttpContext httpContext) 14 { 15 if (!_loaded && _permissionRepository.Permissions.Count == 0) 16 _permissionRepository.Get(); 17 _loaded = true; 18 19 var permission = _permissionRepository.Permissions 20 .FirstOrDefault(it => string.Equals(it.Path, httpContext.Request.Path, StringComparison.CurrentCultureIgnoreCase) && it.Method == httpContext.Request.Method); 21 22 if (permission == null) 23 return true; 24 25 var values = _claimsParser.GetValuesByClaimType(httpContext.User.Claims, _scope); 26 27 var matchesScopes = permission.Scope.Intersect(values).ToList(); 28 29 if (matchesScopes.Count == 0) 30 return false; 31 32 return true; 33 } 34 }
其中_permissionRepository.Permissions是應用的接口列表與接口對應的可訪問scope;權限倉儲下面進行介紹
認證受權數據庫設計,tb_api_resources Api資源表、tb_roles 角色表、tb_role_apis 角色Api資源關係表、tb_users 用戶表、tb_user_roles 用戶角色表
常規驗證權限方式,是根據用戶的id查詢用戶角色,而後驗證角色是否擁有接口權限;而在網關中是反過來該接口有哪些角色能夠訪問;
因此咱們須要初始化出應用接口對應所需角色,目前咱們實現了mysql版本的權限倉儲IPermissionRepository的數據查詢,代碼以下
1 public class MySqlPermissionRepository : IPermissionRepository 2 { 3 private readonly string _dbConnectionString; 4 private readonly string _projectName; 5 6 public MySqlPermissionRepository(string dbConnectionString, string projectName) 7 { 8 _dbConnectionString = dbConnectionString; 9 _projectName = projectName; 10 } 11 public List<Permission> Permissions { get; private set; } = new List<Permission>(); 12 public async Task Get() 13 { 14 using (var dbContext = new MySqlConnection(_dbConnectionString)) 15 { 16 // 平臺下全部須要認證Scope的接口 17 var apiList = await dbContext.QueryAsync<ApiInfo>(@"SELECT api.Url,api.Method,roleapi.RoleId 18 FROM tb_api_resources AS api 19 LEFT JOIN tb_role_apis AS roleapi ON api.Id = roleapi.ApiId 20 WHERE AllowScope = 2 AND ProjectName = @ProjectName", new { ProjectName = _projectName }); 21 // 全部角色 22 var roleList = await dbContext.QueryAsync<RoleInfo>(@"SELECT Id, `Key` from tb_roles WHERE IsDel=0", new { ProjectName = _projectName }); 23 if (apiList.Any()) 24 { 25 var permission = new List<Permission>(); 26 var apiUrlList = apiList.GroupBy(it => it.Url).Select(it => it.FirstOrDefault()).ToList(); 27 apiUrlList.ForEach(api => 28 { 29 var apiMethodList = apiList.Where(it => it.Url == api.Url).GroupBy(it => it.Method).Select(it => it.FirstOrDefault()).ToList(); 30 apiMethodList.ForEach(method => 31 { 32 var apiInfo = apiList.Where(it => it.Url == api.Url && it.Method == method.Method).FirstOrDefault(); 33 var roleids = apiList.Where(it => it.Url == api.Url && it.Method == method.Method).Select(it => it.RoleId).ToArray(); 34 var scopes = roleList.Where(it => roleids.Contains(it.Id)).Select(it => it.Key).ToList(); 35 permission.Add(new Permission 36 { 37 Path = apiInfo.Url, 38 Method = apiInfo.Method, 39 Scope = scopes 40 }); 41 }); 42 }); 43 if (permission.Count > 0) 44 Permissions = permission; 45 } 46 } 47 } 48 }
這裏只會實現一次查詢,若是中間有接口權限進行了修改,那麼如何進行更新呢,在上一篇配置中間使用中,咱們介紹瞭如何使用組件的定時任務和組件的監聽方式,因此咱們只需作對應擴展便可,定時代碼就不貼了,監聽代碼以下:
1 public class BucketAuthorizeListener : IBucketListener 2 { 3 private readonly IPermissionRepository _permissionRepository; 4 5 public BucketAuthorizeListener(IPermissionRepository permissionRepository) 6 { 7 _permissionRepository = permissionRepository; 8 } 9 10 public string ListenerName => "Bucket.Authorize"; 11 12 public async Task ExecuteAsync(string commandText) 13 { 14 if (!string.IsNullOrWhiteSpace(commandText) && commandText == NetworkCommandType.Reload.ToString()) 15 await _permissionRepository.Get(); 16 } 17 }
下面貼一下Bucket.Authorize如何使用代碼
1 public void ConfigureServices(IServiceCollection services) 2 { 3 // 添加受權 4 services.AddApiJwtAuthorize(Configuration); 5 // 添加受權認證 6 services.AddApiJwtAuthorize(Configuration).UseAuthoriser(services, builder => { builder.UseMySqlAuthorize(); }); 7 }
而後在須要認證受權的action或者controller加上[Authorize("permission")]屬性,appsetting配置以下,也可移至配置中心
"JwtAuthorize": { "ProjectName": "", "Secret": "", "Issuer": "", "Audience": "", "PolicyName": "", "DefaultScheme": "", "IsHttps": false, "RequireExpirationTime": true, "MySqlConnectionString": "", "RefreshInteval": 300 },
在FamilyBucket-UI中咱們能夠對項目的接口權限認證方式、用戶、用戶角色、角色、角色權限、角色菜單等等進行配置,
同時FamilyBucket-UI還具備通用管理系統的基礎模塊,裏面增長一個管理平臺的概念,能夠直接多個管理平臺使用同一個用戶體系
本章就不作介紹了,內容有點長,下次再作詳細介紹,在多平臺同時管理時項目還須要進行一些升級,截圖以下
本章涉及源碼都可在github中進行查看