复杂策略授权-实战
1.配置用户跟角色关联表
/// <summary>
/// 用户跟角色关联表
/// </summary>
public class UserRole : RootEntityTkey<long>
{
/// <summary>
/// 用户ID
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 角色ID
/// </summary>
public long RoleId { get; set; }
/// <summary>
///获取或设置是否禁用,逻辑上的删除,非物理删除
/// </summary>
[SugarColumn(IsNullable = true)]
public bool? IsDeleted { get; set; }
/// <summary>
/// 创建ID
/// </summary>
[SugarColumn(IsNullable = true)]
public long? CreateId { get; set; }
/// <summary>
/// 创建者
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string CreateBy { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 修改ID
/// </summary>
[SugarColumn(IsNullable = true)]
public int? ModifyId { get; set; }
/// <summary>
/// 修改者
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string ModifyBy { get; set; }
/// <summary>
/// 修改时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime? ModifyTime { get; set; }
}
/// <summary>
/// 用户跟角色关联表
/// </summary>
public class UserRoleVo
{
public long UserId { get; set; }
public long RoleId { get; set; }
public bool? IsDeleted { get; set; }
public long? CreateId { get; set; }
public string CreateBy { get; set; }
public DateTime? CreateTime { get; set; }
public int? ModifyId { get; set; }
public string ModifyBy { get; set; }
public DateTime? ModifyTime { get; set; }
}
2.配置通用返回信息类MessageModel
/// <summary>
/// 通用返回信息类
/// </summary>
public class MessageModel<T>
{
/// <summary>
/// 状态码
/// </summary>
public int status { get; set; } = 200;
/// <summary>
/// 操作是否成功
/// </summary>
public bool success { get; set; } = false;
/// <summary>
/// 返回信息
/// </summary>
public string msg { get; set; } = "";
/// <summary>
/// 开发者信息
/// </summary>
public string msgDev { get; set; }
/// <summary>
/// 返回数据集合
/// </summary>
public T response { get; set; }
/// <summary>
/// 返回成功
/// </summary>
/// <param name="msg">消息</param>
/// <returns></returns>
public static MessageModel<T> Success(string msg)
{
return Message(true, msg, default);
}
/// <summary>
/// 返回成功
/// </summary>
/// <param name="msg">消息</param>
/// <param name="response">数据</param>
/// <returns></returns>
public static MessageModel<T> Success(string msg, T response)
{
return Message(true, msg, response);
}
/// <summary>
/// 返回失败
/// </summary>
/// <param name="msg">消息</param>
/// <returns></returns>
public static MessageModel<T> Fail(string msg)
{
return Message(false, msg, default);
}
/// <summary>
/// 返回失败
/// </summary>
/// <param name="msg">消息</param>
/// <param name="response">数据</param>
/// <returns></returns>
public static MessageModel<T> Fail(string msg, T response)
{
return Message(false, msg, response);
}
/// <summary>
/// 返回消息
/// </summary>
/// <param name="success">失败/成功</param>
/// <param name="msg">消息</param>
/// <param name="response">数据</param>
/// <returns></returns>
public static MessageModel<T> Message(bool success, string msg, T response)
{
return new MessageModel<T>() { msg = msg, response = response, success = success };
}
}
public class MessageModel
{
/// <summary>
/// 状态码
/// </summary>
public int status { get; set; } = 200;
/// <summary>
/// 操作是否成功
/// </summary>
public bool success { get; set; } = false;
/// <summary>
/// 返回信息
/// </summary>
public string msg { get; set; } = "";
/// <summary>
/// 返回数据集合
/// </summary>
public object response { get; set; }
}
3.接口API地址信息表Modules
/// <summary>
/// 接口API地址信息表
/// </summary>
public class Modules : RootEntityTkey<long>
{
public long ParentId { get; set; }
/// <summary>
///获取或设置是否禁用,逻辑上的删除,非物理删除
/// </summary>
[SugarColumn(IsNullable = true)]
public bool? IsDeleted { get; set; }
/// <summary>
/// 名称
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string Name { get; set; }
/// <summary>
/// 菜单链接地址
/// </summary>
[SugarColumn(Length = 100, IsNullable = true)]
public string LinkUrl { get; set; }
/// <summary>
/// 区域名称
/// </summary>
[SugarColumn(Length = 2000, IsNullable = true)]
public string Area { get; set; }
/// <summary>
/// 控制器名称
/// </summary>
[SugarColumn(Length = 2000, IsNullable = true)]
public string Controller { get; set; }
/// <summary>
/// Action名称
/// </summary>
[SugarColumn(Length = 2000, IsNullable = true)]
public string Action { get; set; }
/// <summary>
/// 图标
/// </summary>
[SugarColumn(Length = 100, IsNullable = true)]
public string Icon { get; set; }
/// <summary>
/// 菜单编号
/// </summary>
[SugarColumn(Length = 10, IsNullable = true)]
public string Code { get; set; }
/// <summary>
/// 排序
/// </summary>
public int OrderSort { get; set; }
/// <summary>
/// /描述
/// </summary>
[SugarColumn(Length = 100, IsNullable = true)]
public string Description { get; set; }
/// <summary>
/// 是否是右侧菜单
/// </summary>
public bool IsMenu { get; set; }
/// <summary>
/// 是否激活
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// 创建ID
/// </summary>
[SugarColumn(IsNullable = true)]
public long? CreateId { get; set; }
/// <summary>
/// 创建者
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string CreateBy { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// 修改ID
/// </summary>
[SugarColumn(IsNullable = true)]
public long? ModifyId { get; set; }
/// <summary>
/// 修改者
/// </summary>
[SugarColumn(Length = 100, IsNullable = true)]
public string ModifyBy { get; set; }
/// <summary>
/// 修改时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime? ModifyTime { get; set; } = DateTime.Now;
//public virtual Module ParentModule { get; set; }
//public virtual ICollection<Module> ChildModule { get; set; }
//public virtual ICollection<ModulePermission> ModulePermission { get; set; }
//public virtual ICollection<RoleModulePermission> RoleModulePermission { get; set; }
}
4.配置Token信息视图模型TokenInfoViewModel
public class TokenInfoViewModel
{
public bool success { get; set; }
public string token { get; set; }
public double expires_in { get; set; }
public string token_type { get; set; }
}
5.配置按钮跟权限关联表RoleModulePermission类
/// <summary>
/// 按钮跟权限关联表
/// </summary>
public class RoleModulePermission : RootEntityTkey<long>
{
public long RoleId { get; set; }
/// <summary>
/// 接口ID
/// </summary>
public long ModuleId { get; set; }
/// <summary>
/// 菜单前端 ID
/// </summary>
[SugarColumn(IsNullable = true)]
public long PermissionId { get; set; }
/// <summary>
///获取或设置是否禁用,逻辑上的删除,非物理删除
/// </summary>
[SugarColumn(IsNullable = true)]
public bool? IsDeleted { get; set; }
/// <summary>
/// 创建ID
/// </summary>
[SugarColumn(IsNullable = true)]
public long? CreateId { get; set; }
/// <summary>
/// 创建者
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string CreateBy { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime? CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// 修改ID
/// </summary>
[SugarColumn(IsNullable = true)]
public long? ModifyId { get; set; }
/// <summary>
/// 修改者
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string ModifyBy { get; set; }
/// <summary>
/// 修改时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime? ModifyTime { get; set; } = DateTime.Now;
// 下边三个实体参数,只是做传参作用,所以忽略下
[SugarColumn(IsIgnore = true)]
public Role Role { get; set; }
[SugarColumn(IsIgnore = true)]
public Modules Module { get; set; }
}
6.重新配置Model层UserVo类
public class UserVo
{
public long Id { get; set; }
public string UserName { get; set; }
public string LoginName { get; set; }
public string LoginPWD { get; set; }
public string RealName { get; set; }
public int Status { get; set; } = 0;
public long DepartmentId { get; set; } = -1;
public string Remark { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
public DateTime UpdateTime { get; set; } = DateTime.Now;
public DateTime CriticalModifyTime { get; set; } = DateTime.Now;
public DateTime LastErrorTime { get; set; } = DateTime.Now;
public int ErrorCount { get; set; } = 0;
public string Name { get; set; }
public int Sex { get; set; } = 0;
public int Age { get; set; }
public DateTime Birth { get; set; } = DateTime.Now;
public string Address { get; set; }
public bool Enable { get; set; } = true;
public bool IsDeleted { get; set; } = false;
public long TenantId { get; set; }
public List<string> RoleNames { get; set; }
public List<long> Dids { get; set; }
public string DepartmentName { get; set; }
public List<long> RIDs { get; set; }
}
7.IBaseRepository和BaseRepository添加方法

Task<List<TEntity>> Query(Expression<Func<TEntity, bool>> whereExpression);
Task<List<TResult>> QueryMuch<T, T2, T3, TResult>(Expression<Func<T, T2, T3, object[]>> joinExpression, Expression<Func<T, T2, T3, TResult>> selectExpression, Expression<Func<T, T2, T3, bool>> whereLambda = null) where T : class, new();

public async Task<List<TEntity>> Query(Expression<Func<TEntity, bool>> whereExpression = null)
{
await Console.Out.WriteLineAsync(Db.GetHashCode().ToString());
return await _db.Queryable<TEntity>().WhereIF(whereExpression != null, whereExpression).ToListAsync();
}
public async Task<List<TResult>> QueryMuch<T, T2, T3, TResult>(
Expression<Func<T, T2, T3, object[]>> joinExpression,
Expression<Func<T, T2, T3, TResult>> selectExpression,
Expression<Func<T, T2, T3, bool>> whereLambda = null) where T : class, new()
{
if (whereLambda == null)
{
return await _db.Queryable(joinExpression).Select(selectExpression).ToListAsync();
}
return await _db.Queryable(joinExpression).Where(whereLambda).Select(selectExpression).ToListAsync();
}
8.配置IUserRepository和UserRepository
public interface IUserRepository
{
Task<List<SysUserInfo>> Query();
Task<List<RoleModulePermission>> RoleModuleMaps();
}
public class UserRepository : BaseRepository<SysUserInfo>, IUserRepository
{
public UserRepository(IUnitOfWorkManage unitOfWorkManage) : base(unitOfWorkManage)
{
}
public async Task<List<SysUserInfo>> Query()
{
await Task.CompletedTask;
var data = "[{\"Id\": 18,\"Name\":\"laozhang\"}]";
return JsonConvert.DeserializeObject<List<SysUserInfo>>(data) ?? new List<SysUserInfo>();
}
public async Task<List<RoleModulePermission>> RoleModuleMaps()
{
return await QueryMuch<RoleModulePermission, Modules, Role, RoleModulePermission>(
(rmp, m, r) => new object[] {
JoinType.Left, rmp.ModuleId == m.Id,
JoinType.Left, rmp.RoleId == r.Id
},
(rmp, m, r) => new RoleModulePermission()
{
Role = r,
Module = m,
IsDeleted = rmp.IsDeleted
},
(rmp, m, r) => rmp.IsDeleted == false && m.IsDeleted == false && r.IsDeleted == false
);
}
}
9.IBaseService和BaseService添加方法

/// <summary>
/// 根据表达式查询信息
/// </summary>
/// <param name="whereExpression"></param>
/// <returns></returns>
Task<List<TVo>> Query(Expression<Func<TEntity, bool>>? whereExpression = null);

/// <summary>
/// 根据表达式查询
/// </summary>
/// <param name="whereExpression"></param>
/// <returns></returns>
public async Task<List<TVo>> Query(Expression<Func<TEntity, bool>>? whereExpression = null)
{
var entities = await _baseRepository.Query(whereExpression);
Console.WriteLine($"_baseRepository 实例HashCode : {_baseRepository.GetHashCode()}");
var llout = _mapper.Map<List<TVo>>(entities);
return llout;
}
10.在IUserService继承IBaseService并添加方法,UserService实现方法

public interface IUserService : IBaseService<SysUserInfo, UserVo>
{
Task<bool> TestTranPropagation();
Task<string> GetUserRoleNameStr(string loginName, string loginPwd);
Task<List<RoleModulePermission>> RoleModuleMaps();
}
11.配置JWTToken生成类
/// <summary>
/// JWTToken生成类
/// </summary>
public class JwtToken
{
/// <summary>
/// 获取基于JWT的Token
/// </summary>
/// <param name="claims">需要在登陆的时候配置</param>
/// <param name="permissionRequirement">在startup中定义的参数</param>
/// <returns></returns>
public static TokenInfoViewModel BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement)
{
var now = DateTime.Now;
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sdfsdfsrty45634kkhllghtdgdfss345t678fs"));
// 实例化JwtSecurityToken
var jwt = new JwtSecurityToken(
issuer: "Blog.Core",
audience: "wr",
claims: claims,
notBefore: now,
expires: DateTime.Now.AddSeconds(3600),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
);
// 生成 Token
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
//打包返回前台
var responseJson = new TokenInfoViewModel
{
success = true,
token = encodedJwt,
expires_in = 3600,
token_type = "Bearer"
};
return responseJson;
}
}
12.重新配置【许可要求】
/// <summary>
/// 用户或角色或其他凭据实体,就像是订单详情一样
/// 之前的名字是 Permission
/// </summary>
public class PermissionItem
{
/// <summary>
/// 用户或角色或其他凭据名称
/// </summary>
public virtual string Role { get; set; }
/// <summary>
/// 请求Url
/// </summary>
public virtual string Url { get; set; }
}
public class PermissionRequirement : IAuthorizationRequirement
{
public List<PermissionItem> Permissions { get; set; } = new List<PermissionItem>();
}
public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>, IAuthorizationRequirement
{
private readonly IHttpContextAccessor _accessor;
private readonly IUserService _userService;
public IAuthenticationSchemeProvider Schemes { get; set; }
public PermissionRequirementHandler(IAuthenticationSchemeProvider schemes,
IHttpContextAccessor accessor,
IUserService userService)
{
Schemes = schemes;
_accessor = accessor;
_userService = userService;
}
public List<PermissionItem> Permissions { get; set; }
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var httpContext = _accessor.HttpContext;
// 获取系统中所有的角色和菜单的关系集合
if (!requirement.Permissions.Any())
{
var data = await _userService.RoleModuleMaps();
var list = new List<PermissionItem>();
list = (from item in data
where item.IsDeleted == false
orderby item.Id
select new PermissionItem
{
Url = item.Module?.LinkUrl,
Role = item.Role?.Name.ObjToString(),
}).ToList();
requirement.Permissions = list;
}
if (httpContext != null)
{
var questUrl = httpContext.Request.Path.Value.ToLower();
// 整体结构类似认证中间件UseAuthentication的逻辑,具体查看开源地址
// https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs
httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = httpContext.Request.Path,
OriginalPathBase = httpContext.Request.PathBase
});
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
// 是否开启测试环境
var isTestCurrent = AppSettings.app(new string[] { "AppSettings", "UseLoadTest" }).ObjToBool();
//result?.Principal不为空即登录成功
if (result?.Principal != null || isTestCurrent)
{
if (!isTestCurrent) httpContext.User = result.Principal;
//应该要先校验用户的信息 再校验菜单权限相关的
SysUserInfo user = new();
// 判断token是否过期,过期则重新登录
var isExp = false;
// jwt
isExp = (httpContext.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.Expiration)
?.Value) != null &&
DateTime.Parse(httpContext.User.Claims
.FirstOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now;
if (!isExp)
{
context.Fail(new AuthorizationFailureReason(this, "授权已过期,请重新授权"));
return;
}
// 获取当前用户的角色信息
var currentUserRoles = new List<string>();
currentUserRoles = (from item in httpContext.User.Claims
where item.Type == ClaimTypes.Role
select item.Value).ToList();
if (!currentUserRoles.Any())
{
currentUserRoles = (from item in httpContext.User.Claims
where item.Type == "role"
select item.Value).ToList();
}
//超级管理员 默认拥有所有权限
//if (currentUserRoles.All(s => s != "SuperAdmin"))
{
var isMatchRole = false;
var permisssionRoles =
requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role));
foreach (var item in permisssionRoles)
{
try
{
if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl)
{
isMatchRole = true;
break;
}
}
catch (Exception)
{
// ignored
}
}
//验证权限
if (currentUserRoles.Count <= 0 || !isMatchRole)
{
context.Fail();
return;
}
}
context.Succeed(requirement);
return;
}
}
if ((!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
{
context.Fail();
return;
}
}
}
}
12.在program.cs文件中提供单例注册【许可要求】

