一种灵活的鉴权实现方式

主要的思路是使用DynamicExpresso.Core包实现的两个鉴权的自定义函数:hasRolehasAuth

  1. 主要程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public sealed class AuthExpressionEvaluator
{
private static readonly Interpreter Interpreter = new();

private readonly ILogger<AuthExpressionEvaluator> _logger;

/// <summary>
/// 自定义了两个用于实现自定义鉴权的dsl表达式,一个用于判断用户是否有相关角色,另一个用于判断用户是否有特定的权限,方便使用
/// 中间可以使用逻辑表达式进行连接
/// </summary>
/// <param name="httpContextAccessor"></param>
/// <param name="logger"></param>
public AuthExpressionEvaluator(IHttpContextAccessor httpContextAccessor, ILogger<AuthExpressionEvaluator> logger)
{
_logger = logger;
Interpreter.SetFunction("hasAuth", (string perm) =>
{
var httpContext = httpContextAccessor.HttpContext;
if (httpContext == null) return false;

var authSet = httpContext.Items["auths"] as IEnumerable<string>;
return authSet != null && authSet.Contains(perm);
});

Interpreter.SetFunction("hasRole", (string role) =>
{
var httpContext = httpContextAccessor.HttpContext;
if (httpContext == null) return false;

var roleSet = httpContext.Items["roles"] as IEnumerable<RoleNameVersionPairs>;
return roleSet != null && roleSet.Any(p => p.roleName == role);
});
}

public bool Eval(string expression)
{
try
{
return Interpreter.Eval<bool>(expression);
}
catch(Exception e)
{
_logger.LogError("{}", e.Message);
return false;
}
}
}
  1. 解释
  • 将用户的roles信息和auths(这个指细粒度的授权)信息存放到Items当中(这一步可以在前方定义一个中间件等)
  • 这个包生成出来的函数线程安全
  • 这个帮助类需要定义成单例,否则多线程访问的时候会造成线程不安全
  1. 使用方法
  • 自定义中间件,在中间件中获取自定义的注解,从注解中获取鉴权表达式然后计算
  • 简要实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var desc = GetAuthDescriptor(context);
if (desc is null)
{
await next(context);
return;
}

if (!context.User.Identity?.IsAuthenticated ?? true)
{
await WriteError(context, 403, "Forbidden");
return;
}

var authClaim = GetAuthClaims(context.User, AuthKey);
var roleClaim = GetRoleClaims(context.User, RoleKey);
var userIdClaim = GetUserIdClaim(context.User);

// 解析并放到 Items 中
context.Items[AuthKey] = authClaim;
context.Items[RoleKey] = roleClaim;

if (!EvaluateExpression(desc.AuthList))
{
await WriteError(context, 403, "Forbidden");
return;
}
await next(context);
}
  • 注意:此中间件放到Authentication中间件后,并且去掉Authorization中间件