Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置

天翔者 2019-02-26 16:09:00 阅读数:198 评论数:0 点赞数:0 收藏数:0

介绍

系列目录:【NET CORE微服务一条龙应用】开始篇与目录

在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务中网关和子服务如何使用统一的权限认证

主要介绍内容为:

1、子服务如何实现和网关相同的鉴权方式

2、接口权限如何动态配置与修改

3、前后端分离模式下通用的后台管理系统(用户、权限、菜单、平台)

需提前了解知识点:

1、Jwt (JSON Web Token)

2、ClaimsPrincipal

3、Microsoft.AspNetCore.Authorization、AuthorizationPolicy、AuthorizationHandler

子服务和网关鉴权方式

首先我们需要了解一下Ocelot网关权限的使用方式,直接上代码

配置"AuthenticationOptions": {"AuthenticationProviderKey": "TestKey","AllowedScopes": ["admin","user"] }

认证

var result = awaitcontext.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);

从以前的代码我们可以看出认证授权的逻辑

1、HttpContext.AuthenticateAsync来进行认证验证,即验证Jwt token的有效可用性,其中AuthenticationProviderKey为身份验证提供程序标识,例如public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; services.AddAuthentication().AddJwtBearer(authenticationProviderKey, x => { }); }

2、当1验证通过后,我们可以通过context.HttpContext.User获取key为scope的Claim数组信息(所以token生成要带上此参数),然后与配置的AllowedScopes的数组进行交集验证,当交集大于0时即为有权限访问

所以子服务如果需要实现和网关相同的权限验证就需要实现以上的方式,用过net core默认的权限认证时会发现,权限的验证都需要体现设定好接口的可访问角色等参数,这不符合我们的需求所以我们需要实现一个自定义的权限认证AuthorizationHandler,直接上代码:

1 public class PermissionHandler : AuthorizationHandler 2 {3 ///

4 ///authentication scheme provider5 /// 6 readonlyIAuthenticationSchemeProvider schemes;7 /// 8 ///validate permission9 /// 10 readonlyIPermissionAuthoriser permissionAuthoriser;11 /// 12 ///ctor13 /// 14 /// 15 publicPermissionHandler(IAuthenticationSchemeProvider schemes, IPermissionAuthoriser permissionAuthoriser)16 {17 schemes =schemes;18 permissionAuthoriser =permissionAuthoriser;19 }20 /// 21 ///handle requirement22 /// 23 /// authorization handler context 24 /// jwt authorization requirement 25 /// 26 protected override asyncTask HandleRequirementAsync(AuthorizationHandlerContext context, JwtAuthorizationRequirement jwtAuthorizationRequirement)27 {28 //convert AuthorizationHandlerContext to HttpContext 29 var httpContext = context.Resource.GetType().GetProperty("HttpContext").GetValue(context.Resource) asHttpContext;30 31 var defaultAuthenticate = awaitschemes.GetDefaultAuthenticateSchemeAsync();32 if (defaultAuthenticate != null)33 {34 var result = awaithttpContext.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 }View Code

其中_permissionAuthoriser.Authorise为权限验证方法,继续往下看实现逻辑

1 public classScopesAuthoriser : IPermissionAuthoriser2 {3 private readonlyIPermissionRepository permissionRepository;4 private readonlyIClaimsParser claimsParser;5 private readonly string scope = "scope";6 private bool loaded = false;7 publicScopesAuthoriser(IPermissionRepository permissionRepository, IClaimsParser claimsParser)8 {9 permissionRepository =permissionRepository;10 claimsParser =claimsParser;11 }12 13 public boolAuthorise(HttpContext httpContext)14 {15 if (!loaded && permissionRepository.Permissions.Count == 0)16 permissionRepository.Get();17 loaded = true;18 19 var permission =permissionRepository.Permissions20 .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;权限仓储下面进行介绍

接口权限如何动态配置与修改

认证授权数据库设计,tbapiresources Api资源表、tbroles 角色表、tbroleapis 角色Api资源关系表、tbusers 用户表、tbuserroles 用户角色表

常规验证权限方式,是根据用户的id查询用户角色,然后验证角色是否拥有接口权限;而在网关中是反过来该接口有哪些角色可以访问;

所以我们需要初始化出应用接口对应所需角色,目前我们实现了mysql版本的权限仓储IPermissionRepository的数据查询,代码如下

 1 public classMySqlPermissionRepository : IPermissionRepository2 {3 private readonly stringdbConnectionString;4 private readonly stringprojectName;5 6 public MySqlPermissionRepository(string dbConnectionString, stringprojectName)7 {8 dbConnectionString =dbConnectionString;9 projectName =projectName;10 }11 public List Permissions { get; private set; } = new List();12 public asyncTask Get()13 {14 using (var dbContext = newMySqlConnection(dbConnectionString))15 {16 //平台下所有需要认证Scope的接口 17 var apiList = await dbContext.QueryAsync(@"SELECT api.Url,api.Method,roleapi.RoleId18 FROM tbapiresources AS api19 LEFT JOIN tbroleapis AS roleapi ON api.Id = roleapi.ApiId20 WHERE AllowScope = 2 AND ProjectName = @ProjectName", new { ProjectName =projectName });21 //所有角色 22 var roleList = await dbContext.QueryAsync(@"SELECT Id, Key from tbroles WHERE IsDel=0", new { ProjectName =projectName });23 if(apiList.Any())24 {25 var permission = new List();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(newPermission36 {37 Path =apiInfo.Url,38 Method =apiInfo.Method,39 Scope =scopes40 });41 });42 });43 if (permission.Count > 0)44 Permissions =permission;45 }46 }47 }48 }

这里只会实现一次查询,如果中间有接口权限进行了修改,那么如何进行更新呢,在上一篇配置中间使用中,我们介绍了如何使用组件的定时任务和组件的监听方式,所以我们只需做对应扩展即可,定时代码就不贴了,监听代码如下:

1 public classBucketAuthorizeListener : IBucketListener2 {3 private readonlyIPermissionRepository permissionRepository;4 5 publicBucketAuthorizeListener(IPermissionRepository permissionRepository)6 {7 permissionRepository =permissionRepository;8 }9 10 public string ListenerName => "Bucket.Authorize";11 12 public async Task ExecuteAsync(stringcommandText)13 {14 if (!string.IsNullOrWhiteSpace(commandText) && commandText ==NetworkCommandType.Reload.ToString())15 await_permissionRepository.Get();16 }17 }

下面贴一下Bucket.Authorize如何使用代码

1 public voidConfigureServices(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},View Code

前后端分离模式下通用的后台管理系统

在FamilyBucket-UI中我们可以对项目的接口权限认证方式、用户、用户角色、角色、角色权限、角色菜单等等进行配置,

同时FamilyBucket-UI还具有通用管理系统的基础模块,里面增加一个管理平台的概念,可以直接多个管理平台使用同一个用户体系

本章就不做介绍了,内容有点长,下次再做详细介绍,在多平台同时管理时项目还需要进行一些升级,截图如下

本章涉及源码均可在github中进行查看

https://github.com/q315523275/FamilyBucket

https://github.com/q315523275/FamilyBucket-UI 

版权声明
本文为[天翔者]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/tianxiangzhe/p/10419334.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领