首页 > 编程语言 >Asp.Net Core 中IdentityServer4 实战之角色授权详解

Asp.Net Core 中IdentityServer4 实战之角色授权详解

时间:2023-01-06 16:44:29浏览次数:58  
标签:Core 网关 Asp 角色 用户 new 授权 Net IdentityServer4

一、前言

前几篇文章分享了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 有没有什么好的方式实现呢?留给大家思考,思考就有学习的目标,也是思维的进步。

标签:Core,网关,Asp,角色,用户,new,授权,Net,IdentityServer4
From: https://www.cnblogs.com/stry/p/17028074.html

相关文章

  • 【.net core】电商平台升级之微服务架构应用实战(core-grpc)
    一、前言这篇文章本来是继续分享IdentityServer4的相关文章,由于之前有博友问我关于微服务相关的问题,我就先跳过IdentityServer4的分享,进行微服务相关的技术学习和分享。......
  • Asp.Net Core EndPoint 终结点路由工作原理解读
    一、背景在本打算写一篇关于Identityserver4的文章时候,却发现自己对EndPoint-终结点路由还不是很了解,故暂时先放弃了IdentityServer4的研究和编写;所以才产生了今天这篇......
  • Asp.Net Core 中IdentityServer4 授权中心之应用实战
    一、前言查阅了大多数相关资料,查阅到的IdentityServer4的相关文章大多是比较简单并且多是翻译官网的文档编写的,我这里在Asp.NetCore中IdentityServer4的应用分析中会......
  • kubernetes集群升级
    1.集群升级版本和方案说明#集群版本升级说明小版本升级:1.21升级到1.21.5,小版本的升级是稳定的升级,是属于稳定更新,一般是修复此版本的某些bug大版本升级:1.21升级到1.26(......
  • [转]设置.NET Core支持下载wwwroot下的apk文件
    .netcore项目发布Linux服务器后,有个apk放在wwwroot文件夹下,想请求地址的时候下载apk文件,但是一直提示找不到该文件其他png,txt,html等格式都能访问排查了jexus原因后......
  • netcore 容器内部监听设置localhost 外部无法访问
    情况1  由于localhost只能够在容器内部访问,所以在设置的时候改为*号,而不是固定ip。情况2  也可能由于只配置了容器和宿主机的映射,容器内部未监听端口号导致情况3......
  • .Net Core 用自动生成Dockerfile的坑
    简介  之前采用shell脚本+dockerfile的方式构建项目,后来发现Docker在17.05版本之后有多阶段构建方式,该文主要记录了netcore采用dockerfile构建遇到的坑。原先的方式......
  • netcore添加skywalking链式追踪
    简介  在分布式系统当中,想要监控服务与服务之间调用耗时,或者是查问题的时候,不能像向单机那种形式去查询.查找了一段时间发现目前市场上用的是skywalking,由华为大佬......
  • kubernetes1.25版本新版本启动时报错
    前言:kubernetes的部署从1.24版本开始后,弃用docker-shim,也就是说部署1.24版本后的集群不能使用docker-ce了。比较清晰的解决方案有两个,一是使用containerd,这个是一个新的......
  • .NETCore Docker实现容器化与私有镜像仓库管理
    一、Docker介绍Docker是用Go语言编写基于Linux操作系统的一些特性开发的,其提供了操作系统级别的抽象,是一种容器管理技术,它隔离了应用程序对基础架构(操作系统等)的依赖。相......