ASP.NET Core 8.0 JWT安全实践:从认证到授权的全链路实现

发布于:2025-05-02 ⋅ 阅读:(8) ⋅ 点赞:(0)

一、认证与授权的区别

1、概念核心差异‌


认证(Authentication)‌

验证用户身份的过程,确认用户是否合法且“是否是他所声称的人”。例如用户通过用户名密码登录系统时,系统会验证凭证的有效性。

  • 在.NET中,认证的底层实现通常与 IIdentity 接口相关,该接口存储用户的基本身份信息(如用户名)。
  • 常见的认证方式包括:Cookie 认证、JWT(JSON Web Tokens)、OAuth 2.0 等。

授权(Authorization)‌

确定用户是否有权限访问特定资源或执行操作。例如,管理员登录后可管理系统设置,而普通用户只能查看基础数据。

  • 在.NET中,授权依赖于 IPrincipal 接口,该接口包含用户的角色或权限信息。
  • 实现方式包括:基于角色的授权、策略授权、声明(Claims)授权等。

2、功能阶段划分‌


认证是前置条件‌

用户必须先通过认证(如登录成功)后,系统才进一步处理授权逻辑。例如,ASP.NET Core 中的认证中间件会先验证请求中的身份标识(如 Cookie 或 Token),若无效则直接拒绝访问。


授权是后续决策‌

认证成功后,系统根据用户身份(角色、权限等)动态控制其对资源的访问权限。例如,通过 [Authorize(Roles = “Admin”)] 属性限制只有管理员角色才能访问某个接口。

3、技术实现差异‌

维度‌ 认证(Authentication)‌ 授权(Authorization)‌
核心接口‌ ‌IIdentity(存储用户身份信息) ‌ IPrincipal(存储用户角色/权限信息)
‌典型场景‌ ‌用户登录、Token 签发 ‌权限校验、资源访问控制
‌配置方式‌ ‌通过 AddAuthentication 配置认证方案 ‌ 通过 AddAuthorization 定义策略或角色要求

4、实际应用示例‌

  • 认证示例‌
    用户通过表单提交用户名密码,系统验证后生成 Cookie 或 JWT,并返回给客户端。
  • 授权示例‌
    已认证的用户访问管理页面时,系统检查其角色是否为“Admin”,若否则返回 403 错误。

总结

认证解决“你是谁”,授权解决“你能做什么”。在.NET中,二者通过不同接口和中间件分离实现,但需协同工作以构建安全的应用程序。

二、认证(Authenication)

1、创建项目

dotnet new webapi -controllers -f net8.0

在这里插入图片描述

2、安装 Nuget 包

# Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer -v 8.0.7

在这里插入图片描述

3、生成秘钥

我们需要一个密钥用于加密和验证 JWT,可以使用使用 RandomNumberGenerator 类生成随机字节。

using System;
using System.Security.Cryptography;

// 使用示例:
var secretKey = JwtKeyGenerator.GenerateSecureSecret();
Console.WriteLine($"Generated SecretKey: {secretKey}");

public class JwtKeyGenerator
{
    public static string GenerateSecureSecret(int keyLengthInBytes = 32)
    {
        // 生成加密安全的随机字节数组(推荐256位即32字节)
        byte[] keyBytes = new byte[keyLengthInBytes];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(keyBytes);
        }

        // 将字节数组转换为 Base64 字符串(更紧凑且适合配置)
        return Convert.ToBase64String(keyBytes);
    }
}

Generated SecretKey: pEZADfw6uJe8MOAozDoaAuXC+/ATW4XysMtQwH7wFmw=

4、配置信息

appsettings.json 中新增节点信息:

  "JwtConfig": {
    "SecretKey": "pEZADfw6uJe8MOAozDoaAuXC+/ATW4XysMtQwH7wFmw=",
    "Audience": "test",
    "Issuer": "test", 
    "Expired": 5 
  }

在这里插入图片描述

5、JwtConfig

新增 JwtConfig 对象类,用于读取配置文件信息:

using System.Text;
using Microsoft.IdentityModel.Tokens;

namespace JwtDemo.Config
{
    public class JwtConfig
    {
        /// <summary>
        /// 原始密钥
        /// </summary>
        public string? SecretKey { get; set; }

        /// <summary>
        /// 接收者
        /// </summary>
        public string? Issuer { get; set; }

