zoukankan      html  css  js  c++  java
  • 基于中间件的授权管理示例

      基于角色访问控制(RBAC)的主要思路是以角色为“桥梁”连接用户与资源,而角色对资源的访问控制又是通过授权许可来完成。菜单是一种特殊的资源,不过为了从技术上和业务上方便操作,可以将其单独抽出来,做为一种独立的“资源”处理。开始之前需要理解授权与认证,此处没有对认证的处理,默认流程走到授权时都是认证通过的。

    1.新建/生成实体

      依次为菜单表(Menu)描述系统菜单项,以URI做为基本控制单元;授权许可表(Permission)描述系统可授予的许可,同样以URI做为基本控制单元;角色表(Role)描述系统可以授予用户的角色;权限类别表(PermissionCategory)描述权限的类别,主要作用是为权限归类,该类别可以根据实际业务自定义。剩下的实体表示数据之间的关联关系,如MenuInRole表示菜单与角色的对应关系,PermissionInRole表示许可与角色的对应关系,UserInRole表示用户与角色的对应关系。因之前已经创建了授权表结构,所以这里的实体是通过EFCore的逆向工程自动生成的。示例如下:

     1     /// <summary>
     2     /// Menu
     3     /// </summary>
     4     public class Menu
     5     {
     6         /// <summary>
     7         /// Id
     8         /// </summary>
     9         public string Id { get; set; }
    10         /// <summary>
    11         /// Name
    12         /// </summary>
    13         public string Name { get; set; }
    14         /// <summary>
    15         /// Label
    16         /// </summary>
    17         public string Label { get; set; }
    18         /// <summary>
    19         /// Icon
    20         /// </summary>
    21         public string Icon { get; set; }
    22         /// <summary>
    23         /// Uri
    24         /// </summary>
    25         public string Uri { get; set; }
    26         /// <summary>
    27         /// Parent Id
    28         /// </summary>
    29         public string ParentId { get; set; }
    30         /// <summary>
    31         /// Sort
    32         /// </summary>
    33         public int? Sort { get; set; }
    34         /// <summary>
    35         /// Is Deleted
    36         /// </summary>
    37         public bool IsDeleted { get; set; }
    38         /// <summary>
    39         /// Created By
    40         /// </summary>
    41         public string CreatedBy { get; set; }
    42         /// <summary>
    43         /// Created Time
    44         /// </summary>
    45         [Column(TypeName = "timestamp")]
    46         public DateTime CreatedTime { get; set; }
    47         /// <summary>
    48         /// Last Updated By
    49         /// </summary>
    50         public string LastUpdatedBy { get; set; }
    51         /// <summary>
    52         /// Last Updated Time
    53         /// </summary>
    54         [Column(TypeName = "timestamp")]
    55         public DateTime LastUpdatedTime { get; set; }
    56 
    57         /// <summary>
    58         /// Menu In Roles
    59         /// </summary>
    60         public virtual ICollection<MenuInRole> MenuInRoles { get; set; }
    61     }
    Menu
     1     /// <summary>
     2     /// Permission
     3     /// </summary>
     4     public class Permission
     5     {
     6         /// <summary>
     7         /// Id
     8         /// </summary>
     9         public string Id { get; set; }
    10         /// <summary>
    11         /// Name
    12         /// </summary>
    13         public string Name { get; set; }
    14         /// <summary>
    15         /// Label
    16         /// </summary>
    17         public string Label { get; set; }
    18         /// <summary>
    19         /// Uri
    20         /// </summary>
    21         public string Uri { get; set; }
    22         /// <summary>
    23         /// CategoryId
    24         /// </summary>
    25         public string PermissionCategoryId { get; set; }
    26         /// <summary>
    27         /// Description
    28         /// </summary>
    29         public string Description { get; set; }
    30         /// <summary>
    31         /// Is Deleted
    32         /// </summary>
    33         public bool IsDeleted { get; set; }
    34         /// <summary>
    35         /// Created By
    36         /// </summary>
    37         public string CreatedBy { get; set; }
    38         /// <summary>
    39         /// Created Time
    40         /// </summary>
    41         [Column(TypeName = "timestamp")]
    42         public DateTime CreatedTime { get; set; }
    43         /// <summary>
    44         /// Last Updated By
    45         /// </summary>
    46         public string LastUpdatedBy { get; set; }
    47         /// <summary>
    48         /// Last Updated Time
    49         /// </summary>
    50         [Column(TypeName = "timestamp")]
    51         public DateTime LastUpdatedTime { get; set; }
    52 
    53         /// <summary>
    54         /// CategoryId Navigation
    55         /// </summary>
    56         public virtual PermissionCategory PermissionCategory { get; set; }
    57         /// <summary>
    58         /// Permission In Roles
    59         /// </summary>
    60         public virtual ICollection<PermissionInRole> PermissionInRoles { get; set; }
    61     }
    Permission
     1     /// <summary>
     2     /// Role
     3     /// </summary>
     4     public class Role
     5     {
     6         /// <summary>
     7         /// Id
     8         /// </summary>
     9         public string Id { get; set; }
    10         /// <summary>
    11         /// Tenant Id
    12         /// </summary>
    13         public string TenantId { get; set; }
    14         /// <summary>
    15         /// Name
    16         /// </summary>
    17         public string Name { get; set; }
    18         /// <summary>
    19         /// Label
    20         /// </summary>
    21         public string Label { get; set; }
    22         /// <summary>
    23         /// Description
    24         /// </summary>
    25         public string Description { get; set; }
    26         /// <summary>
    27         /// Is Deleted
    28         /// </summary>
    29         public bool IsDeleted { get; set; }
    30         /// <summary>
    31         /// Created By
    32         /// </summary>
    33         public string CreatedBy { get; set; }
    34         /// <summary>
    35         /// Created Time
    36         /// </summary>
    37         [Column(TypeName = "timestamp")]
    38         public DateTime CreatedTime { get; set; }
    39         /// <summary>
    40         /// Last Updated By
    41         /// </summary>
    42         public string LastUpdatedBy { get; set; }
    43         /// <summary>
    44         /// Last Updated Time
    45         /// </summary>
    46         [Column(TypeName = "timestamp")]
    47         public DateTime LastUpdatedTime { get; set; }
    48 
    49         /// <summary>
    50         /// Menu In Roles
    51         /// </summary>
    52         public virtual ICollection<MenuInRole> MenuInRoles { get; set; }
    53         /// <summary>
    54         /// Permission In Roles
    55         /// </summary>
    56         public virtual ICollection<PermissionInRole> PermissionInRoles { get; set; }
    57         /// <summary>
    58         /// User In Roles
    59         /// </summary>
    60         public virtual ICollection<UserInRole> UserInRoles { get; set; }
    61     }
    Role
     1     /// <summary>
     2     /// Permission CategoryId
     3     /// </summary>
     4     public class PermissionCategory
     5     {
     6         /// <summary>
     7         /// Id
     8         /// </summary>
     9         public string Id { get; set; }
    10         /// <summary>
    11         /// Name
    12         /// </summary>
    13         public string Name { get; set; }
    14         /// <summary>
    15         /// Label
    16         /// </summary>
    17         public string Label { get; set; }
    18         /// <summary>
    19         /// Is Deleted
    20         /// </summary>
    21         public bool IsDeleted { get; set; }
    22         /// <summary>
    23         /// Created By
    24         /// </summary>
    25         public string CreatedBy { get; set; }
    26         /// <summary>
    27         /// Created Time
    28         /// </summary>
    29         [Column(TypeName = "timestamp")]
    30         public DateTime CreatedTime { get; set; }
    31         /// <summary>
    32         /// Last Updated By
    33         /// </summary>
    34         public string LastUpdatedBy { get; set; }
    35         /// <summary>
    36         /// Last Updated Time
    37         /// </summary>
    38         [Column(TypeName = "timestamp")]
    39         public DateTime LastUpdatedTime { get; set; }
    40 
    41         /// <summary>
    42         /// Permissions
    43         /// </summary>
    44         public virtual ICollection<Permission> Permissions { get; set; }
    45     }
    PermissionCategory
     1     /// <summary>
     2     /// Menu In Role
     3     /// </summary>
     4     public class MenuInRole
     5     {
     6         /// <summary>
     7         /// Id
     8         /// </summary>
     9         public string Id { get; set; }
    10         /// <summary>
    11         /// Role Id
    12         /// </summary>
    13         public string RoleId { get; set; }
    14         /// <summary>
    15         /// Menu Id
    16         /// </summary>
    17         public string MenuId { get; set; }
    18         /// <summary>
    19         /// Label
    20         /// </summary>
    21         public string Label { get; set; }
    22         /// <summary>
    23         /// Icon
    24         /// </summary>
    25         public string Icon { get; set; }
    26         /// <summary>
    27         /// Sort
    28         /// </summary>
    29         public int? Sort { get; set; }
    30         /// <summary>
    31         /// Is Deleted
    32         /// </summary>
    33         public bool IsDeleted { get; set; }
    34         /// <summary>
    35         /// Created By
    36         /// </summary>
    37         public string CreatedBy { get; set; }
    38         /// <summary>
    39         /// Created Time
    40         /// </summary>
    41         [Column(TypeName = "timestamp")]
    42         public DateTime CreatedTime { get; set; }
    43         /// <summary>
    44         /// Last Updated By
    45         /// </summary>
    46         public string LastUpdatedBy { get; set; }
    47         /// <summary>
    48         /// Last Updated Time
    49         /// </summary>
    50         [Column(TypeName = "timestamp")]
    51         public DateTime LastUpdatedTime { get; set; }
    52 
    53         /// <summary>
    54         /// Menu
    55         /// </summary>
    56         public virtual Menu Menu { get; set; }
    57         /// <summary>
    58         /// Role
    59         /// </summary>
    60         public virtual Role Role { get; set; }
    61     }
    MenuInRole
     1     /// <summary>
     2     /// Permission In Role
     3     /// </summary>
     4     public class PermissionInRole
     5     {
     6         /// <summary>
     7         /// Id
     8         /// </summary>
     9         public string Id { get; set; }
    10         /// <summary>
    11         /// Role Id
    12         /// </summary>
    13         public string RoleId { get; set; }
    14         /// <summary>
    15         /// Permission Id
    16         /// </summary>
    17         public string PermissionId { get; set; }
    18         /// <summary>
    19         /// Not Before (nbf)
    20         /// </summary>
    21         [Column(TypeName = "timestamp")]
    22         public DateTime NotBefore { get; set; }
    23         /// <summary>
    24         /// Expiration (exp)
    25         /// </summary>
    26         [Column(TypeName = "timestamp")]
    27         public DateTime Expiration { get; set; }
    28         /// <summary>
    29         /// Country Region
    30         /// </summary>
    31         public string CountryRegion { get; set; }
    32         /// <summary>
    33         /// Is Deleted
    34         /// </summary>
    35         public bool IsDeleted { get; set; }
    36         /// <summary>
    37         /// Created By
    38         /// </summary>
    39         public string CreatedBy { get; set; }
    40         /// <summary>
    41         /// Created Time
    42         /// </summary>
    43         [Column(TypeName = "timestamp")]
    44         public DateTime CreatedTime { get; set; }
    45         /// <summary>
    46         /// Last Updated By
    47         /// </summary>
    48         public string LastUpdatedBy { get; set; }
    49         /// <summary>
    50         /// Last Updated Time
    51         /// </summary>
    52         [Column(TypeName = "timestamp")]
    53         public DateTime LastUpdatedTime { get; set; }
    54 
    55         /// <summary>
    56         /// Permission
    57         /// </summary>
    58         public virtual Permission Permission { get; set; }
    59         /// <summary>
    60         /// Role
    61         /// </summary>
    62         public virtual Role Role { get; set; }
    63     }
    PermissionInRole
     1     /// <summary>
     2     /// User In Role
     3     /// </summary>
     4     public class UserInRole
     5     {
     6         /// <summary>
     7         /// Id
     8         /// </summary>
     9         public string Id { get; set; }
    10         /// <summary>
    11         /// User Id
    12         /// </summary>
    13         public string UserId { get; set; }
    14         /// <summary>
    15         /// Role Id
    16         /// </summary>
    17         public string RoleId { get; set; }
    18         /// <summary>
    19         /// Is Deleted
    20         /// </summary>
    21         public bool IsDeleted { get; set; }
    22         /// <summary>
    23         /// Created By
    24         /// </summary>
    25         public string CreatedBy { get; set; }
    26         /// <summary>
    27         /// Created Time
    28         /// </summary>
    29         [Column(TypeName = "timestamp")]
    30         public DateTime CreatedTime { get; set; }
    31         /// <summary>
    32         /// Last Updated By
    33         /// </summary>
    34         public string LastUpdatedBy { get; set; }
    35         /// <summary>
    36         /// Last Updated Time
    37         /// </summary>
    38         [Column(TypeName = "timestamp")]
    39         public DateTime LastUpdatedTime { get; set; }
    40 
    41         /// <summary>
    42         /// Role
    43         /// </summary>
    44         public virtual Role Role { get; set; }
    45     }
    UserInRole

    2.创建数据访问层

      使用EFCore的逆向工程生成实体和上下文,为每一个实体建立DAO接口和实现。为方便后续迁移和执行正向工程,可以为每一个实体做数据结构和关系映射。示例如下:(上下文类、一个接口示例类、一个实现示例类、一个映射类)

     1     public class PermissionContext : DbContext
     2     {
     3         private static readonly string _connectionString = AppSettings.Configuration.GetConnectionString("NpgConnectionString");
     4 
     5         public PermissionContext()
     6         {
     7             Database.EnsureCreated();
     8         }
     9 
    10         public PermissionContext(DbContextOptions<PermissionContext> options) : base(options)
    11         {
    12             Database.EnsureCreated();
    13         }
    14 
    15         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    16         {
    17             if (!optionsBuilder.IsConfigured)
    18             {
    19                 optionsBuilder.UseNpgsql(_connectionString);
    20             }
    21         }
    22 
    23         protected override void OnModelCreating(ModelBuilder modelBuilder)
    24         {
    25             modelBuilder.ApplyConfiguration(new MenuInRoleMapping());
    26             modelBuilder.ApplyConfiguration(new MenuMapping());
    27             modelBuilder.ApplyConfiguration(new PermissionCategoryMapping());
    28             modelBuilder.ApplyConfiguration(new PermissionInRoleMapping());
    29             modelBuilder.ApplyConfiguration(new PermissionMapping());
    30             modelBuilder.ApplyConfiguration(new RoleMapping());
    31             modelBuilder.ApplyConfiguration(new UserInRoleMapping());
    32         }
    33 
    34         public virtual DbSet<Menu> Menus { get; set; }
    35         public virtual DbSet<MenuInRole> MenuInRoles { get; set; }
    36         public virtual DbSet<Entities.Permission> Permissions { get; set; }
    37         public virtual DbSet<PermissionCategory> PermissionCategories { get; set; }
    38         public virtual DbSet<PermissionInRole> PermissionInRoles { get; set; }
    39         public virtual DbSet<Role> Roles { get; set; }
    40         public virtual DbSet<UserInRole> UserInRoles { get; set; }
    41     }
    PermissionContext
     1     public interface IUserInRoleDataAccess
     2     {
     3         int AddUserInRole(string tenantId, UserInRole userInRole);
     4 
     5         int EditUserInRole(string tenantId, UserInRole userInRole);
     6 
     7         int RemoveUserInRole(string tenantId, string id);
     8 
     9         int Remove(string tenantId, string id);
    10 
    11         UserInRole GetUserInRole(string tenantId, string id);
    12 
    13         List<UserInRole> GetUserInRolesByUserId(string tenantId, string userId);
    14 
    15         List<UserInRole> GetUserInRoles(string tenantId);
    16     }
    IUserInRoleDataAccess
     1     public class UserInRoleDataAccess : IUserInRoleDataAccess
     2     {
     3         public int AddUserInRole(string tenantId, UserInRole userInRole)
     4         {
     5             using PermissionContext context = new PermissionContext();
     6 
     7             context.UserInRoles.Add(userInRole);
     8             int result = context.SaveChanges();
     9 
    10             return result;
    11         }
    12 
    13         public int EditUserInRole(string tenantId, UserInRole userInRole)
    14         {
    15             using PermissionContext context = new PermissionContext();
    16 
    17             context.Attach(userInRole);
    18             context.Entry(userInRole).Property(p => p.LastUpdatedBy).IsModified = true;
    19             context.Entry(userInRole).Property(p => p.LastUpdatedTime).IsModified = true;
    20             context.Entry(userInRole).Property(p => p.RoleId).IsModified = true;
    21             context.Entry(userInRole).Property(p => p.UserId).IsModified = true;
    22             int result = context.SaveChanges();
    23 
    24             return result;
    25         }
    26 
    27         public int RemoveUserInRole(string tenantId, string id)
    28         {
    29             using PermissionContext context = new PermissionContext();
    30 
    31             int result = context.Database.ExecuteSqlInterpolated($"UPDATE "user_in_role" SET "is_deleted" = true WHERE "id"={id}");
    32 
    33             return result;
    34         }
    35 
    36         public int Remove(string tenantId, string id)
    37         {
    38             using PermissionContext context = new PermissionContext();
    39 
    40             int result = context.Database.ExecuteSqlInterpolated($"DELETE FROM "user_in_role" WHERE "id"={id}");
    41 
    42             return result;
    43         }
    44 
    45         public UserInRole GetUserInRole(string tenantId, string id)
    46         {
    47             using PermissionContext context = new PermissionContext();
    48 
    49             UserInRole userInRole = context.UserInRoles.Include(p => p.Role).Where(p => p.Id == id && !p.IsDeleted).AsNoTracking().FirstOrDefault();
    50 
    51             return userInRole;
    52         }
    53 
    54         public List<UserInRole> GetUserInRolesByUserId(string tenantId, string userId)
    55         {
    56             using PermissionContext context = new PermissionContext();
    57 
    58             List<UserInRole> userInRole = context.UserInRoles
    59                                             .Include(p => p.Role)
    60                                             .ThenInclude(p => p.PermissionInRoles)
    61                                             .ThenInclude(p => p.Permission)
    62                                             .Where(p => p.UserId == userId && !p.IsDeleted)
    63                                             .AsNoTracking()
    64                                             .ToList();
    65 
    66             return userInRole;
    67         }
    68 
    69         public List<UserInRole> GetUserInRoles(string tenantId)
    70         {
    71             using PermissionContext context = new PermissionContext();
    72 
    73             List<UserInRole> userInRoles = context.UserInRoles.Where(p => !p.IsDeleted).AsNoTracking().ToList();
    74 
    75             return userInRoles;
    76         }
    77     }
    UserInRoleDataAccess
     1     public class UserInRoleMapping : IEntityTypeConfiguration<UserInRole>
     2     {
     3         public void Configure(EntityTypeBuilder<UserInRole> builder)
     4         {
     5             builder.HasKey(p => p.Id).HasName("user_in_role_id"); ;
     6 
     7             builder.Property(p => p.Id).IsRequired().HasColumnType("varchar(36)").HasColumnName("id");
     8             builder.Property(p => p.CreatedBy).IsRequired().HasColumnType("varchar(36)").HasColumnName("created_by");
     9             builder.Property(p => p.CreatedTime).IsRequired().HasColumnType("timestamp(6) without time zone").HasColumnName("created_time");
    10             builder.Property(p => p.IsDeleted).HasColumnType("boolean").HasDefaultValue(false).HasColumnName("is_deleted");
    11             builder.Property(p => p.LastUpdatedBy).HasColumnType("varchar(36)").HasColumnName("last_updated_by");
    12             builder.Property(p => p.LastUpdatedTime).HasColumnType("timestamp(6) without time zone").HasColumnName("last_updated_time");
    13             builder.Property(p => p.RoleId).IsRequired().HasColumnType("varchar(36)").HasColumnName("role_id");
    14             builder.Property(p => p.UserId).IsRequired().HasColumnType("varchar(36)").HasColumnName("user_id");
    15 
    16             builder.ToTable("user_in_role");
    17         }
    18     }
    UserInRoleMapping

    3.创建服务提供程序

      一般来说有了实体和数据访问层就可以做CRUD操作了,但为了屏蔽数据访问层差异、为接入其它层做准备、方便集成工作单元、事务等,需要包装各种业务服务使其易用易维护。示例如下:(一个接口示例类、一个实现示例类)

     1     public interface IUserInRoleProvider
     2     {
     3         UserInRole AddUserInRole(string tenantId, UserInRole userInRole);
     4 
     5         UserInRole EditUserInRole(string tenantId, UserInRole userInRole);
     6 
     7         bool RemoveUserInRole(string tenantId, string userId, string id);
     8 
     9         bool Remove(string tenantId, string userId, string id);
    10 
    11         UserInRole GetUserInRole(string tenantId, string id);
    12 
    13         List<UserInRole> GetUserInRolesByUserId(string tenantId, string userId);
    14 
    15         List<UserInRole> GetUserInRoles(string tenantId);
    16     }
    IUserInRoleProvider
     1     public class UserInRoleProvider : IUserInRoleProvider
     2     {
     3         private static readonly string cacheKeyPrefix = nameof(UserInRole);
     4         private static readonly IUserInRoleDataAccess _userInRoleDataAccess = new UserInRoleDataAccess();
     5 
     6         public UserInRole AddUserInRole(string tenantId, UserInRole userInRole)
     7         {
     8             userInRole.Id = Guid.NewGuid().ToString();
     9 
    10             _userInRoleDataAccess.AddUserInRole(tenantId, userInRole);
    11 
    12             string key = $"{cacheKeyPrefix}_{userInRole.UserId}";
    13             CacheManager.Remove(key);
    14 
    15             return userInRole;
    16         }
    17 
    18         public UserInRole EditUserInRole(string tenantId, UserInRole userInRole)
    19         {
    20             _userInRoleDataAccess.EditUserInRole(tenantId, userInRole);
    21 
    22             string key = $"{cacheKeyPrefix}_{userInRole.UserId}";
    23             CacheManager.Remove(key);
    24 
    25             return _userInRoleDataAccess.GetUserInRole(tenantId, userInRole.Id);
    26         }
    27 
    28         public bool RemoveUserInRole(string tenantId, string userId, string id)
    29         {
    30             bool result = _userInRoleDataAccess.RemoveUserInRole(tenantId, id) >= 1;
    31             if (result)
    32             {
    33                 string key = $"{cacheKeyPrefix}_{userId}";
    34                 CacheManager.Remove(key);
    35             }
    36 
    37             return result;
    38         }
    39 
    40         public bool Remove(string tenantId, string userId, string id)
    41         {
    42             bool result = _userInRoleDataAccess.Remove(tenantId, id) >= 1;
    43             if (result)
    44             {
    45                 string key = $"{cacheKeyPrefix}_{userId}";
    46                 CacheManager.Remove(key);
    47             }
    48 
    49             return result;
    50         }
    51 
    52         public UserInRole GetUserInRole(string tenantId, string id)
    53         {
    54             return _userInRoleDataAccess.GetUserInRole(tenantId, id);
    55         }
    56 
    57         public List<UserInRole> GetUserInRolesByUserId(string tenantId, string userId)
    58         {
    59             string key = $"{cacheKeyPrefix}_{userId}";
    60 
    61             return CacheManager.GetOrCreate(key, () =>
    62             {
    63                 return _userInRoleDataAccess.GetUserInRolesByUserId(tenantId, userId);
    64             });
    65         }
    66 
    67         public List<UserInRole> GetUserInRoles(string tenantId)
    68         {
    69             return _userInRoleDataAccess.GetUserInRoles(tenantId);
    70         }
    71     }
    UserInRoleProvider

    4.以中间件的方式授权

      如果我们的服务端(服务端与客户端的名称是相对的,比如在这里提到的服务端在IDS中其实是客户端)需要接入授权功能,有较多的方式可以选择使用。因为中间件使用简单,且授权流程与中间件设计思想有异曲同工之妙,所以此处选择中间件来完成授权。使用中间件实现授权功能的方式主要有两种,一种是直接引入中间件项目,包括服务提供程序,另一种是仅引入中间件项目,使用RPC的方式调用授权服务。两种方式的优缺点都比较明显,第一种会将中间件项目的整个项目依赖全部引入,包括数据访问层等,另一种仅需要在引入中间件的同时引入实体项目即可。第一种方式的代码依赖变复杂了,但是因为没有RPC,性能会比第二种方式好一些,第二种方式代码简洁易维护易升级,但是性能就比第一种差了些。可根据实际情况选择,示例选用的是第二种方案,中间件项目以HTTP请求的方式完成授权功能,示例如下:(中间件示例,扩展示例)

      1 public class PermissionMiddleware
      2 {
      3     private static readonly string _timeFormatter = "yyyy-MM-dd HH:mm:ss fff";
      4     private static readonly string _logPrefix = "PermissionMiddleware";
      5 
      6     private readonly string _identityApiHost;
      7     private readonly string _permissionHost;
      8     private readonly RequestDelegate _next;
      9     private readonly ILogger _logger;
     10 
     11     public PermissionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
     12     {
     13         _next = next;
     14         _logger = loggerFactory.CreateLogger<PermissionMiddleware>();
     15         _identityApiHost = AppSettings.Configuration.GetSection("IdentityApiHost").Value;
     16         _permissionHost = AppSettings.Configuration.GetSection("PermissionHost").Value;
     17 
     18         _logger.LogInformation($"{_logPrefix}|{DateTime.Now.ToString(_timeFormatter)}|Middleware Registered Successfully.");
     19     }
     20 
     21     public async Task Invoke(HttpContext context)
     22     {
     23         Stopwatch stopwatch = new Stopwatch();
     24 
     25         try
     26         {
     27             DebugEnter(stopwatch, nameof(Invoke));
     28 
     29             bool isPassed = false;
     30 
     31             UserIdentityInfo userIdentityInfo = TryGetUserIdentityInfo(context);
     32             string tenantId = userIdentityInfo.TenantId;
     33             string userId = userIdentityInfo.UserId;
     34             string token = userIdentityInfo.AccessToken;
     35 
     36             if (string.IsNullOrWhiteSpace(tenantId) || string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(token))
     37             {
     38                 Unauthorized(context, "必要信息为空");
     39 
     40                 return;
     41             }
     42 
     43             if (CheckAllowAnonymous(context))
     44             {
     45                 isPassed = true;
     46             }
     47             else
     48             {
     49                 bool result = await CheckPermission(context, tenantId, userId);
     50                 if (result)
     51                 {
     52                     isPassed = true;
     53                 }
     54             }
     55 
     56             if (isPassed)
     57             {
     58                 DebugExit(stopwatch, nameof(Invoke));
     59 
     60                 await _next.Invoke(context);
     61             }
     62             else
     63             {
     64                 Unauthorized(context, "未通过权限校验");
     65             }
     66         }
     67         catch (Exception ex)
     68         {
     69             await ExceptionHandle(context, ex);
     70         }
     71     }
     72 
     73     private UserIdentityInfo TryGetUserIdentityInfo(HttpContext context)
     74     {
     75         IHeaderDictionary headers = context.Request.Headers;
     76         IQueryCollection query = context.Request.Query;
     77         UserIdentityInfo userIdentityInfo = new UserIdentityInfo();
     78 
     79         if (headers != null)
     80         {
     81             userIdentityInfo.TenantId = headers.ContainsKey(Constant.TenantId) ? headers[Constant.TenantId].ToString() : null;
     82             userIdentityInfo.UserId = headers.ContainsKey(Constant.UserId) ? headers[Constant.UserId].ToString() : null;
     83             userIdentityInfo.AccessToken = headers.ContainsKey(Constant.Authorization) ? headers[Constant.Authorization].ToString() : null;
     84         }
     85 
     86         if (query != null)
     87         {
     88             userIdentityInfo.TenantId = string.IsNullOrWhiteSpace(userIdentityInfo.TenantId) ? query.ContainsKey(Constant.TenantId) ? query[Constant.TenantId].ToString() : null : userIdentityInfo.TenantId;
     89             userIdentityInfo.UserId = string.IsNullOrWhiteSpace(userIdentityInfo.UserId) ? query.ContainsKey(Constant.UserId) ? query[Constant.UserId].ToString() : null : userIdentityInfo.UserId;
     90             userIdentityInfo.AccessToken = string.IsNullOrWhiteSpace(userIdentityInfo.AccessToken) ? query.ContainsKey(Constant.Authorization) ? query[Constant.Authorization].ToString() : null : userIdentityInfo.AccessToken;
     91         }
     92 
     93         return userIdentityInfo;
     94     }
     95 
     96     private bool CheckAllowAnonymous(HttpContext context)
     97     {
     98         Endpoint endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
     99         if (endpoint != null)
    100         {
    101             return endpoint.Metadata.GetMetadata<IAllowAnonymous>() != null;
    102         }
    103 
    104         return false;
    105     }
    106 
    107     private async Task<bool> CheckPermission(HttpContext context, string tenantId, string userId)
    108     {
    109         string requestPath = context.Request.Path.Value.ToLower();
    110 
    111         List<Entities.Permission> permissions = await GetUserPermissions(tenantId, userId);
    112         Entities.Permission permission = permissions?.Find(p => requestPath.StartsWith(p.Uri.ToLower()) && (requestPath.Length == p.Uri.Length || requestPath[p.Uri.Length] == '/'));
    113 
    114         if (permission != null)
    115         {
    116             return true;
    117         }
    118 
    119         return false;
    120     }
    121 
    122     private async Task<List<Entities.Permission>> GetUserPermissions(string tenantId, string userId)
    123     {
    124         Stopwatch stopwatch = new Stopwatch();
    125         DebugEnter(stopwatch, nameof(GetUserPermissions));
    126 
    127         List<Entities.Permission> permissions = new List<Entities.Permission>();
    128 
    129         string url = $"{_permissionHost}api/controllerdemo/actiondemo?tenantId={tenantId}&userId={userId}";
    130         string result = await HttpHelper.RequestAsync(url);
    131         if (string.IsNullOrWhiteSpace(result))
    132         {
    133             return null;
    134         }
    135 
    136         List<UserInRole> userInRoles = JsonSerializer.Deserialize<List<UserInRole>>(result);
    137 
    138         if (userInRoles != null)
    139         {
    140             foreach (var userInRole in userInRoles)
    141             {
    142                 List<PermissionInRole> permissionInRoles = userInRole.Role?.PermissionInRoles?.ToList();
    143                 if (permissionInRoles != null)
    144                 {
    145                     foreach (var permissionInRole in permissionInRoles)
    146                     {
    147                         permissions.Add(permissionInRole.Permission);
    148                     }
    149                 }
    150             }
    151         }
    152 
    153         DebugExit(stopwatch, nameof(GetUserPermissions));
    154 
    155         return permissions;
    156     }
    157 
    158     private async Task ExceptionHandle(HttpContext context, Exception ex)
    159     {
    160         _logger.LogError(ex, ex.Message);
    161 
    162         context.Response.StatusCode = StatusCodes.Status401Unauthorized;
    163         string message = "Unauthorized";
    164 
    165         await context.Response.WriteAsync($"{message}");
    166     }
    167 
    168     private void Unauthorized(HttpContext context, string message)
    169     {
    170         string logSegment = $"{_logPrefix}|{DateTime.Now.ToString(_timeFormatter)}";
    171 
    172         _logger.LogWarning($"{logSegment}|Unauthorized|{message}");
    173 
    174         context.Response.StatusCode = StatusCodes.Status401Unauthorized;
    175     }
    176 
    177     private void DebugEnter(Stopwatch stopwatch, string method)
    178     {
    179         string logSegment = $"{_logPrefix}|{DateTime.Now.ToString(_timeFormatter)}";
    180 
    181         stopwatch.Start();
    182 
    183         _logger.LogDebug($"{logSegment}|Enter {method}.");
    184     }
    185 
    186     private void DebugExit(Stopwatch stopwatch, string method)
    187     {
    188         string logSegment = $"{_logPrefix}|{DateTime.Now.ToString(_timeFormatter)}";
    189 
    190         _logger.LogDebug($"{logSegment}|Exit {method}. Cost:{stopwatch.ElapsedMilliseconds}");
    191 
    192         stopwatch.Stop();
    193     }
    194 }
    PermissionMiddleware
     1     public static class PermissionExtensions
     2     {
     3         public static IApplicationBuilder UsePermission(this IApplicationBuilder app)
     4         {
     5             if (app == null)
     6             {
     7                 throw new ArgumentNullException(nameof(app));
     8             }
     9 
    10             return app.UseMiddleware<PermissionMiddleware>();
    11         }
    12     }
    PermissionExtensions

    5.为授权提供API服务

      新增AspNetCore项目,该项目引用服务提供程序,主要完成两个功能,一个是对资源、许可、角色的管理,一个是为授权中间件提供远程服务。示例如下:(一个Controller基类、一个Controller示例、Startup)

     1     /// <summary>
     2     /// Permission ControllerBase Class
     3     /// </summary>
     4     [Route("api/[controller]/[action]")]
     5     [ApiController]
     6     public class PermissionControllerBase : ControllerBase
     7     {
     8         /// <summary>
     9         /// User HttpContext
    10         /// </summary>
    11         protected UserHttpContext UserHttpContext { get; }
    12 
    13         /// <summary>
    14         /// TenantId
    15         /// </summary>
    16         protected string TenantId { get; }
    17 
    18         /// <summary>
    19         /// UserId
    20         /// </summary>
    21         protected string UserId { get; }
    22 
    23         /// <summary>
    24         /// Permission ControllerBase Constructor
    25         /// </summary>
    26         /// <param name="userHttpContext"></param>
    27         public PermissionControllerBase(UserHttpContext userHttpContext)
    28         {
    29             UserHttpContext = userHttpContext;
    30 
    31             if (UserHttpContext != null)
    32             {
    33                 TenantId = UserHttpContext.TenantId;
    34                 UserId = UserHttpContext.UserId;
    35             }
    36         }
    37     }
    PermissionControllerBase
     1     /// <summary>
     2     /// UserInRole Controller Class
     3     /// </summary>
     4     public class UserInRoleController : PermissionControllerBase
     5     {
     6         private readonly IUserInRoleProvider _userInRoleProvider;
     7 
     8         /// <summary>
     9         /// UserInRole Controller Constructor
    10         /// </summary>
    11         /// <param name="userInRoleProvider"></param>
    12         /// <param name="userHttpContext"></param>
    13         public UserInRoleController(IUserInRoleProvider userInRoleProvider, UserHttpContext userHttpContext) : base(userHttpContext)
    14         {
    15             _userInRoleProvider = userInRoleProvider;
    16         }
    17 
    18         /// <summary>
    19         /// Get UserInRole
    20         /// </summary>
    21         /// <param name="id"></param>
    22         /// <returns></returns>
    23         [HttpGet]
    24         public UserInRole GetUserInRole(string id)
    25         {
    26             return _userInRoleProvider.GetUserInRole(TenantId, id);
    27         }
    28 
    29         /// <summary>
    30         /// Get UserInRoles By UserId
    31         /// </summary>
    32         /// <returns></returns>
    33         [HttpGet]
    34         public List<UserInRole> GetUserInRolesByUserId()
    35         {
    36             return _userInRoleProvider.GetUserInRolesByUserId(TenantId, UserId);
    37         }
    38 
    39         /// <summary>
    40         /// Get UserInRoles
    41         /// </summary>
    42         /// <returns></returns>
    43         [HttpGet]
    44         public List<UserInRole> GetUserInRoles()
    45         {
    46             return _userInRoleProvider.GetUserInRoles(TenantId);
    47         }
    48 
    49         /// <summary>
    50         /// Add UserInRole
    51         /// </summary>
    52         /// <param name="userInRole"></param>
    53         /// <returns></returns>
    54         [HttpPost]
    55         public UserInRole AddUserInRole(UserInRole userInRole)
    56         {
    57             return _userInRoleProvider.AddUserInRole(TenantId, userInRole);
    58         }
    59 
    60         /// <summary>
    61         /// Edit UserInRole
    62         /// </summary>
    63         /// <param name="userInRole"></param>
    64         /// <returns></returns>
    65         [HttpPost]
    66         public UserInRole EditUserInRole(UserInRole userInRole)
    67         {
    68             return _userInRoleProvider.EditUserInRole(TenantId, userInRole);
    69         }
    70 
    71         /// <summary>
    72         /// Remove UserInRole
    73         /// </summary>
    74         /// <param name="id"></param>
    75         /// <returns></returns>
    76         [HttpPost]
    77         public bool RemoveUserInRole(string id)
    78         {
    79             return _userInRoleProvider.RemoveUserInRole(TenantId, UserId, id);
    80         }
    81     }
    UserInRoleController
      1     /// <summary>
      2     /// Startup Class
      3     /// </summary>
      4     public class Startup
      5     {
      6         /// <summary>
      7         /// Startup Constructor
      8         /// </summary>
      9         /// <param name="configuration"></param>
     10         public Startup(IConfiguration configuration)
     11         {
     12             Configuration = configuration;
     13         }
     14 
     15         /// <summary>
     16         /// Represents a set of key/value application configuration properties.
     17         /// </summary>
     18         public IConfiguration Configuration { get; }
     19 
     20         /// <summary>
     21         /// This method gets called by the runtime. Use this method to add services to the container.
     22         /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
     23         /// </summary>
     24         /// <param name="services"></param>
     25         public void ConfigureServices(IServiceCollection services)
     26         {
     27             services.AddDbContext<PermissionContext>(options => options.UseNpgsql(Configuration.GetConnectionString("NpgConnectionString")));
     28 
     29             services.AddControllers().AddNewtonsoftJson(p =>
     30                                          {
     31                                              p.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
     32                                              p.SerializerSettings.ContractResolver = new DefaultContractResolver();
     33                                          });
     34 
     35             SwaggerGenServiceCollection(services);
     36 
     37             services.AddHttpContextAccessor();
     38 
     39             services.AddScoped<UserHttpContext>();
     40             services.AddScoped<IUserInRoleProvider, UserInRoleProvider>();
     41         }
     42 
     43         /// <summary>
     44         /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
     45         /// </summary>
     46         /// <param name="app"></param>
     47         /// <param name="env"></param>
     48         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
     49         {
     50             if (env.IsDevelopment())
     51             {
     52                 app.UseDeveloperExceptionPage();
     53 
     54                 // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
     55                 // specifying the Swagger JSON endpoint.
     56                 app.UseSwaggerUI(c =>
     57                 {
     58                     c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample Swagger V1");
     59                 });
     60             }
     61 
     62             app.UseStaticFiles();
     63 
     64             app.UseSwagger();
     65 
     66             app.UseRouting();
     67 
     68             app.UseEndpoints(endpoints =>
     69             {
     70                 endpoints.MapControllers();
     71             });
     72         }
     73 
     74         private void SwaggerGenServiceCollection(IServiceCollection services)
     75         {
     76             //Swagger
     77             services.AddSwaggerGen(c =>
     78             {
     79                 c.SwaggerDoc("v1", new OpenApiInfo
     80                 {
     81                     Version = "v1",
     82                     Title = "Permission",
     83                     Description = "Authentication Authorization",
     84                     TermsOfService = new Uri("https://TODO")
     85                     Contact = new OpenApiContact
     86                     {
     87                         Name = "#TODO#",
     88                         Email = "#TODO#",
     89                         Url = new Uri("https://TODO"),
     90                     },
     91                     License = new OpenApiLicense
     92                     {
     93                         Name = "#TODO#",
     94                         Url = new Uri("https://TODO"),
     95                     }
     96                 });
     97 
     98                 c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Demo.Permission.Entities.xml"));
     99                 c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Demo.Permission.Restful.xml"));
    100             });
    101         }
    102     }
    Startup

    6.单元测试

      中间件的单元测试参见:为AspNetCore中间件编写单元测试,地址:https://www.cnblogs.com/xuejietong/p/14336602.html  

    7.其它技术

      示例中使用了缓存(仅做示例使用,重点在于简单授权的思路,缓存未做高可用支持,比如逻辑过期时间、物理过期时间、同步等均未考虑)、依赖注入(当前用户上下文)等技术,核心代码如下所示:

     1 public sealed class LocalCacheProvider
     2     {
     3         private static readonly IMemoryCache _cache = new MemoryCache(Options.Create(new MemoryCacheOptions() { }));
     4 
     5         public static LocalCacheProvider Instance { get; } = new LocalCacheProvider();
     6 
     7         private LocalCacheProvider() { }
     8 
     9         public bool? Get<T>(string key, out T t)
    10         {
    11             return _cache.TryGetValue(key, out t);
    12         }
    13 
    14         public bool? Set<T>(string key, T t)
    15         {
    16             _cache.Set(key, t, UseDefaultExpiration());
    17             return true;
    18         }
    19 
    20         public bool? Remove(string key)
    21         {
    22             _cache.Remove(key);
    23             return true;
    24         }
    25 
    26         private static MemoryCacheEntryOptions UseDefaultExpiration()
    27         {
    28             return new MemoryCacheEntryOptions().SetAbsoluteExpiration(new DateTimeOffset(DateTime.Now.Date.AddDays(1).AddHours(4)));
    29         }
    30     }
    LocalCacheProvider
     1 public static class CacheManager
     2     {
     3         private static readonly object obj = new object();
     4 
     5         public static T GetOrCreate<T>(string key, Func<T> func)
     6         {
     7             T t = GetCache<T>(key);
     8 
     9             if (null == t)
    10             {
    11                 lock (obj)
    12                 {
    13                     t = GetCache<T>(key);
    14 
    15                     if (null == t)
    16                     {
    17                         t = func();
    18 
    19                         SetCache(key, t);
    20                     }
    21                 }
    22             }
    23 
    24             return t;
    25         }
    26 
    27         public static void SetCache<T>(string key, T t)
    28         {
    29             LocalCacheProvider.Instance.Set(key, t);
    30         }
    31 
    32         public static void Remove(string key)
    33         {
    34             LocalCacheProvider.Instance.Remove(key);
    35         }
    36 
    37         private static T GetCache<T>(string key)
    38         {
    39             LocalCacheProvider.Instance.Get(key, out T t);
    40 
    41             return t;
    42         }
    43     }
    CacheManager
     1 public sealed class UserHttpContext
     2     {
     3         private string _tenantId;
     4         private string _userId;
     5 
     6         public string TenantId { get { return _tenantId; } }
     7 
     8         public string UserId { get { return _userId; } }
     9 
    10         public UserHttpContext(IHttpContextAccessor httpContextAccessor)
    11         {
    12             HttpContext context = httpContextAccessor?.HttpContext;
    13 
    14             if (context != null)
    15             {
    16                 TrySetUserHttpContext(context);
    17 
    18                 if (_tenantId == null)
    19                 {
    20                     throw new System.ArgumentNullException(Constant.TenantId);
    21                 }
    22                 if (_userId == null)
    23                 {
    24                     throw new System.ArgumentNullException(Constant.UserId);
    25                 }
    26             }
    27         }
    28 
    29         private void TrySetUserHttpContext(HttpContext context)
    30         {
    31             IHeaderDictionary headers = context.Request.Headers;
    32             IQueryCollection query = context.Request.Query;
    33 
    34             if (headers != null)
    35             {
    36                 _tenantId = headers.ContainsKey(Constant.TenantId) ? headers[Constant.TenantId].ToString() : null;
    37                 _userId = headers.ContainsKey(Constant.UserId) ? headers[Constant.UserId].ToString() : null;
    38             }
    39 
    40             if (query != null)
    41             {
    42                 _tenantId = string.IsNullOrWhiteSpace(_tenantId) ? query.ContainsKey(Constant.TenantId) ? query[Constant.TenantId].ToString() : null : _tenantId;
    43                 _userId = string.IsNullOrWhiteSpace(_userId) ? query.ContainsKey(Constant.UserId) ? query[Constant.UserId].ToString() : null : _userId;
    44             }
    45         }
    46     }
    UserHttpContext

    8.总结

      使用PostgreSQL数据库,Entity Framework Core做为数据提供程序且实现正向工程、逆向工程。在数据层(DataAccess)的上层和服务提供程序(Provider)的下层引入了缓存层(Cache),实现了简单的权限缓存功能。一般情况下服务提供程序职责可能是为服务层(Service)提供支持,但在本示例中省略了服务层,直接使用WebApi调用了服务提供程序(Provider)。最后以中间件的方式实现鉴权功能。此示例使用RPC实现鉴权,在中间件获取权限一处的URL片段controllerdemo/actiondemo其实就是UserInRole/GetUserInRolesByUserId。根据需要也可以再提供一套SDK为不适用RPC的项目使用。

  • 相关阅读:
    设置共享文件夹大小
    About IConfigurationSectionHandler Interface
    zoj 1050
    SQL Server 数据库优化经验总结
    一、页面输出缓存
    [转]深入解读 ADO.NET 2.0 的十大最新特性
    ASP.NET 缓存学习
    [转]写给ASP.NET程序员:网站中的安全问题
    [转] ASP.NET 性能提升秘诀之管道与进程优化
    实战 SQL Server 2005 镜像配置
  • 原文地址:https://www.cnblogs.com/xuejietong/p/14349601.html
Copyright © 2011-2022 走看看