一、前言

前几篇文章分享了IdentityServer4密码模式的基本授权及自定义授权等方式,最近由于改造一个网关服务,用到了IdentityServer4的授权,改造过程中发现比较适合基于Role角色的授权,通过不同的角色来限制用户访问不同的Api资源,这里我就来分享IdentityServer4基于角色的授权详解。

IdentityServer4 历史文章目录

没有看过之前的几篇文章,我建议先回过头看看上面那几篇文章再来看本篇文章,不过对于大牛来说就可以跳过了。。。。

二、模拟场景

还是按照我的文章风格套路,实战之前先来模拟下应用场景,无场景的实战都是耍流氓,模拟场景更能让大家投入,同时也是自我学习、思考、总结的结晶之处!!!

对于角色授权大家也不陌生,大家比较熟悉的应该是RBAC的设计,这里就不阐述RBAC,有兴趣的可以百度。我们这里简单模拟下角色场景

假如有这么一个数据网关服务服务(下面我统称为数据网关),客户端有三种账号角色(普通用户、管理员用户、超级管理员用户),数据网关针对这三种角色用户分配不同的数据访问权限,场景图如下:

那么这种场景我们会怎么去设计呢?这个场景还算比较简单,角色比较单一,比较固定,对于这种场景很多人可能会考虑到通过Filter过滤器等方式来实现,这当然可以。不过正对这种场景IdentityServer4中本身就支持角色授权,下面我来给大家分享IdentityServer4的角色授权.

三、角色授权实战

授权流程

撸代码之前我们先整理下IdentityServer4的 角色授权流程图,我简单概括画了下,流程图如下:

场景图概括如下:

  • 客户端分为三种核心角色(普通用户、管理员用户、超级管理-老板)用户,三种用户访问同一个数据网关(API资源)
  • 数据网关(API资源)对这三种用户角色做了访问限制。

角色授权流程解释如下:

  • 第一步: 不同的用户携带用户密码等信息访问授权中心(ids4)尝试授权
  • 第二步: 授权中心对用户授权通过返回access_token给用户同时声明用户的RoleClaim中。。
  • 第三步: 客户端携带拿到的access_token尝试请求数据网关(API资源)。
  • 第四步:数据网关收到客户端的第一次请求会到授权中心请求获得验证公钥。
  • 第五步:授权中心返回验证公钥数据网关并且缓存起来,后面不再到授权中心再次获得验证公钥(只会请求一次,除非重启服务)。
  • 第六步:数据网关(ids4)通过验证网关验证access_token是否验证通过,并且验证请求的客户端用户声明的Role是否和请求的API资源约定的的角色一致。如果一致则通过第步返回给用户端,否则直接拒绝请求.

撸代码

代码继续上面几篇文章的例子的续集,你懂的,就不从零开始撸代码啦(强烈建议没看过上面几篇的先看下上面的目录中的几篇,要不然会一头雾水,大佬跳过)

要使IdentityServer4实现的授权中心支持角色验证的支持,我们需要在定义的API资源中添加角色的引入,代码如下:

上几篇文章的授权中心(Jlion.NetCore.Identity.Service)的

代码如下:

 /// <summary>
/// 资源
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName),
};
}

加入角色的支持代码改造如下:

 /// <summary>
/// 资源
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource(
OAuthConfig.UserApi.ApiName,
OAuthConfig.UserApi.ApiName,
new List<string>(){JwtClaimTypes.Role }
),
};
}

API资源中添加了角色验证的支持后,需要在用户登录授权成功后声明Claim用户的Role信息,代码如下:

改造前代码:

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
var userName = context.UserName;
var password = context.Password; //验证用户,这么可以到数据库里面验证用户名和密码是否正确
var claimList = await ValidateUserAsync(userName, password); // 验证账号
context.Result = new GrantValidationResult
(
subject: userName,
authenticationMethod: "custom",
claims: claimList.ToArray()
);
}
catch (Exception ex)
{
//验证异常结果
context.Result = new GrantValidationResult()
{
IsError = true,
Error = ex.Message
};
}
} #region Private Method
/// <summary>
/// 验证用户
/// </summary>
/// <param name="loginName"></param>
/// <param name="password"></param>
/// <returns></returns>
private async Task<List<Claim>> ValidateUserAsync(string loginName, string password)
{
//TODO 这里可以通过用户名和密码到数据库中去验证是否存在,
// 以及角色相关信息,我这里还是使用内存中已经存在的用户和密码
var user = OAuthMemoryData.GetTestUsers(); if (user == null)
throw new Exception("登录失败,用户名和密码不正确"); return new List<Claim>()
{ new Claim(ClaimTypes.Name, $"{loginName}"),
new Claim(EnumUserClaim.DisplayName.ToString(),"测试用户"),
new Claim(EnumUserClaim.UserId.ToString(),"10001"),
new Claim(EnumUserClaim.MerchantId.ToString(),"000100001"),
};
}
#endregion
}