        /// <summary>
        /// 生效时间(分钟)
        /// </summary>
        public int Expired { get; set; }

        /// <summary>
        /// 颁发者
        /// </summary>
        public string? Audience { get; set; }

        /// <summary>
        /// 生效时间
        /// </summary>
        public DateTime NoteBeofre => DateTime.Now;

        /// <summary>
        /// 过期时间
        /// </summary>
        public DateTime Expiration => DateTime.Now.AddMinutes(Expired);

        /// <summary>
        /// 安全密钥
        /// </summary>
        public SecurityKey SignKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));

        /// <summary>
        /// 签名凭证
        /// </summary>
        public SigningCredentials SigningCredentials => new SigningCredentials(SignKey, SecurityAlgorithms.HmacSha256);
    }

}

6、JwtHelper

新增 JwtHelper 工具类,主要用于生成和解析 Token:

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using JwtDemo.Config;

namespace JwtDemo.Helper
{
    public class JwtHelper
    {
        private JwtConfig _jwtConfig;

        public JwtHelper(JwtConfig config)
        {
            _jwtConfig = config;
        }

        public string GetAccessToken(string userId,string password)
        {
            var claims = new List<Claim>() {
                new Claim("UserId", userId),
                new Claim("Password", password)
            };
            var jwtSecurityToken = new JwtSecurityToken(
                    issuer: _jwtConfig.Issuer,
                    audience: _jwtConfig.Audience,
                    claims,
                    notBefore: _jwtConfig.NoteBeofre,
                    expires: _jwtConfig.Expiration,
                    signingCredentials: _jwtConfig.SigningCredentials
                );
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);

            return token;
        }

        public List<Claim> GetClaims(HttpRequest request)
        {
            var authorization = request.Headers["Authorization"].ToString();
            var auth = authorization.Split(" ")[1];
            var handler = new JwtSecurityTokenHandler();
            //反解密,获取其中的Claims
            var payload = handler.ReadJwtToken(auth).Payload;
            var claims = payload.Claims;
            return claims.ToList();
        }
    }

}

7、JwtService

在这里针对 services 新增一个扩展方法 JwtService,用于注册 Jwt 服务:

using JwtDemo.Config;
using JwtDemo.Helper;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace JwtDemo.Service
{
    public static class JwtService
    {
        public static void AddJwtService(this IServiceCollection services, ConfigurationManager config)
        {
            var jwtConfig = new JwtConfig();
            // 新增 JwtConfig 节点信息与 JwtConfig对象类做绑定,方便读取数据。
            config.Bind("JwtConfig", jwtConfig);
            services.AddSingleton(jwtConfig);

            // 将 JwtHelper 工具类注册为单例模式
            services.AddSingleton<JwtHelper>();

            services.AddAuthentication(option =>
            {
                // 认证配置
                option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {

                    ValidIssuer = jwtConfig.Issuer,             // 发行人
                    ValidAudience = jwtConfig.Audience,         // 订阅人
                    IssuerSigningKey = jwtConfig.SignKey,       // SecurityKey
                    ValidateLifetime = true,                    // 验证Token有效期
                    ValidateIssuer = true,                      // 是否验证Issure
                    ValidateAudience = true,                    // 是否验证 Audience
                    ValidateIssuerSigningKey = true,            // 是否验证SecurityKey
                    RequireExpirationTime = true,               // 是否验证失效时间
                    ClockSkew = TimeSpan.FromSeconds(30) // 缓冲时间,默认5分钟
                };
            });
        }
    }

}

8、调用中间件

// Program.cs

// ...
// JWT 认证服务
builder.Services.AddJwtService(builder.Configuration);
// ...

// 先认证
app.UseAuthentication();
// 再授权
app.UseAuthorization();

9、AccountController

using JwtDemo.DTO;
using JwtDemo.Helper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace JwtDemo.Controllers
{

    [ApiController]
    [Route("api/[controller]")]
    public class AccountController : ControllerBase
    {
        private readonly JwtHelper _jwtHelper;
        public AccountController(JwtHelper jwtHelper)
        {
            _jwtHelper = jwtHelper;
        }

        [HttpPost("login")]
        public IResult Login(AccountDTO dto)
        {
            if (string.IsNullOrEmpty(dto.UserId) || string.IsNullOrEmpty(dto.Password))
            {
                return TypedResults.BadRequest("用户名或密码为空");
            }

            var token = _jwtHelper.GetAccessToken(dto.UserId,dto.Password);
            return TypedResults.Ok(token);
        }

        [HttpGet("test")]
        [Authorize]
        public IResult Test()
        {
            return TypedResults.Ok("success");
        }

    }
}

