ASP.NET Core 8.0 JWT认证和授权

1,认证(Authenication)

1.1 基本步骤

  1. 安装相关nuget包:Microsoft.AspNetCore.Authentication.JwtBearer
  2. 准备配置信息
  3. 注册服务
  4. 调用中间件
  5. 实现一个 JwtHelper用于生成Token
  6. 控制器限制访问(添加Authorize标签)

1.2 Nuget 包安装

在控制台中:

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

在包程序中:
image.png

1.3 配置信息

appsettings.json中新增节点信息:

 "JwtConfig": {
   "SecretKey": "jokdsp;fgjsiolepjhgfisroedghrdsoliughdrtiughrdikughdriughiulghrdeilug", // 密钥
   "Audience": "test",
   "Issuer": "test", // 颁发者
   "Expired": 1 // 过期时间(分钟)
 }

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

public class JwtConfig {
    /// <summary>
    /// 密钥Key
    /// </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>
    /// 加密后的密钥,使用SHA256
    /// </summary>
    public SigningCredentials SigningCredentials => new SigningCredentials(SignKey, SecurityAlgorithms.HmacSha256);
}

1.4 实现 JwtHelper 工具类

主要用于生成和解析 Token

using AuthWebApplication.Config;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;

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

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


        public string GetAccessToken () {
            var claims = new List<Claim>() { 
                new Claim("UserID", "123456"),
                new Claim("UserName", "Admin")
            };
            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();
        }
    }
}

1.5 注册服务和调用中间件

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

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分钟
            };
        });
    }
}

调用中间件,在Program.cs增加一下代码

// 要在授权之前认证,必须在身份认证中间件之前调用,比如(UserAuthorization)
app.UseAuthentication(); 
app.UseAuthorization();  // 授权

1.6 控制器配置

using AuthWebApplication.Helper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AuthWebApplication.Controllers {

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

        private readonly JwtHelper jwtHelper;
        public AccountController(JwtHelper jwtHelper) {
            this.jwtHelper = jwtHelper;
        }

        [HttpGet("login")]
        public Result Get() {
            
            var token = jwtHelper.GetAccessToken();
            return Result.Success(token);
        }

        [HttpGet("test")]
        [Authorize]
        public Result test () {
            return Result.Success("success");
        }

    }
}

1.7 测试调用

在这里使用ApiFox进行测试,在调用 /api/Account/login 生成 Token
image.png
在调用 /api/Account/GetTest 时传入 test,得到返回结果image.png

2,授权(Authorization)

授权是指用户可以执行那些操作,比如用户是否可以对文档的创建、编辑添加等。
授权(Authorization)认证(Authenication)是相互独立的

2.1 基本属性

Authorize允许经过身份验证的用户访问该组件,AllowAnoymous允许未身份验证的用户访问单个操作。如果 2 个属性结合使用,系统会忽略Authorize属性。
授权规则可以是 Roles(角色),Policy(策略),AuthenticationSchemes(方案)

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

2.2 授权方式

基本上授权只有:Policy、Role、Scheme 这3种方式,对应 Authorize 标签的3个属性。

2.2.1 Roles 角色授权

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

new Claim(ClaimTypes.Role, "admin"),
new Claim(ClaimTypes.Role, "user")

ControllerAction

[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "user")]
public class TestController : ControllerBase{

    private JwtHelper _jwtHelper;
    public TestController(JwtHelper jwtHelper) {  _jwtHelper = jwtHelper; }

    [Authorize(Roles = "admin")]    // 需要用户 user和admin两个角色才可以访问
    [HttpGet("get2")]
    public string Get2 () => "Get2";
    
    [Authorize(Roles = "admin,user")]   // 拥有 admin或user 其中一个角色就能访问
    [HttpGet("get1")]               
    public Result Get1 () => Result.Success("get1");
}
2.2.2 Policy 策略授权

一个Policy可以包含一个或多个要求,可以是Roles匹配,也可以是Claims匹配等。
下面是一个基础案例,添加两个policy

  • policy1,要求Cliams必须包含一个Employee
  • policy2,要求 Claim 必须包含一个 Employee 且值必须要 1,2 或 3 其中的一个。
services.AddAuthorization(options => {
    options.AddPolicy("policy1", x => x.RequireClaim("Employee"));
    options.AddPolicy("policy2", x => x.RequireClaim("Employee", "1", "2", "3"));
});

在控制器或Action上添加标签Authorize即可生效

