TenantBusinessContextHelper.cs
4.68 KB
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Yi.Framework.Rbac.Domain.Shared.Consts;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace FoodLabeling.Application.Helpers;
/// <summary>
/// SaaS 业务租户上下文判定(平台主库 / Default 租户不走公司菜单子集过滤)。
/// </summary>
public static class TenantBusinessContextHelper
{
private static readonly Guid ProtectedDefaultTenantId = Guid.Parse("11111111-1111-1111-1111-111111111111");
/// <summary>
/// 解析当前请求的业务租户 Id:优先 <see cref="ICurrentTenant"/>,其次 JWT / Authorization Claim。
/// </summary>
public static Guid? ResolveBusinessTenantId(ICurrentTenant? currentTenant, HttpContext? httpContext)
{
if (currentTenant?.Id is { } fromContext
&& fromContext != Guid.Empty
&& fromContext != ProtectedDefaultTenantId)
{
return fromContext;
}
var fromJwt = TryGetTenantIdFromHttpContext(httpContext);
if (fromJwt.HasValue
&& fromJwt.Value != Guid.Empty
&& fromJwt.Value != ProtectedDefaultTenantId)
{
return fromJwt;
}
return null;
}
/// <summary>
/// 当前请求是否处于真实公司业务租户上下文(须按 fl_th_tenant_menu_permission 限定可分配菜单)。
/// </summary>
public static bool ShouldScopeMenusByCompany(
DbConnOptions options,
ICurrentTenant? currentTenant,
HttpContext? httpContext)
{
return ShouldScopeMenusByCompany(options, ResolveBusinessTenantId(currentTenant, httpContext));
}
/// <summary>
/// 当前请求是否处于真实公司业务租户上下文(须按 fl_th_tenant_menu_permission 限定可分配菜单)。
/// </summary>
public static bool ShouldScopeMenusByCompany(DbConnOptions options, Guid? tenantId)
{
return options.EnabledSaasMultiTenancy
&& tenantId.HasValue
&& tenantId.Value != Guid.Empty
&& tenantId.Value != ProtectedDefaultTenantId;
}
/// <summary>
/// 从 HttpContext Principal 或 Authorization Bearer JWT 读取 TenantId Claim 原始值。
/// </summary>
public static string? TryGetTenantIdClaimValue(HttpContext? httpContext)
{
if (httpContext is null)
{
return null;
}
return TryGetTenantIdFromPrincipal(httpContext.User)
?? TryGetTenantIdFromAuthorizationHeader(httpContext.Request.Headers.Authorization.ToString());
}
private static Guid? TryGetTenantIdFromHttpContext(HttpContext? httpContext)
{
var tenantClaim = TryGetTenantIdClaimValue(httpContext);
if (string.IsNullOrWhiteSpace(tenantClaim)
|| !Guid.TryParse(tenantClaim, out var tenantGuid)
|| tenantGuid == Guid.Empty)
{
return null;
}
return tenantGuid;
}
private static string? TryGetTenantIdFromPrincipal(ClaimsPrincipal? user)
{
if (user?.Identity?.IsAuthenticated != true)
{
return null;
}
return user.FindFirst(TokenTypeConst.TenantId)?.Value
?? user.FindFirst(AbpClaimTypes.TenantId)?.Value
?? user.Claims.FirstOrDefault(c =>
c.Type.EndsWith("tenantId", StringComparison.OrdinalIgnoreCase)
|| c.Type.Equals("tenantid", StringComparison.OrdinalIgnoreCase))?.Value;
}
/// <summary>
/// 多租户中间件可能早于 JWT Principal 就绪;直接从 Authorization 解析 TenantId Claim。
/// </summary>
private static string? TryGetTenantIdFromAuthorizationHeader(string? authorization)
{
if (string.IsNullOrWhiteSpace(authorization))
{
return null;
}
const string bearerPrefix = "Bearer ";
if (!authorization.StartsWith(bearerPrefix, StringComparison.OrdinalIgnoreCase))
{
return null;
}
var jwt = authorization[bearerPrefix.Length..].Trim();
if (string.IsNullOrWhiteSpace(jwt))
{
return null;
}
try
{
var token = new JwtSecurityTokenHandler().ReadJwtToken(jwt);
return token.Claims.FirstOrDefault(c =>
c.Type == TokenTypeConst.TenantId
|| c.Type == AbpClaimTypes.TenantId
|| c.Type.EndsWith("tenantId", StringComparison.OrdinalIgnoreCase))
?.Value;
}
catch (Exception)
{
return null;
}
}
}