10、认证测试

访问 http://localhost:5076/swagger/index.html ,使用 Swagger 进行测试,

在这里插入图片描述

可以看到,使用账号密码登录,成功返回 Token。

在这里插入图片描述

当密码为 null 时返回报错 “用户名或密码为空”

在这里插入图片描述

不携带 Token 访问 http://localhost:5076/api/Account/test 时,报错 401 Error: Unauthorized

在这里插入图片描述

携带 Token 访问时,返回成功。

11、从 Token 中解析信息

直接通过 Request 属性调用:

namespace JwtDemo.Controllers
{

    [ApiController]
    [Route("api/[controller]")]
    public class AccountController : ControllerBase
    {
        // ...
        
        [HttpGet("getClaims")]
        [Authorize]
        public IResult GetClaims()
        {
            var claims = _jwtHelper.GetClaims(Request);
            // 将 claims 转换为简单对象返回
            var claimData = claims.Select(c => new { Type = c.Type, Value = c.Value });
            return TypedResults.Ok(claimData);
        }

    }
}

在这里插入图片描述

三、授权(Authorization)

1、基本属性

Authorize 允许经过身份验证的用户访问该组件,AllowAnoymous允许未身份验证的用户访问单个操作。如果这两个属性结合使用,系统会忽略 Authorize 属性。

授权规则可以是 Roles(角色)、Policy(策略)、AuthenticationSchemes(方案):

[Authorize(Roles = "", Policy = "", AuthenticationSchemes = "")]

2、Roles 角色授权

基于角色授权,用户拥有这个角色,就能通过授权验证。在认证时给用户添加相应的 Claim,即可表示该用户拥有该角色,一个用户可以拥有多个角色。

namespace JwtDemo.Helper
{
    public class JwtHelper
    {
        // ...
        
        public string GetAccessToken(string userId,string[] userRoles)
        {
            var claims = new List<Claim>() {
                new Claim("UserId", userId)
            };

            // 逐个添加角色到 Claim
            foreach (var role in userRoles) {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }

            var jwtSecurityToken = new JwtSecurityToken(
                    issuer: _jwtConfig.Issuer,
                    audience: _jwtConfig.Audience,
                    claims,
                    notBefore: _jwtConfig.NoteBeofre,
                    expires: _jwtConfig.Expiration,
                    signingCredentials: _jwtConfig.SigningCredentials
                );
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);

            return token;
        }

        // ...
}

AccountController 中配置角色授权,

using JwtDemo.DTO;
using JwtDemo.Helper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace JwtDemo.Controllers
{

    [ApiController]
    [Route("api/[controller]")]
    public class AccountController : ControllerBase
    {
        private readonly JwtHelper _jwtHelper;

        public AccountController(JwtHelper jwtHelper)
        {
            _jwtHelper = jwtHelper;
        }

        [HttpPost("login")]
        public IResult Login(AccountDTO dto)
        {
            if (string.IsNullOrEmpty(dto.UserId) || string.IsNullOrEmpty(dto.Password))
            {
                return TypedResults.BadRequest("用户名或密码为空");
            }

            var token = _jwtHelper.GetAccessToken(dto.UserId, new string[] {"user"});
            return TypedResults.Ok(token);
        }

        [HttpGet("test1")]
        [Authorize]
        public IResult Test1()
        {
            return TypedResults.Ok("Test1");
        }

        [HttpGet("test2")]
        [Authorize(Roles = "user")]
        public IResult Test2()
        {
            return TypedResults.Ok("Roles = user");
        }

        [HttpGet("test3")]
        [Authorize(Roles = "admin")]
        public IResult Test3()
        {
            return TypedResults.Ok("Roles = admin");
        }

        [HttpGet("getClaims")]
        [Authorize(Roles = "admin,user")]
        public IResult GetClaims()
        {
            var claims = _jwtHelper.GetClaims(Request);
            // 将 claims 转换为简单对象返回
            var claimData = claims.Select(c => new { Type = c.Type, Value = c.Value });
            return TypedResults.Ok(claimData);
        }

    }
}

运行效果:

在这里插入图片描述

