总体设计思路
在设计本项目的通用权限前,我参阅过很多设计方案,最终定下RBAC(基于角色的权限控制)。微软本身是有一套默认的权限控制的(asp.net core identity),但有如下几个缺点
1、表结构固定,不好扩展。
2、不能动态的对接口进行角色的授权,只能写在代码里。所以本框架的设计会考虑如下几点
- 不定义表结构,各权限表的结构完全可由用户自己定义,只需按规范实现接口即可
- 能动态分配接口的角色
具体设计摘要
- 权限包含人员、角色、人员角色关系、资源、角色资源这5个表,各表不自定具体的结构,只通过接口进行约定
- 权限核心逻辑由IPermission和IPermissionStore来定义。
- IPermission定义了用户的登录、鉴权、初始化资源、从请求上下文识别出资源id、获取所有资源及对应角色、登录密码加密算法等方法,默认实现为DefaultPermission->BasePermission->IPermission
- IPermissionStore定义各权限相关数据的获取、更新、缓存刷新方法。所有的权限相关数据都在缓存里,当数据有变化时,要调用IPermissionStore的缓存刷新方法来进行。默认实现为DefaultPermissionStore->BasePermissionStore->IPermissionStore
- 用“基于策略”的方式进行鉴权。策略的实现逻辑为PermissionRequirementHandler,依赖于IPermission,通过IPermission.HasPermission方法来判断是否有权限。
- 支持cookies和jwt两种方式。在登录时,由
- 通过在Action上加ResourceAttribute来定义哪些接口是需要进行鉴权的,并自动加入到Resource数据表里
各表结构的接口约定
- 人员、角色、人员角色关系、资源、角色资源关系的接口如下
public interface IHasKeyAndName
{
/// <summary>
/// 一般为id,主键
/// </summary>
/// <returns></returns>
string GetKey();
/// <summary>
/// 一般为描述
/// </summary>
/// <returns></returns>
string GetName();
}
人员接口
public interface IUser:IHasKeyAndName
{
string GetAccount();
string GetPassword();
}
角色接口
public interface IRole:IHasKeyAndName
{
}
人员角色关系接口
public interface IUserRole
{
string GetUserKey();
string GetRoleKey();
}
资源接口
/// <summary>
/// 资源(指所有要权限控制的资源,如接口,菜单)
/// </summary>
public interface IResource:IHasKeyAndName
{
/// <summary>
/// 用于绑定到前端,前端在做权限和界面元素的绑定时,一般不会用id(id可读性差)和name(name可能会改变),一般以code做约定
/// </summary>
/// <returns></returns>
string GetResourceCode();
}
角色资源关系接口
public interface IRoleResource
{
string GetRoleKey();
string GetResourceKey();
}
核心权限接口定义
IPermission接口定义
/// <summary>
/// 权限接口,这此接口是对外的,非对外的方法,不要写在接口里。
/// </summary>
public interface IPermission
{
#region 用于判断用户是否有资源权限的必要方法
/// <summary>
/// 通过访问的资源,获取资源的key。如obj可能为action,url
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
string GetRequestResourceKey(object obj);
/// <summary>
/// 通过对象获取资源code
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
string GetRequestResourceCode(object obj);
/// <summary>
/// 用户是否有资源的权限
/// </summary>
/// <param name="resourceKey">资源key</param>
/// <param name="userKey">用户key</param>
/// <returns></returns>
bool HasPermission(string resourceKey, string userKey);
/// <summary>
/// 从ClaimsPrincipal获取用户信息
/// </summary>
/// <param name="claimsPrincipal">ClaimsPrincipal</param>
/// <returns></returns>
UserInfo GetUserInfo(ClaimsPrincipal claimsPrincipal);
#endregion
#region 登录、前端界面权限控制必要方法
/// <summary>
/// 登录
/// </summary>
/// <param name="loginDto">登录dto</param>
/// <returns>如果登录成功,返回的结果;如果登录不成功,会抛出异常</returns>
/// <remarks>
/// 配置GetAllResourceRoles方法,可实现前端的权限控制
/// </remarks>
LoginResult Login(LoginDto loginDto);
/// <summary>
/// 获取所有的资源以及资源角色的对应关系信息
/// </summary>
/// <returns></returns>
/// <remarks>
/// 前端调用此接口,获取所有的资源及资源的角色,用于渲染界面权限控制
/// </remarks>
List<ResourceRoleInfo> GetAllResourceRoles();
/// <summary>
/// 通过userInfo生成Claims,Claims会用于生成token
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
List<Claim> GetClaims(IUserInfo userInfo);
///// <summary>
///// 获取登录token
///// </summary>
///// <param name="account"></param>
///// <param name="pwd"></param>
///// <returns></returns>
//string GetLoginToken(string account, string pwd);
///// <summary>
///// 获取用户信息,用于给前端用户展示
///// </summary>
///// <param name="token"></param>
//IUserInfo GetUserInfo(string token);
#endregion
#region 其它
/// <summary>
/// 获取password的hash,可能加salt或是不加,hash的算法也可以由用户自己配置。
/// 如果用户密码在存储时不做hash处理,则此方法返回pwd的明文即可
/// 此方法用于两处
/// 1、登录验证
/// 2、修改、增加密码时
/// </summary>
/// <param name="pwd">用户输入的密码明文</param>
/// <returns>密码明文的hash</returns>
string HashPwd(string pwd);
/// <summary>
/// 初始化权限资源
/// </summary>
void InitResource();
#endregion
}
IPermissionStore接口定义
/// <summary>
/// 权限存储相关的接口约定
/// </summary>
public interface IPermissionStore
{
#region 查询权限数据
/// <summary>
/// 获取所有的用户
/// </summary>
/// <returns></returns>
List<IUser> GetAllUser();
/// <summary>
/// 获取所有的角色
/// </summary>
/// <returns></returns>
List<IRole> GetAllRole();
/// <summary>
/// 获取所有角色和用户的关系
/// </summary>
/// <returns></returns>
List<IUserRole> GetAllUserRole();
/// <summary>
/// 获取所有的资源
/// </summary>
/// <returns></returns>
List<IResource> GetAllResource();
/// <summary>
/// 获取所有角色和资源的关系
/// </summary>
/// <returns></returns>
List<IRoleResource> GetAllRoleResource();
#endregion
#region 管理权限数据
/// <summary>
/// 保存用户
/// </summary>
/// <param name="user"></param>
void SaveUser(IUser user);
/// <summary>
/// 删除用户
/// </summary>
/// <param name="userKey"></param>
void RemoveUser(string userKey);
/// <summary>
/// 保存角色
/// </summary>
/// <param name="role"></param>
void SaveRole(IRole role);
/// <summary>
/// 删除角色
/// </summary>
/// <param name="roleKey"></param>
void RemoveRole(string roleKey);
/// <summary>
/// 保存资源
/// </summary>
/// <param name="resource"></param>
void SaveResource(IResource resource);
/// <summary>
/// 删除资源
/// </summary>
/// <param name="resourceKey"></param>
void RemoveResource(string resourceKey);
/// <summary>
/// 设备用户的角色
/// </summary>
/// <param name="userKey"></param>
/// <param name="roleKeys"></param>
void SetUserRoles(string userKey, List<string> roleKeys);
/// <summary>
/// 设置角色的资源
/// </summary>
/// <param name="roleKey">角色key</param>
/// <param name="resourceKeys">资源keys</param>
void SetRoleResources(string roleKey, List<string> resourceKeys);
/// <summary>
/// IPermissionStore的实现里如果用了缓存,此方法用于刷新缓存为最新数据。
/// 如果用户是通过非IPermissionStore接口方法操作权限数据,则要调用此方法进行数据刷新
/// </summary>
void ReloadPemissionDatas();
#endregion
}
怎么用?
下面按将权限控制接入到项目的开发步骤进行示例和解读
1、定义权限表实体
- 包含人员、角色、人员角色关系、资源、角色资源这5个表
- 分别定义User,Role,UserRole,Resource,RoleResource5个实体,分别继承IUser,IRole,IUserRole,IResource,IRoleResource接口
- 由于代码比较简单,就不附源码了,详细可以查看ApplicationCore的Entities文件夹里的实现定义
2、创建IPermissionStore接口的实现类
- 数据库框架用的是entityframework core,将已经定义好的实体加到DbContext里(参考Infrastracture项目里的AppDbContext)
- 为方便扩展,我创建了基类BasePermissionStore,并实现IPermissionStore,在项目接入时,可继承BasePermissionStore类,并实现部分虚方法即可。如DefaultPermissionStore。(由于只是简单的数据库CRUD操作,详细代码请查看Web项目里Permission里的代码)
- 默认的实现里,我加了缓存,避免每次权限验证时去查库,并在权限相关数据改变时清空对应的缓存
3、创建IPermission接口的实现类
- 为方便是快速接入,可继承BasePermission。或自己实现IPermission接口
- 本框架默认的实现为DefaultPermission
- IPermission的大致思路为,用IPermissionStore里提供的权限相关数据,判断用户的角色,进而知道用户有哪些授权资源。
- 附BasePermission和DefaultPermission的源码
BasePermission
/// <summary>
/// 权限控制抽象基类,外部在实现权限控制时,如果继承此类,会简化实现的过程,也可以继承IPermission接口,自己实现
/// </summary>
/// <remarks>
/// todo 由于鉴权是频繁的操作,后期计划将鉴权方法里linq相关的操作用hash和缓存技术实现,进一步提高性能
/// </remarks>
public abstract class BasePermission : IPermission
{
protected IPermissionStore _permissionStore;
protected abstract PermissionOptions PermissionOptions {set;get;}
public BasePermission(IPermissionStore permissionStore)
{
_permissionStore = permissionStore;
}
#region 用于判断用户是否有资源权限的必要方法
public virtual string GetRequestResourceKey(object obj)
{
var resourceKey = string.Empty;
var resourceCode = GetRequestResourceCode(obj);
if (!string.IsNullOrEmpty(resourceCode))
{
resourceKey = _permissionStore.GetAllResource().FirstOrDefault(a => a.GetResourceCode() == resourceCode)?.GetKey();
}
return resourceKey;
}
public abstract string GetRequestResourceCode(object obj);
public virtual bool HasPermission(string resourceKey, string userKey)
{
var userRoleKeys = _permissionStore.GetAllUserRole().Where(a => a.GetUserKey() == userKey).Select(a => a.GetRoleKey());
var resource = _permissionStore.GetAllResource().FirstOrDefault(a => a.GetKey() == resourceKey);
//未纳入到资源表里的资源,如果进入到鉴权过程时,不允许访问。请将不需要做权限控制的资源设置成允许匿名访问,避免进入到鉴权流程
if (resource==null)
{
return false;
}
var resourceRoleKeys = _permissionStore.GetAllRoleResource().Where(a => a.GetResourceKey() == resource.GetKey()).Select(a => a.GetRoleKey());
return userRoleKeys.Intersect(resourceRoleKeys).Any();
}
public virtual UserInfo GetUserInfo(ClaimsPrincipal claimsPrincipal)
{
return new UserInfo
{
Account = claimsPrincipal.FindFirst(PermissionConstant.accountClaim)?.Value,
RoleKeys = (claimsPrincipal.FindFirst(PermissionConstant.roleIdsClaim)?.Value ?? "").Split(',').ToList(),
RoleNames = (claimsPrincipal.FindFirst(PermissionConstant.rolesNamesClaim)?.Value ?? "").Split(',').ToList(),
UserKey = claimsPrincipal.FindFirst(PermissionConstant.userIdClaim)?.Value,
UserName = claimsPrincipal.FindFirst(PermissionConstant.userNameClaim)?.Value,
};
}
#endregion
#region 登录、前端界面权限控制必要方法
/// <summary>
/// 登录,返回用户的基本信息和token
/// </summary>
/// <param name="loginDto">登录dto</param>
/// <returns>用户的基本信息和token对象</returns>
public virtual LoginResult Login(LoginDto loginDto)
{
var user = _permissionStore.GetAllUser().FirstOrDefault(a => a.GetAccount().Equals(loginDto.Account,StringComparison.OrdinalIgnoreCase));
if (user != null && HashPwd(loginDto.Pwd).Equals(user.GetPassword(),StringComparison.OrdinalIgnoreCase))
{
var roleKeys = _permissionStore.GetAllUserRole().Where(a => a.GetUserKey() == user.GetKey()).Select(a => a.GetRoleKey()) ?? new List<string>();
var roleNames = _permissionStore.GetAllRole().Where(a => roleKeys.Contains(a.GetKey())).Select(a => a.GetName()) ?? new List<string>();
var userInfo = new UserInfo
{
Account = user.GetAccount(),
RoleKeys = roleKeys.ToList(),
RoleNames = roleNames.ToList(),
UserKey = user.GetKey(),
UserName = user.GetName()
};
var claims = GetClaims(userInfo);
var tokenStr= GenerateTokenStr(claims);
return new LoginResult
{
Token = tokenStr,
UserInfo = userInfo
};
}
else
{
throw new BusinessException($"用户名或密码错误");
}
}
public virtual List<ResourceRoleInfo> GetAllResourceRoles()
{
var result = new List<ResourceRoleInfo>();
var allResource = _permissionStore.GetAllResource();
var allRole = _permissionStore.GetAllRole();
var allRoleResource = _permissionStore.GetAllRoleResource();
allResource.ForEach(resource =>
{
var resourceRoleKeys = allRoleResource.Where(a => a.GetResourceKey() == resource.GetKey()).Select(a => a.GetRoleKey()).Distinct().ToList();
result.Add(new ResourceRoleInfo
{
ResourceCode=resource.GetResourceCode(),
ResourceKey=resource.GetKey(),
ResourceName=resource.GetName(),
RoleKeys= resourceRoleKeys
});
});
return result;
}
public virtual List<Claim> GetClaims(IUserInfo userInfo)
{
return new List<Claim>
{
new Claim(PermissionConstant.userIdClaim,userInfo.UserKey),
new Claim(PermissionConstant.userNameClaim,userInfo.UserName),
new Claim(PermissionConstant.accountClaim,userInfo.Account),
new Claim(PermissionConstant.roleIdsClaim,string.Join(",",userInfo.RoleKeys??new List<string>()) ),
new Claim(PermissionConstant.rolesNamesClaim,string.Join(",",userInfo.RoleNames??new List<string>()) ),
};
}
#endregion
/// <summary>
/// 默认的密码hash算法
/// </summary>
/// <param name="pwd">密码明文</param>
/// <returns></returns>
public virtual string HashPwd(string pwd)
{
return BitConverter.ToString(HashAlgorithm.Create(HashAlgorithmName.MD5.Name).ComputeHash(Encoding.UTF8.GetBytes(pwd))).Replace("-", "");
}
public abstract string GenerateTokenStr(List<Claim> claims);
public abstract void InitResource();
}
DefaultPermission
/// <summary>
/// 权限的默认实现类
/// </summary>
public class DefaultPermission : BasePermission
{
public static readonly string superAdminRoleName = "SuperAdmin";
protected override PermissionOptions PermissionOptions { get; set; }
public DefaultPermission(IPermissionStore permissionStore, IOptionsMonitor<PermissionOptions> permissionOptions) : base(permissionStore)
{
PermissionOptions = permissionOptions.CurrentValue ?? new PermissionOptions();
}
public override bool HasPermission(string resourceKey, string userKey)
{
if (IsSuperAdmin(userKey))
{
return true;
}
return base.HasPermission(resourceKey, userKey);
}
public override string GenerateTokenStr(List<Claim> claims)
{
var expireTimeSpan = (PermissionOptions.ExpireTimeSpan == null || PermissionOptions.ExpireTimeSpan == TimeSpan.Zero) ? new TimeSpan(6, 0, 0) : PermissionOptions.ExpireTimeSpan;
SigningCredentials creds;
if (PermissionOptions.IsAsymmetric)
{
var key = new RsaSecurityKey(RSAHelper.GetRSAParametersFromFromPrivatePem(PermissionOptions.RsaPrivateKey));
creds = new SigningCredentials(key, SecurityAlgorithms.RsaSha256);
}
else
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(PermissionOptions.SymmetricSecurityKey));
creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
}
var token = new JwtSecurityToken(PermissionOptions.Issuer, PermissionOptions.Audience, claims, DateTime.Now, DateTime.Now.Add(expireTimeSpan), creds);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
return tokenStr;
}
public override string HashPwd(string pwd)
{
return HashHelper.Md5($"{pwd}{PermissionOptions.PasswordSalt}");
}
/// <summary>
/// 获取资源对象的code,已经适配如下类型:AuthorizationFilterContext,ControllerActionDescriptor,methodInfo
/// 默认为className_methodName,或是resourceAttribute里设置的code
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override string GetRequestResourceCode(object obj)
{
if (obj is MethodInfo)
{
return GetResourceCode((MethodInfo)obj);
}
MethodInfo methodInfo;
if (obj is AuthorizationFilterContext authorizationFilterContext)
{
if (authorizationFilterContext.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
methodInfo = controllerActionDescriptor.MethodInfo;
return GetResourceCode(methodInfo);
//resourceCode = GetResourceCode(controllerActionDescriptor.ControllerName, controllerActionDescriptor.ActionName);
}
}
if (obj is ControllerActionDescriptor controllerActionDescriptor1)
{
methodInfo = controllerActionDescriptor1.MethodInfo;
return GetResourceCode(methodInfo);
//resourceCode = GetResourceCode(controllerActionDescriptor1.ControllerName, controllerActionDescriptor1.ActionName);
}
if (obj is RouteEndpoint endpoint)
{
//.net core 3.1后,AuthorizationHandlerContext.Resource为endpoint
methodInfo = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>()?.MethodInfo;
return GetResourceCode(methodInfo);
}
return string.Empty;
}
/// <summary>
/// 初始化所有的权限资源。
/// 所有有定义ResourceAttribute的方法都为权限资源,否则不是。要使方法受权限控制,必须做到如下两点:1、在方法上加ResourceAttribute,2、在controller或是action上加Authorize
/// </summary>
public override void InitResource()
{
var resources = new List<Resource>();
if (PermissionOptions.ResourceAssemblies == null)
{
PermissionOptions.ResourceAssemblies = new List<Assembly>();
}
var existResources = _permissionStore.GetAllResource();
PermissionOptions.ResourceAssemblies.Add(this.GetType().Assembly);
PermissionOptions.ResourceAssemblies?.Distinct().ToList().ForEach(assembly =>
{
//对所有的controller类进行扫描
assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToList().ForEach(controller =>
{
var controllerIsAdded = false;//父是否增加
var parentId = IdGenerator.Generate<string>();
var parentResource = controller.GetCustomAttribute<ResourceAttribute>();
controller.GetMethods().ToList().ForEach(method =>
{
if (method.IsDefined(typeof(ResourceAttribute), true))
{
var methodResource = method.GetCustomAttribute<ResourceAttribute>();
if (!controllerIsAdded)
{
// 增加父
resources.Add(new Resource
{
Id = parentId,
Code = parentResource?.ResourceCode??controller.Name,
CreateTime = DateTime.Now,
IsDeleted = false,
Name = parentResource?.Description??controller.Name
});
controllerIsAdded = true;
}
// 增加子
resources.Add(new Resource
{
Id = IdGenerator.Generate<string>(),
Code = GetResourceCode(method),
CreateTime = DateTime.Now,
IsDeleted = false,
ParentId = parentId,
Name = methodResource?.Description??method.Name
});
}
});
});
});
resources.ForEach(item =>
{
var temp = new Resource
{
Id = item.Id,
Code = item.Code,
CreateTime = DateTime.Now,
IsDeleted = false,
Name = item.Name,
ParentId = item.ParentId,
UpdateTime = DateTime.Now
};
// 设置资源的id
var matchRs = existResources.FirstOrDefault(i => i.GetResourceCode() == temp.Code);
if (matchRs!=null)
{
temp.Id = matchRs.GetKey();
}
// 设置资源的父id
if (!string.IsNullOrEmpty(temp.ParentId))
{
var pa = resources.FirstOrDefault(a => a.Id == temp.ParentId);
var matchPa = existResources.FirstOrDefault(i => i.GetResourceCode() == pa?.Code);
if (matchPa!=null)
{
item.ParentId = matchPa.GetKey();
}
}
_permissionStore.SaveResource(item);
});
}
private bool IsSuperAdmin(string userKey)
{
var superRole = _permissionStore.GetAllRole().FirstOrDefault(a => a.GetName().Equals(DefaultPermission.superAdminRoleName,StringComparison.OrdinalIgnoreCase));
return _permissionStore.GetAllUserRole().Any(a => a.GetUserKey() == userKey && a.GetRoleKey() == superRole.GetKey());
}
/// <summary>
/// 通过类名和方法名,获取
/// </summary>
/// <param name="className"></param>
/// <param name="methodName"></param>
/// <returns></returns>
private string GetResourceCode(MethodInfo methodInfo)
{
if (Attribute.IsDefined(methodInfo, typeof(ResourceAttribute)))
{
var attr = methodInfo.GetCustomAttribute<ResourceAttribute>();
if (attr != null && !string.IsNullOrEmpty(attr.ResourceCode))
{
return attr.ResourceCode;
}
}
return $"{methodInfo.DeclaringType.Name.Replace("Controller", "")}_{methodInfo.Name}";
}
}
4、编写鉴权处理类PermissionRequirementHandler
- 鉴权的原理请参考微软的官方文档https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/introduction?view=aspnetcore-3.1
- 原理概要解说
我用的是基于策略的鉴权方式,一个项目里可以有多种方法(策略)来判断一个资源是否有访问权限。策略的实现里是以“是否获得某个Requirement”来判断是否有权限,获得Requirement即有授权,反之则无。而判断是否有某某Requirement是由此Requirement的AuthorizationHandler来处理
配置策略
services.AddAuthorization(options =>
{
// 增加鉴权策略,并告知这个策略要判断用户是否获得了PermissionRequirement这个Requirement
options.AddPolicy(PermissionConstant.PermissionAuthorizePolicy, policyBuilder =>
{
policyBuilder.AddRequirements(new PermissionRequirement());
});
});
PermissionRequirementHandler源码如下
public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
{
private IPermission _permission;
public PermissionRequirementHandler(IPermission permission)
{
_permission = permission;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var resourceKey=_permission.GetRequestResourceKey(context.Resource);// 获取资源的key
var userKey = _permission.GetUserInfo(context.User).UserKey; // 根据用户的claims获取用户的key
if (_permission.HasPermission(resourceKey,userKey)) // 判断用户是否有权限
{
context.Succeed(requirement); // 如果有权限,则获得此Requirement
}
return Task.CompletedTask;
}
}
5、配置身份验证和权限验证
- 在Startup.cs里注入权限组件services.AddPermission,并在asp.net core管理里配置好身份验证和鉴权,即增加 app.UseAuthentication()和app.UseAuthorization();
- 默认的身份验证现实支持cookies和token,当请求过来时,如果不包含token则走cookies方式,否则走token。
AddPermission源码如下
/// <summary>
/// 权限控制核心,即必须的配置
/// </summary>
/// <param name="services"></param>
/// <param name="action"></param>
public static void AddPermission(this IServiceCollection services, Action<PermissionOptions> action)
{
services.TryAddScoped<IPermission, DefaultPermission>();
services.TryAddScoped<IPermissionStore, DefaultPermissionStore>();
#region 身份验证
var permissionOption = new PermissionOptions();
action(permissionOption);
//addAuthentication不放到AddPermissionCore方法里,是为了外部可自己配置
// 当未通过authenticate时(如无token或是token出错时),会返回401,当通过了authenticate但没通过authorize时,会返回403。
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(
CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
//下面的委托方法只会在第一次cookie验证时调用,调用时会用到上面的permissionOption变量,但其实permissionOption变量是在以前已经初始化的,所以在此方法调用之前,permissionOption变量不会被释放
options.Cookie.Name = "auth";
options.AccessDeniedPath = permissionOption.AccessDeniedPath;
options.LoginPath = permissionOption.LoginPath;
options.ExpireTimeSpan = permissionOption.ExpireTimeSpan != default ? permissionOption.ExpireTimeSpan : new TimeSpan(12, 0, 0);
options.ForwardDefaultSelector = context =>
{
string authorization = context.Request.Headers["Authorization"];
//身份验证的顺序为jwt、cookie
if (authorization != null && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
return JwtBearerDefaults.AuthenticationScheme;
}
else
{
return CookieAuthenticationDefaults.AuthenticationScheme;
}
};
var cookieAuthenticationEvents = new CookieAuthenticationEvents
{
OnSignedIn = context =>
{
return Task.CompletedTask;
},
OnSigningOut = context =>
{
return Task.CompletedTask;
}
};
options.Events = cookieAuthenticationEvents;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
// jwt可用对称和非对称算法进行验签
SecurityKey key;
if (permissionOption.IsAsymmetric)
{
key = new RsaSecurityKey(RSAHelper.GetRSAParametersFromFromPublicPem(permissionOption.RsaPublicKey));
}
else
{
key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(permissionOption.SymmetricSecurityKey));
}
options.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = PermissionConstant.userIdClaim,
RoleClaimType = PermissionConstant.roleIdsClaim,
ValidIssuer = permissionOption.Issuer,
ValidAudience = permissionOption.Audience,
IssuerSigningKey = key,
ValidateIssuer = false,
ValidateAudience = false
};
var jwtBearerEvents = new JwtBearerEvents
{
OnMessageReceived = context =>
{
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
return Task.CompletedTask;
}
};
options.Events = jwtBearerEvents;
});
#endregion
#region 授权
//权限控制只要在配置IServiceCollection,不需要额外配置app管道
//权限控制参考:https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.2
//handler和requirement有几种关系:1 handler对多requirement(此时handler实现IAuthorizationHandler);1对1(实现AuthorizationHandler<PermissionRequirement>),和多对1
//所有的handler都要注入到services,用services.AddSingleton<IAuthorizationHandler, xxxHandler>(),而哪个requirement用哪个handler,低层会自动匹配。最后将requirement对到policy里即可
services.AddAuthorization(options =>
{
options.AddPolicy(PermissionConstant.PermissionAuthorizePolicy, policyBuilder =>
{
policyBuilder.AddRequirements(new PermissionRequirement());
});
});
services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
services.AddMemoryCache();
services.TryAddScoped<IApplicationContext, ApplicationContext>();
services.AddHttpContextAccessor();
services.Configure(action);
#endregion
}
6、在需要进行权限控制的action或是Controller上加Authorize特性
[Authorize(Policy = PermissionConstant.PermissionAuthorizePolicy)]
其它要点
如何根据代码的接口自动生成权限资源
- IPermission接口里定义了InitResource方法,此方法即是自动生成权限资源的入口。
- 默认将所有的Controller里的Action设置为权限资源,如果不需要,则加上AllowAnonymous特性可即
- 资源的code为ControllerName_ActionName,描述信息可以用Resource特性来定义
- 参考DefaultPermission.InitResource的实现逻辑