// 自定义授权需求
builder.Services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
// 注册许可要求
builder.Services.AddSingleton(new PermissionRequirement());
13.通过autofac注册IUserRepository和UserRepository服务

// 注册IUserRepository和UserRepository服务
builder.RegisterType(typeof(UserRepository)).As(typeof(IUserRepository)).InstancePerDependency();
14.配置LoginController控制器
/// <summary>
/// 登录管理
/// </summary>
[Produces("application/json")]
[Route("api/[controller]/[action]")]
public class LoginController : ControllerBase
{
private readonly IUserService _userService;
private readonly IBaseService<UserRole, UserRoleVo> _userRoleService;
readonly PermissionRequirement _requirement;
private readonly ILogger<LoginController> _logger;
/// <summary>
/// 构造函数注入
/// </summary>
/// <param name="sysUserInfoServices"></param>
/// <param name="userRoleServices"></param>
/// <param name="roleServices"></param>
/// <param name="requirement"></param>
/// <param name="roleModulePermissionServices"></param>
/// <param name="logger"></param>
public LoginController(
IUserService userService,
IBaseService<UserRole, UserRoleVo> userRoleService,
PermissionRequirement requirement,
ILogger<LoginController> logger)
{
_userService = userService;
_userRoleService = userRoleService;
_requirement = requirement;
_logger = logger;
}
[HttpGet]
public async Task<MessageModel<TokenInfoViewModel>> GetJwtToken3(string name = "", string pass = "")
{
string jwtStr = string.Empty;
pass = MD5Encrypt32(pass);
var user = await _userService.Query(d => d.LoginName == name && d.LoginPWD == pass && d.IsDeleted == false);
if (user.Count > 0)
{
var userRoles = await _userService.GetUserRoleNameStr(name, pass);
//如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, name),
new Claim(JwtRegisteredClaimNames.Jti, user.FirstOrDefault().Id.ToString()),
new Claim("TenantId", user.FirstOrDefault().TenantId.ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.DateToTimeStamp()),
new Claim(ClaimTypes.Expiration,
DateTime.Now.AddSeconds(3600).ToString())
};
claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
var token = JwtToken.BuildJwtToken(claims.ToArray(), _requirement);
return Success(token, "获取成功");
}
else
{
return Failed<TokenInfoViewModel>("认证失败");
}
}
private MessageModel<T> Success<T>(T data, string msg = "成功")
{
return new MessageModel<T>()
{
success = true,
msg = msg,
response = data,
};
}
private MessageModel<T> Failed<T>(string msg = "失败", int status = 500)
{
return new MessageModel<T>()
{
success = false,
status = status,
msg = msg,
response = default,
};
}
private string MD5Encrypt32(string password = "")
{
string pwd = string.Empty;
try
{
if (!string.IsNullOrEmpty(password) && !string.IsNullOrWhiteSpace(password))
{
MD5 md5 = MD5.Create(); //实例化一个md5对像
// 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择
byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
// 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得
foreach (var item in s)
{
// 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符
pwd = string.Concat(pwd, item.ToString("X2"));
}
}
}
catch
{
throw new Exception($"错误的 password 字符串:【{password}】");
}
return pwd;
}
}
15.配置控制器UserController
/// <summary>
/// 用户管理
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
[Authorize("Permission")]
public class UserController : ControllerBase
{
private readonly ILogger<UserController> _logger;
public UserController(ILogger<UserController> logger)
{
_logger = logger;
}
// GET: api/User
[HttpGet]
public string Get(int page = 1, string key = "")
{
_logger.LogInformation(key, page);
return "OK!!!";
}
// GET: api/User/5
[HttpGet("{id}")]
public string Get(string id)
{
_logger.LogError("test wrong");
return "value";
}
}

浙公网安备 33010602011771号