Test1 接口没有配置授权方式,因此只需要认证之后就能访问。

在这里插入图片描述

Test2 接口配置了角色 user 能够访问。

在这里插入图片描述

Test3 接口配置了角色 admin 能够访问,因此用户角色 user 尝试访问该接口的时候会返回 403 Forbidden 禁止访问。

在这里插入图片描述

getClaims 接口配置了角色 adminuser 都能够访问。

3、Policy 策略授权

一个 Policy 可以包含一个或多个要求,可以是 Roles 匹配,也可以是 Claims 匹配等。

services.AddAuthorization(options => {
    options.AddPolicy("policy1", x => x.RequireClaim("Employee"));
    options.AddPolicy("policy2", x => x.RequireClaim("Employee", "p1", "p2", "p3"));
});

在这里插入图片描述

以上代码案例,添加了两个 policy

  • policy1,要求 Cliams 必须包含一个Employee
  • policy2,要求 Claims 必须包含一个 Employee 且值必须要 p1p2p3 其中的一个。

在控制器上或者方法上添加属性 Authorize 即可生效:

namespace JwtDemo.Controllers
{

    [ApiController]
    [Route("api/[controller]")]
    public class AccountController : ControllerBase
    {
       
        // ...
        
        [Authorize(Policy = "policy1")]
        [HttpGet("test4")]
        public IResult Test4()
        {
            return TypedResults.Ok("Policy = policy1");
        }
        
        [Authorize(Policy = "policy2")]
        [HttpGet("test5")]
        public IResult Test5()
        {
            return TypedResults.Ok("Policy = policy2");
        }

        // ...

    }
}

在这里插入图片描述

运行效果:

在这里插入图片描述

当 Token 不携带 Employee 声明时,返回 403 Forbidden 。此时修改 Helper/JwtHelper.cs 中的 GetAccessToken 方法,添加 Employee 声明:

namespace JwtDemo.Helper
{
    public class JwtHelper
    {
        // ...
        
        public string GetAccessToken(string userId, string[] userRoles,string employeeId)
        {
            var claims = new List<Claim>() {
                new Claim("UserId", userId),
                new Claim("Employee", employeeId)
            };

            // 逐个添加角色到 Claim
            foreach (var role in userRoles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }

            var jwtSecurityToken = new JwtSecurityToken(
                    issuer: _jwtConfig.Issuer,
                    audience: _jwtConfig.Audience,
                    claims,
                    notBefore: _jwtConfig.NoteBeofre,
                    expires: _jwtConfig.Expiration,
                    signingCredentials: _jwtConfig.SigningCredentials
                );
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);

            return token;
        }

        // ...
        
    }

}

Login 方法对应修改:

namespace JwtDemo.Controllers
{

    [ApiController]
    [Route("api/[controller]")]
    public class AccountController : ControllerBase
    {

        // ...
        
        [HttpPost("login")]
        public IResult Login(AccountDTO dto)
        {
            if (string.IsNullOrEmpty(dto.UserId) || string.IsNullOrEmpty(dto.Password))
            {
                return TypedResults.BadRequest("用户名或密码为空");
            }

            var token = _jwtHelper.GetAccessToken(dto.UserId, ["user"],"p1");
            return TypedResults.Ok(token);
        }

        // ...
    }
}

在这里插入图片描述

可以看到,当 Token 携带 Employee 声明时,访问接口正常。

在这里插入图片描述

当 Token 携带 Employee 声明的值在策略范围内,访问接口正常。

在这里插入图片描述

在这里插入图片描述

[Authorize(Policy = "policy2", Roles = "user")]

采用角色+策略混合授权方式,也是能够正常访问接口。

4、基于策略 Policy 的自定义授权

在配置中调用 AddPolicy 来注册策略,在某些情况下,可能无法采用此方式注册所有策略,在这些情况下,可以使用自定义 IAuthorizationPolicyProvider 来控制如何提供授权策略,比如在运行时使用外部数据源中的信息策略确定授权要求。授权的主要方式,是通过 Handler 程序判断授权,需要实现 IAuthorizationHandler 接口。

实现步骤如下:

  • 自定义一个 Requirement 实现 IAuthorizationRequirement
  • 自定义 Handler 程序继承 AuthorizationHandler<TRequirement> 并重写 HandleRequirementAsync 方法。
  • 实现策略提供的 PolicyProvider 来动态添加策略。

Step1、实现 AuthorizeAttribute