[Authorize(Policy = "policy1")]
public class PolicyController : ControllerBase

2.3 基于策略 Policy 的自定义授权

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

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

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

public class PermissionAttribute  : AuthorizeAttribute{
    const string _permission = "_auth_permission:";
    public string? per { 
        get {
            return per;    
        } 
        set {
            // Policy 为必须值,在为per更改时,与Policy同步。
            // 如果为null,自定义的策略则不会响应。
            Policy = _permission + value?.ToString();
        } 
    }
    public PermissionAttribute(string per) {
        this.per = per;
    }
    public PermissionAttribute () {
    }
}
2.3.2 实现 Requirement
public class PermissionRequirement : IAuthorizationRequirement{
    public string? per { get; set; }
    public PermissionRequirement(string per) { this.per = per;}
}

属性 per 表示权限名

2.3.3 实现策略提供程序 PolicyProvider

通过配置的方式添加策略不灵活,无法动态添加。通过实现IAuthorizationPolicyProvider并添加到DI中,可实现动态Policy
IAuthorizationPolicyProvider默认实现为DefaultAuthorizationPolicyProvider
实现一个PolicyProvider代码如下:

public class PermissionPolicyProvider : DefaultAuthorizationPolicyProvider,IAuthorizationPolicyProvider {

    // 只能使用一个授权策略,如果自定义实现不处理所有策略,他应该退回到默认实现(在这里继承DefaultAuthorizationPolicyProvider,使用原有的实现。)
    public PermissionPolicyProvider (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("_auth_permission")){
            var policy = new AuthorizationPolicyBuilder();
            var targetName = policyName.Substring("_auth_permission:".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 不生效。

2.3.4 实现 Handler
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement> {
    private ILogger<PermissionAuthorizationHandler> _logger;

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

    Dictionary<string, List<string>> keys = new Dictionary<string, List<string>>() {
        // 为1123 添加test2的查询和添加权限
        {"1123", new List<string> {"test.get", "test.add"} }
    };

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

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

        // 权限判断,可以是外部数据,数据库等权限配置。
        if(!keys.ContainsKey(claim.Value)) {
            context.Fail();
            return Task.CompletedTask;
        }
        var rule = keys[claim.Value].FirstOrDefault(x => x == requirement.per);
        if(rule == null) {
            context.Fail();
            return Task.CompletedTask;
        }

        // 成功
        context.Succeed(requirement);
        // 失败
        //context.Fail();
        return Task.CompletedTask;
    }
}

运行HandleRequirementAsync时, 会将用户的 Claim 中 ClaimType 为 userId 的项取出,检测是否有执行权限。
在 Programe.cs 中,将自定义的 PolicyProvider 和 Handler 添加到DI中:

services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
2.3.5 在 Action 或 Controller 中
[ApiController]
[Route("api/[controller]")]
public class Test2Controller : ControllerBase{
    [HttpGet("get")]
    [Permission(per = "test.get")]
    public Result get() => Result.Success("success");

    [HttpGet("add")]
    [Permission(per = "test.add")]
    public Result add () => Result.Success("add");

    [HttpGet("edit")]
    [Permission(per = "test.edit")]
    public Result edit () => Result.Success("edit");
}
2.3.6 测试

在上述案例中,定义只可执行带有test.get``test.add标签的 Action。
image.png
image.png
未定义edit,在访问时会出现 403 未授权。
image.png

2.4 自定义AuthorizationMiddleware

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

  1. 返回自定义的响应
  2. 增强默认质询或禁止响应。
public class SampleAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
    private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();

    public async Task HandleAsync(
        RequestDelegate next,
        HttpContext context,
        AuthorizationPolicy policy,
        PolicyAuthorizationResult authorizeResult)
    {
        // If the authorization was forbidden and the resource had a specific requirement,
        // provide a custom 404 response.
        if (authorizeResult.Forbidden
            && authorizeResult.AuthorizationFailure!.FailedRequirements
                .OfType<Show404Requirement>().Any())
        {
            // Return a 404 to make it appear as if the resource doesn't exist.
            context.Response.StatusCode = StatusCodes.Status404NotFound;
            return;
        }

        // Fall back to the default implementation.
        await defaultHandler.HandleAsync(next, context, policy, authorizeResult);
    }

在 Program.cs 中注册 IAuthorizationMiddlewareResultHandler 的实现:

builder.Services.AddSingleton<
    IAuthorizationMiddlewareResultHandler, SampleAuthorizationMiddlewareResultHandler>();
  • 32
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值