为了保留之前文章的源代码,好让之前的文章源代码可追溯,我这里不在源代码上改造升级,我直接新增一个用户密码验证器类,

命名为RoleTestResourceOwnerPasswordValidator,代码改造如下:

 /// <summary>
/// 角色授权用户名密码验证器demo
/// </summary>
public class RoleTestResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
var userName = context.UserName;
var password = context.Password; //验证用户,这么可以到数据库里面验证用户名和密码是否正确
var claimList = await ValidateUserByRoleAsync(userName, password); // 验证账号
context.Result = new GrantValidationResult
(
subject: userName,
authenticationMethod: "custom",
claims: claimList.ToArray()
);
}
catch (Exception ex)
{
//验证异常结果
context.Result = new GrantValidationResult()
{
IsError = true,
Error = ex.Message
};
}
} #region Private Method /// <summary>
/// 验证用户(角色Demo 专用方法)
/// 这里和之前区分,主要是为了保留和博客同步源代码
/// </summary>
/// <param name="loginName"></param>
/// <param name="password"></param>
/// <returns></returns>
private async Task<List<Claim>> ValidateUserByRoleAsync(string loginName, string password)
{
//TODO 这里可以通过用户名和密码到数据库中去验证是否存在,
// 以及角色相关信息,我这里还是使用内存中已经存在的用户和密码
var user = OAuthMemoryData.GetUserByUserName(loginName); if (user == null)
throw new Exception("登录失败,用户名和密码不正确"); //下面的Claim 声明我为了演示,硬编码了,
//实际生产环境需要通过读取数据库的信息并且来声明 return new List<Claim>()
{ new Claim(ClaimTypes.Name, $"{user.UserName}"),
new Claim(EnumUserClaim.DisplayName.ToString(),user.DisplayName),
new Claim(EnumUserClaim.UserId.ToString(),user.UserId.ToString()),
new Claim(EnumUserClaim.MerchantId.ToString(),user.MerchantId.ToString()),
new Claim(JwtClaimTypes.Role.ToString(),user.Role.ToString())
};
}
#endregion
}

为了方便演示,我直接把Role定义成了一个公共枚举EnumUserRole,代码如下:

/// <summary>
/// 角色枚举
/// </summary>
public enum EnumUserRole
{
Normal,
Manage,
SupperManage
}

GetUserByUserName中硬编码创建了三个角色的用户,代码如下:

 /// <summary>
/// 为了演示,硬编码了,
/// 这个方法可以通过DDD设计到底层数据库去查询数据库
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public static UserModel GetUserByUserName(string userName)
{
var normalUser = new UserModel()
{
DisplayName = "张三",
MerchantId = 10001,
Password = "123456",
Role = Enums.EnumUserRole.Normal,
SubjectId = "1",
UserId = 20001,
UserName = "testNormal"
};
var manageUser = new UserModel()
{
DisplayName = "李四",
MerchantId = 10001,
Password = "123456",
Role = Enums.EnumUserRole.Manage,
SubjectId = "1",
UserId = 20001,
UserName = "testManage"
};
var supperManageUser = new UserModel()
{
DisplayName = "dotNET博士",
MerchantId = 10001,
Password = "123456",
Role = Enums.EnumUserRole.SupperManage,
SubjectId = "1",
UserId = 20001,
UserName = "testSupperManage"
};
var list = new List<UserModel>() {
normalUser,
manageUser,
supperManageUser
};
return list?.Where(item => item.UserName.Equals(userName))?.FirstOrDefault();
}