新增属性,继承 AuthorizeAttribute 为我们添加额外属性来增加自定义授权,属性 Policy 不可为 null

using Microsoft.AspNetCore.Authorization;

namespace JwtDemo.Attribute
{
    public class MyPermissionAttribute : AuthorizeAttribute
    {
        const string _permission = "_auth_permission:";
        public string? Permission
        {
            get
            {
                return Permission;
            }
            set
            {
                // Policy 为必须值,在为per更改时,与Policy同步。
                // 如果为null,自定义的策略则不会响应。
                Policy = _permission + value?.ToString();
            }
        }
        /// <summary>
        /// 参数构造
        /// </summary>
        /// <param name="permission"></param>
        public MyPermissionAttribute(string permission)
        {
            Permission = permission;
        }
        /// <summary>
        /// 无参构造
        /// </summary>
        public MyPermissionAttribute()
        {
        }

    }
}

Step2、实现 Requirement

using Microsoft.AspNetCore.Authorization;

namespace JwtDemo.Impl
{
    public class PermissionRequirement : IAuthorizationRequirement
    {
        /// <summary>
        /// 权限名
        /// </summary>
        public string? Permission { get; set; }
        /// <summary>
        /// 参数构造
        /// </summary>
        /// <param name="permission"></param>
        public PermissionRequirement(string permission) => this.Permission = permission; 
    }
}

Step3、实现策略提供程序 PolicyProvider

通过配置的方式添加策略不灵活,无法动态添加。通过实现IAuthorizationPolicyProvider 并添加到 DI 中,可实现动态 Policy

IAuthorizationPolicyProvider 默认实现为DefaultAuthorizationPolicyProvider

实现一个 PolicyProvider 代码如下:

using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

namespace JwtDemo.Impl
{

    public class MyPermissionPolicyProvider : DefaultAuthorizationPolicyProvider, IAuthorizationPolicyProvider
    {

        /// <summary>
        /// 策略名称前缀
        /// </summary>
        private const string POLICY_PREFIX = "_auth_permission:";

        // 只能使用一个授权策略,如果自定义实现不处理所有策略,他应该退回到默认实现(在这里继承DefaultAuthorizationPolicyProvider,使用原有的实现。)
        public MyPermissionPolicyProvider(IOptions<AuthorizationOptions> options) : base(options)
        {
        }

        public async new Task<AuthorizationPolicy> GetDefaultPolicyAsync()
        {
            return await base.GetDefaultPolicyAsync();
        }

        public async new Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
        {
            return await base.GetFallbackPolicyAsync();
        }
        

        // 通过字符串名称进行查找
        public new Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
        {

            if (policyName.StartsWith(POLICY_PREFIX))
            {
                var policy = new AuthorizationPolicyBuilder();
                var targetName = policyName.Substring(POLICY_PREFIX.Length);
                policy.AddRequirements(new PermissionRequirement(targetName));
                var build = policy.Build();

                if (build == null)
                {

                }
                
                return Task.FromResult(build);
            }

            // 如果名称与期望的格式不匹配,使用默认实现。
            return base.GetPolicyAsync(policyName);
        }
    }

}

注意:只有最后一个添加的 PolicyProvider 会生效,自定义 Provider 必须实现 IAuthorizationPolicyProvider,否则添加到 DI 不生效。

Step4、实现 Handler

运行 HandleRequirementAsync 时, 会将用户的 ClaimClaimTypeUserID 的项取出,检测是否有执行权限。

using JwtDemo.Impl;
using Microsoft.AspNetCore.Authorization;

namespace JwtDemo.Handler
{
    public class MyPermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
    {
        private ILogger<MyPermissionAuthorizationHandler> _logger;

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

        /// <summary>
        /// 为内测人员账号 123456 添加 test 接口的查询和添加权限
        /// </summary>
        Dictionary<string, List<string>> keys = new Dictionary<string, List<string>>() { { "123456", new List<string> { "test.get", "test.add" } } };

