You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6基于Claims和Policy的角色权限授权访问异常排查

ASP.NET Core 6授权异常排查:已认证用户权限匹配仍被拒绝

在ASP.NET Core 6项目中搭建了角色权限管理系统,支持为角色分配应用权限,通过Claims和Policy实现授权控制。但出现异常:用户完成认证且持有正确角色、权限Claims的情况下,访问受保护资源仍跳转到“访问被拒绝”页面。

相关代码

权限常量定义

using System.Collections.Generic;

namespace TheGospelMission.Constants
{
    public static class Permissions
    {
        public static List<string> GeneratePermissionsForModule(string module)
        {
            return new List<string>()
            {
                $"Permission.{module}.All",
                $"Permission.{module}.Create",
                $"Permission.{module}.View",
                $"Permission.{module}.Edit",
                $"Permission.{module}.Delete",
            };
        }

        public static class Admin
        {
            public const string All = "Permission.Admin.All";
            public const string View = "Permission.Admin.View";
            public const string Create = "Permission.Admin.Create";
            public const string Edit = "Permission.Admin.Edit";
            public const string Delete = "Permission.Admin.Delete";
        }

        public static class Roles
        {
            public const string All = "Permission.Admin.All";
        }

        public static class Church
        {
            public const string View = "Permission.Church.View";
            public const string Create = "Permission.Church.Create";
            public const string Edit = "Permission.Church.Edit";
            public const string Delete = "Permission.Church.Delete";
        }

        public static class Members
        {
            public const string View = "Permission.Members.View";
            public const string Create = "Permission.Members.Create";
            public const string Edit = "Permission.Members.Edit";
            public const string Delete = "Permission.Members.Delete";
        }

        public static class Groups
        {
            public const string View = "Permission.Groups.View";
            public const string Create = "Permission.Groups.Create";
            public const string Edit = "Permission.Groups.Edit";
            public const string Delete = "Permission.Groups.Delete";
        }

        public static class Attendances
        {
            public const string View = "Permission.Attendances.View";
            public const string Create = "Permission.Attendances.Create";
            public const string Edit = "Permission.Attendances.Edit";
            public const string Delete = "Permission.Attendances.Delete";
        }
    }
}

ClaimsHelper类

using Microsoft.AspNetCore.Identity;
using TheGospelMission.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;

namespace TheGospelMission.Helpers
{
    public static class ClaimsHelper
    {
        public static void GetPermissions(this List<RoleClaimsViewModel> allPermissions, Type policy, string roleId)
        {
            FieldInfo[] fields = policy.GetFields(BindingFlags.Static | BindingFlags.Public);

            foreach (FieldInfo fi in fields)
            {
                allPermissions.Add(new RoleClaimsViewModel { Value = fi.GetValue(null).ToString(), Type = "Permissions" });
            }
        }

        public static async Task AddPermissionClaim(this RoleManager<IdentityRole> roleManager, IdentityRole role, string permission)
        {
            var allClaims = await roleManager.GetClaimsAsync(role);

            if (!allClaims.Any(a => a.Type == "Permission" && a.Value == permission))
            {
                await roleManager.AddClaimAsync(role, new Claim("Permission", permission));
            }
        }
    }
}

授权处理类