好了,现在用户授权通过后声明的Role也已经完成了,我上面使用的是JwtClaimTypes 默认支持的Role,你也可以不使用JwtClaimTypes类,可以自定义类来实现。

最后为了让新关注我的博客用户没看过之前几篇文章的用户不至于一头雾水,我把注册ids中间件代码还是贴出来,

注册新的用户名密码验证器到DI中 代码如下:

 public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(); #region 数据库存储方式
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
//.AddInMemoryClients(OAuthMemoryData.GetClients())
.AddClientStore<ClientStore>()
//.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddResourceOwnerValidator<RoleTestResourceOwnerPasswordValidator>()
.AddExtensionGrantValidator<WeiXinOpenGrantValidator>()
.AddProfileService<UserProfileService>();//添加微信端自定义方式的验证 #endregion
} public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//使用IdentityServer4 的中间件
app.UseIdentityServer(); app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

授权中心的角色支持代码撸完了,我们来改造上几篇文章中说到的用户网关服务,这里我就叫数据网关

项目:Jlion.NetCore.Identity.UserApiService

上一篇关于Asp.Net Core 中IdentityServer4 实战之 Claim详解

文章中在数据网关服务中新增了UserController控制器,并添加了一个访问用户基本的Claim信息接口,之前的代码如下:

[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{ private readonly ILogger<UserController> _logger; public UserController(ILogger<UserController> logger)
{
_logger = logger;
} [Authorize]
[HttpGet]
public async Task<object> Get()
{
var userId = User.UserId();
return new
{
name = User.Name(),
userId = userId,
displayName = User.DisplayName(),
merchantId = User.MerchantId(),
};
}
}

上面的代码中Authorize没有指定Role,那相当于所有的用户都可以访问这个接口,接下来,我们在UserController中创建一个只能是超级管理员角色才能访问的接口,代码如下

 [Authorize(Roles =nameof(EnumUserRole.SupperManage))]
[HttpGet("{id}")]
public async Task<object> Get(int id)
{
var userId = User.UserId();
return new
{
name = User.Name(),
userId = userId,
displayName = User.DisplayName(),
merchantId = User.MerchantId(),
roleName=User.Role()//获得当前登录用户的角色
};
}

到这里数据网关代码也已经改造完了,我们接下来就是运行结果看看是否正确。

运行

我们分别通过命令行运行我们的授权网关服务和数据网关服务,分别如下图:

授权网关还是指定5000 端口,如下图:



数据网关跟之前几篇文章一样指定 5001 端口,如下图:

现在授权网关数据网关都已经完美运行起来了,接下来我们通过postman模拟请求。

先来通过普通用户(testNormal)请求授权中心获得access_token,如下图:



请求验证通过,

再来通过获取到的access_token 获取普通接口:



也完美获取到数据

再来访问下标注了supperManage超级管理员的角色接口,如下图:



结果跟预想的一样,返回了403访问被拒绝,其他账号运行也是一样,我这里就不一一去运行访问测试了,有兴趣的同学可以到github 上拉起我的源代码进行运行测试,

到这里基于ids4角色授权基础应用也完成了。

结束语:上面分享学习了IdentityServer4 进行角色授权的实战例子,但是从上面的例子中有一个不好的弊端,就是每个api访问都需要硬编码进行指定Role 这在生产环境中很不现实和灵活,Role角色这个东西都是通过后台自管理,进行灵活配置角色和资源的,那IdentityServer4 有没有什么好的方式实现呢?留给大家思考,思考就有学习的目标,也是思维的进步。

博客系列源代码地址:https://github.com/a312586670/NetCoreDemo

感谢语:三月份即将过去,三月份同时也是美好的开始,我的博客从三月份开始整理分享,传承着以一起学习,共同进步为目标,自我自律,开始分享相关技术。文章持续性同步至我的微信公众号【dotNET博士】,这个月来初见成效,一个月内已经荣获500+以上的粉丝,也感谢大家一直以来对我的关注,你的关注让我更有动力分享更好的原创技术文章。还没有关注微信公众号的,搜索"dotNET博士"关注,或者微信扫下面的二维码进行关注,同时大家也可以积极的分享或点个右下角的推荐,让更多人的关注到我的文章。

Asp.Net Core 中IdentityServer4 实战之角色授权详解的更多相关文章

  1. Asp.Net Core 中IdentityServer4 实战之 Claim详解

    一.前言 由于疫情原因,让我开始了以博客的方式来学习和分享技术(持续分享的过程也是自己学习成长的过程),同时也让更多的初学者学习到相关知识,如果我的文章中有分析不到位的地方,还请大家多多指教:以后我会 ...

  2. Asp.Net Core 中IdentityServer4 授权中心之应用实战

    一.前言 查阅了大多数相关资料,查阅到的IdentityServer4 的相关文章大多是比较简单并且多是翻译官网的文档编写的,我这里在 Asp.Net Core 中IdentityServer4 的应 ...

  3. Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式

    一.前言 上一篇我分享了一篇关于 Asp.Net Core 中IdentityServer4 授权中心之应用实战 的文章,其中有不少博友给我提了问题,其中有一个博友问我的一个场景,我给他解答的还不够完 ...

  4. Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用

    一.前言 上面分享了IdentityServer4 两篇系列文章,核心主题主要是密码授权模式及自定义授权模式,但是仅仅是分享了这两种模式的使用,这篇文章进一步来分享IdentityServer4的授权 ...

  5. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

  6. IdentityServer4实战 - JWT Token Issuer 详解

    原文:IdentityServer4实战 - JWT Token Issuer 详解 一.前言 本文为系列补坑之作,拖了许久决定先把坑填完. 下文演示所用代码采用的 IdentityServer4 版 ...

  7. ASP.NET Core WebApi基于JWT实现接口授权验证

    一.ASP.Net Core WebApi JWT课程前言 我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再 ...

  8. 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发

    <ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...

  9. ASP.NET Core分布式项目实战

    ASP.NET Core开发者成长路线图 asp.net core 官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/ ...

随机推荐

  1. Presto单机/集群模式安装笔记

    Presto单机/集群模式安装笔记 一.安装环境 二.安装步骤 三.集群模式安装: 3.1 集群模式修改配置部分 3.1.1 coordinator 节点配置. Node172配置 3.1.2 nod ...

  2. ypoj 2286 佳佳买菜

    题目名称:佳佳买菜 描述 佳佳是我们的ACM社团的副社长,她感觉得自己没存在感,so-由于实验室要聚餐了,佳佳决定买点菜,来做菜给大家吃.佳佳喜欢吃娃娃菜,于是她来到买菜的地方.佳佳:我要10斤娃娃菜 ...

  3. React Native Debug原理浅析

    第一次在segmentfault写博客,很紧张~~~公司项目上ReactNative,之前也是没有接触过,所以也是一边学习一边做项目了,最近腾出手来更新总结了一下RN的Debug的一个小知识点,不是说 ...

  4. [CSS]CSS Position 详解

    一. CSS position 属性介绍 CSS中position属性指定一个元素(静态的,相对的,绝对或固定)的定位方法的类型.有static,relative,absolute和fixed四种取值 ...

  5. idea使用Tomcat部署war 和 war exploded的区别

    war模式:将WEB工程一包的形式上传到服务器中.war exploded模式:将WEB工程以当前文件夹的位置关系上传到服务器.解析:war 模式这种可以称为是发布模式(完整的项目),将项目打成war ...

  6. 【学习笔记】Golang学习方向整理

    前言 作为一个Java开发,给大家说Golang方向,好吓人...溜了溜了... 哦对了,如有不对的地方,还请指出.感谢! 某面试平台golang技能要求简要摘录 掌握 GO 语言,熟悉常用 pack ...

  7. idea 2018.1激活方法

    之前用的idea都是2017版本的,现在已经四月份了,对于2018年1月份的版本应该可以放心的用了. 在这里,仅提供2018版本的激活码. 至于安装步骤,这里省略一千个字...... 下面是具体的激活 ...

  8. java线程并发工具类

    本次内容主要讲Fork-Join.CountDownLatch.CyclicBarrier以及Callable.Future和FutureTask,最后再手写一个自己的FutureTask,绝对干货满 ...

  9. 必备技能echarts

    echart 设置图例图标形状 https://blog.csdn.net/qq_15390381/article/details/81736796legend: { data: ["总数& ...

  10. 【06】openlayers 切片图层

    创建地图: //OSM图层 let source = new ol.source.OSM() //切片图层tilelayer let layers = new ol.layer.Tile({ sour ...