        // 检查上下文是否符合给全要求
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {

            var claim = context.User.Claims.FirstOrDefault(x => x.Type.Equals("UserId"));
            if (claim == null)
            {
                _logger.LogWarning("claims中未找到 Permission 相关权限。");
                return Task.CompletedTask;
            }

            // 权限判断,可以是外部数据,数据库等权限配置。
            if (!keys.ContainsKey(claim.Value))
            {
                _logger.LogWarning("claims中未找到匹配的人员相关权限。");
                context.Fail();
                return Task.CompletedTask;
            }

            var rule = keys[claim.Value].FirstOrDefault(x => x.Equals(requirement.Permission));
            if (rule == null)
            {
                _logger.LogWarning($"claims中未找到匹配的资源相关权限。Permission {requirement.Permission}");
                context.Fail();
                return Task.CompletedTask;
            }

            // 成功
            context.Succeed(requirement);

            // 失败
            //context.Fail();
            return Task.CompletedTask;
        }
    }

}

Step5、在 Action 或 Controller 中配置所需权限

namespace JwtDemo.Controllers
{

    [ApiController]
    [Route("api/[controller]")]
    public class AccountController : ControllerBase
    {
        // ...
        
        [Authorize(Policy = "policy2", Roles = "user")]
        [HttpGet("test6")]
        [MyPermission(Permission = "test.get")]
        public IResult Test6()
        {
            return TypedResults.Ok("Permission = test.get");
        }

        [Authorize(Policy = "policy2", Roles = "user")]
        [HttpGet("test7")]
        [MyPermission(Permission = "test.add")]
        public IResult Test7()
        {
            return TypedResults.Ok("Permission = test.add");
        }

        // ...

    }
}

在这里插入图片描述

Step6、启用自定义授权策略

Service/JwtService.cs 中,将自定义的 PolicyProviderHandler 添加到 DI 中:

services.AddSingleton<IAuthorizationPolicyProvider, MyPermissionPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, MyPermissionAuthorizationHandler>();

在这里插入图片描述

运行效果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以看到,使用账号 123456 访问自定义权限的接口 test6test7 都有权限,同时也能正常访问没有自定义权限的接口。

在这里插入图片描述

若此时新增接口 test8,使用自定义权限 test.edit,则不在指定的权限范围内,无法访问。

在这里插入图片描述

5、自定义 AuthorizationMiddleware

自定义 IAuthorizationMiddlewareResultHandler 处理授权结果,用于:

  • 返回自定义的响应
  • 增强默认质询或禁止响应。
using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;

namespace JwtDemo.Handler
{

    public class MyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
    {
        private readonly AuthorizationMiddlewareResultHandler
             DefaultHandler = new AuthorizationMiddlewareResultHandler();


        public async Task HandleAsync(
            RequestDelegate requestDelegate,
            HttpContext httpContext,
            AuthorizationPolicy authorizationPolicy,
            PolicyAuthorizationResult policyAuthorizationResult)
        {
            /* 官方示例
            // if the authorization was forbidden and the resource had specific requirements,
            // provide a custom response.
            if (Show404ForForbiddenResult(policyAuthorizationResult))
            {
                // Return a 404 to make it appear as if the resource does not exist.
                httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                return;
            }

            // Fallback to the default implementation.
            await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy,
                                   policyAuthorizationResult);*/

            if (!httpContext.User.Identity.IsAuthenticated)
            {
                httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                // 设置响应内容类型为 JSON
                httpContext.Response.ContentType = "application/json";
                // 响应内容
                await httpContext.Response.WriteAsync("{\"data\":{\"succeeded\":false,\"code\":401,\"message\":\"登录已过期,请重新登录\"}}");
                return;
            }

            if (!policyAuthorizationResult.Succeeded)
            {
                // 设置响应内容类型为 JSON
                httpContext.Response.ContentType = "application/json";
                // 响应内容
                await httpContext.Response.WriteAsync("{\"data\":{\"succeeded\":false,\"code\":403,\"message\":\"您没有权限操作\"}}");
                return;
            }

            await requestDelegate(httpContext);
        }

        // bool Show404ForForbiddenResult(PolicyAuthorizationResult policyAuthorizationResult)
        // {
        //     return policyAuthorizationResult.Forbidden &&
        //         policyAuthorizationResult.AuthorizationFailure.FailedRequirements.OfType<
        //                                                        Show404Requirement>().Any();
        // }
    }

    // public class Show404Requirement : IAuthorizationRequirement { }
}

注册中间件:

services.AddSingleton<IAuthorizationMiddlewareResultHandler, MyAuthorizationMiddlewareResultHandler>();

运行效果:

在这里插入图片描述

在这里插入图片描述

参考文档


网站公告

今日签到

点亮在社区的每一天
去签到