using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace TheGospelMission.Permission
{
    internal class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
    {
        private readonly ILogger<PermissionAuthorizationHandler> _logger;

        public PermissionAuthorizationHandler(ILogger<PermissionAuthorizationHandler> logger)
        {
            _logger = logger;
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            try
            {
                if (context.User == null)
                {
                    return;
                }

                _logger.LogInformation("User Claims:");

                foreach (var claim in context.User.Claims)
                {
                    _logger.LogInformation($"{claim.Type}: {claim.Value}");
                }

                var permissions = context.User.Claims.Where(x =>
                    x.Type == "Permissions" &&
                    x.Value == requirement.Permission &&
                    x.Issuer == "LOCAL AUTHORITY");

                if (permissions.Any())
                {
                    context.Succeed(requirement);
                    return;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error during permission authorization");
            }

            context.Fail(); // Authorization failed
        }
    }
}

using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;

namespace TheGospelMission.Permission
{
    internal class PermissionPolicyProvider : IAuthorizationPolicyProvider
    {
        private readonly ILogger<PermissionPolicyProvider> _logger;

        public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }

        public PermissionPolicyProvider(IOptions<AuthorizationOptions> options, ILogger<PermissionPolicyProvider> logger)
        {
            _logger = logger;
            // ASP.NET Core中只能有一个策略提供程序
            // 我们仅处理权限相关策略,其余使用默认提供程序
            FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
        }

        public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync();

        // 动态创建包含权限要求的策略,策略名称需匹配所需权限
        public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
        {
            if (policyName.StartsWith("Permission", StringComparison.OrdinalIgnoreCase))
            {
                var policy = new AuthorizationPolicyBuilder();
                policy.AddRequirements(new PermissionRequirement(policyName));
                var createdPolicy = policy.Build();

                _logger.LogInformation($"创建策略 '{policyName}': {string.Join(", ", createdPolicy.Requirements.Select(r => r.GetType().Name))}");
                return Task.FromResult<AuthorizationPolicy?>(policy.Build());
            }

            // 非权限类策略,使用默认提供程序处理
            return FallbackPolicyProvider.GetPolicyAsync(policyName);
        }

        public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
        {
            return Task.FromResult<AuthorizationPolicy?>(null);
        }
    }
}

using Microsoft.AspNetCore.Authorization;

namespace TheGospelMission.Permission
{
    internal class PermissionRequirement : IAuthorizationRequirement
    {
        public string Permission { get; private set; }

        public PermissionRequirement(string permission)
        {
            Permission = permission;
        }
    }
}

日志信息

info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      User Claims:
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: User Claims:
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: d66790ae-e8ea-4e09-a142-ea76def0199e
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: d66790ae-e8ea-4e09-a142-ea76def0199e
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: TestUser
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: TestUser
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: tt@mail.com
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: tt@mail.com
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: AspNet.Identity.SecurityStamp: P2KUBBQ6NCGLXTQKBDDQVRUQ3QVQS6WW
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      AspNet.Identity.SecurityStamp: P2KUBBQ6NCGLXTQKBDDQVRUQ3QVQS6WW
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      Permission: Permission.Members.View
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: Permission: Permission.Members.View
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      Permission: Permission.Members.Create
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: Permission: Permission.Members.Create
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      Permission: Permission.Members.View
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: Permission: Permission.Members.View
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      http://schemas.microsoft.com/ws/2008/06/identity/claims/role: Group Leader
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: http://schemas.microsoft.com/ws/2008/06/identity/claims/role: Group Leader
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: Permission: Permission.Members.Create
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      Permission: Permission.Members.Create
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      Permission: Permission.Members.View
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: Permission: Permission.Members.View
info: TheGospelMission.Permission.PermissionAuthorizationHandler[0]
      amr: pwd
TheGospelMission.Permission.PermissionAuthorizationHandler: Information: amr: pwd 

问题排查与解决方案

对比代码和日志,核心问题有两个:

  1. Claim Type拼写不一致

    • ClaimsHelper.AddPermissionClaim方法中添加的权限Claim Type是"Permission"(单数)
    • PermissionAuthorizationHandler中判断的Claim Type是"Permissions"(复数)
    • 日志显示用户持有的权限Claim Type为"Permission",与Handler中的判断条件不匹配

    修复方式:统一Claim Type的拼写,将Handler中的判断改为x.Type == "Permission",或者将ClaimsHelper中的Type改为"Permissions",确保全项目一致。

  2. Issuer判断条件不符合实际情况

    • Handler中添加了x.Issuer == "LOCAL AUTHORITY"的判断,但日志中未显示任何Claim的Issuer为该值(默认ASP.NET Identity的Claim Issuer可能不是这个值,且日志未打印Issuer信息),导致匹配失败
    • 修复方式:移除Issuer判断条件,或者确认添加Claim时指定的Issuer并修改判断逻辑。

修改后的PermissionAuthorizationHandler.HandleRequirementAsync核心判断代码:

var permissions = context.User.Claims.Where(x =>
    x.Type == "Permission" && // 统一为单数
    x.Value == requirement.Permission);

if (permissions.Any())
{
    context.Succeed(requirement);
    return;
}
  1. 额外检查点
    • 确认在Program.cs中已注册授权服务:
      builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
      builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
      
    • 清除浏览器缓存或重新登录,确保新的Claim生效。

内容的提问来源于stack exchange,提问作者Timmy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 02:00:54