JWT的简单使用—扩展(菜鸡随笔)

JWT的简单使用—扩展(菜鸡随笔)

除了基本的身份认证和授权方案,ASP.NET Core还提供了更高级、更全面的身份认证和授权方案,例如自定义身份认证和授权、多因素身份认证、声明式策略等。在本篇文章中,我们将介绍这些高级身份认证和授权方案的实现。

声明式策略

声明式策略是一种比较灵活的授权方案,它允许用户根据声明信息

对API端点或控制器进行访问控制。在ASP.NET Core中,可以使用策略(Policy)和声明(Claim)来实现声明式策略。

配置策略

要配置策略,需要在Startup.cs文件中的ConfigureServices方法中调用AddAuthorization方法,并使用AddPolicy方法添加策略。例如:

services.AddAuthorization(options => {
    options.AddPolicy("MyPolicy", policy => {
        policy.RequireClaim(ClaimTypes.Role, "Admin");
    });
});

在上面的代码中,我们首先使用AddAuthorization方法添加授权方案。然后,在AddPolicy方法中添加名为“MyPolicy”的策略,该策略要求用户必须具有Role声明并且值为“Admin”。

应用策略

要应用策略,可以在需要进行鉴权的API端点或控制器上,使用Authorize属性来应用策略。例如:

[HttpGet("my-action")]
[Authorize(Policy = "MyPolicy")]
public IActionResult MyAction() {
    return Ok("Hello World");
}

这样,只有满足“MyPolicy”策略的用户才能访问MyAction方法。

多因素身份认证

多因素身份认证是一种比较安全的身份认证方式,它需要用户提供多个验证因素来证明其身份。常见的验证因素包括密码、短信验证码、指纹等。

在ASP.NET Core中,可以使用AddAuthentication方法的AddScheme方法来实现多因素身份认证。例如:

// 添加身份验证服务
services.AddAuthentication(options =>
{
    options.DefaultScheme = MultiFactorDefaults.AuthenticationScheme;
}).AddScheme<MultiFactorOptions, MultiFactorHandler>(MultiFactorDefaults.AuthenticationScheme, configureOptions =>
{
    // 配置多因素身份认证选项
    configureOptions.Secret = Configuration["MultiFactor:Secret"];
    configureOptions.Issuer = Configuration["MultiFactor:Issuer"];
    configureOptions.Audience = Configuration["MultiFactor:Audience"];
});

// 添加授权策略服务
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
{
    policy.Requirements.Add(new RoleRequirement());
});

options.AddPolicy("TwoFactorEnabled", policy => { // 添加自定义的身份验证需求类 policy.Requirements.Add(new TwoFactorRequirement()); }); });services.AddSingleton<IAuthorizationHandler, RoleHandler>();
    services.AddSingleton<IAuthorizationHandler, TwoFactorHandler>();

    // 注册UserService服务
    services.AddScoped<IUserService, UserService>();

    // 注册HttpContextAccessor服务
    services.AddHttpContextAccessor();

    // 注册MVC服务
    services.AddControllersWithViews();
}

在上面的代码中,我们使用AddAuthentication方法添加认证方案,并指定默认的身份验证和挑战方案。

然后,在AddScheme方法中添加多因素身份认证方案,需要指定多因素身份认证方案的名称、多因素身份认证逻辑处理器、以及任何自定义参数。

在Controller或Action上应用授权策略

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "TwoFactorEnabled")]
public class HomeController : Controller
{
    private readonly IUserService _userService;
    public HomeController(IUserService userService)
    {
        _userService = userService;
    }

    public IActionResult Index()
    {
        var user = _userService.GetUser(User.Identity.Name);
        return View(user);
    }
}

在这个示例中,我们首先在ConfigureServices方法中添加了身份验证服务和授权策略服务。

在身份验证服务中,我们调用AddAuthentication方法来添加一个自定义的身份验证方案,并设置其为默认方案。

然后,使用AddScheme方法将MultiFactorOptions和MultiFactorHandler类型注册到服务容器中,以便在需要时能够进行注入。

在授权策略服务中,我们添加了"AdminOnly"和"TwoFactorEnabled"两个授权策略,分别表示只有具备"Admin"角色和启用了多因素身份认证的用户才能通过授权。

接着,我们使用AddSingleton方法将RoleHandler和TwoFactorHandler注册为授权处理程序,以便在授权过程中使用。

最后,在HomeController类中,我们将Authorize属性标记为需要"TwoFactorEnabled"授权策略才能访问。当用户尝试访问该Action时,它们必须满足TwoFactorRequirement,即必须启用了多因素身份认证才能通过授权。

配置自定义认证选项

配置自定义认证选项可以帮助我们更好地控制应用程序中的身份认证流程。例如,我们可以配置JWT Token的过期时间、签名密钥、颁发者等参数。

要配置自定义认证选项,需要创建一个类,并继承AuthenticationSchemeOptions

自定义身份认证和授权

在某些情况下,基本的身份认证和授权方案无法满足需求,需要自定义身份认证和授权方案。

在ASP.NET Core中,可以通过实现自定义认证处理程序(Authentication Handler)来实现自定义身份认证和授权方案。

实现自定义认证处理程序

using Azure.Core;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text.Encodings.Web;
public class MyAuthenticationHandler : AuthenticationHandler<MyAuthenticationOptions>
{
    private readonly IUserService _userService;

    public MyAuthenticationHandler(IOptionsMonitor<MyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserService userService) : base(options, logger, encoder, clock)
    {
        _userService = userService;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // 获取请求头中的Authorization信息,这里假设Authorization信息为“Bearer {Token}”
        string authorization = Request.Headers["Authorization"];

        if (string.IsNullOrEmpty(authorization) || !authorization.StartsWith("Bearer "))
        {
            return AuthenticateResult.Fail("Invalid Authorization header");
        }

        string token = authorization.Substring("Bearer ".Length).Trim();

        try
        {
            // 根据Token验证用户身份
            var user = await _userService.ValidateToken(token);

            if (user == null)
            {
                return AuthenticateResult.Fail("Invalid token");
            }

            // 创建ClaimsIdentity对象,并添加用户的声明信息
            var claims = new List<Claim> {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
                new Claim(ClaimTypes.Role, user.Role)
            };

            var identity = new ClaimsIdentity(claims, Scheme.Name);

            // 返回AuthenticateResult对象
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Failed to authenticate user");
            return AuthenticateResult.Fail("Authentication failed");
        }
    }
}


public static class MyAuthenticationDefaults
{
    public const string AuthenticationScheme = "MyAuthentication";
    public const string DisplayName = null;
    public const string ChallengeScheme = null;
}
public class MyAuthenticationOptions : AuthenticationSchemeOptions
{
    public string Secret { get; set; }
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public bool RequireHttpsMetadata { get; set; } = true;
    public int ClockSkewMinutes { get; set; } = 5;

    public MyAuthenticationOptions()
    {
        // 设置默认值
        AuthenticationScheme = MyAuthenticationDefaults.AuthenticationScheme;
        AutomaticAuthenticate = true;
        AutomaticChallenge = true;
    }
}

在构造函数中,我们注入了IUserService服务,用于验证用户的Token。HandleAuthenticateAsync方法首先从请求头中获取Authorization信息,并提取出Token部分。然后,使用_userService服务验证Token,并获取到对应的用户信息。如果验证失败或者用户不存在,则返回AuthenticateResult.Fail结果。

如果验证成功,则创建一个新的ClaimsIdentity对象并添加用户的声明信息,这些声明信息包括用户的ID、用户名和角色等。然后,使用AuthenticationTicket对象创建一个新的认证票据,并将其作为AuthenticateResult.Success结果返回。

MyAuthenticationDefaults类是一个静态类,其中包含了自定义身份验证方案的默认值,包括身份验证方案名称、显示名称和授权方案名称等。

MyAuthenticationOptions类是一个自定义选项的容器类,它包含了一些常见的选项,比如JWT的Secret、Issuer和Audience等,以及其他一些控制身份验证处理程序行为的选项。在构造函数中,我们设置了默认值,以确保这些选项在应用程序中始终存在并且可用。

注册自定义认证处理程序

要注册自定义认证处理程序,需要在Startup.cs文件中的ConfigureServices方法中调用AddAuthentication方法,并使用AddScheme方法添加自定义认证方案。例如:

services.AddAuthentication(options => {
    options.DefaultScheme = MyAuthenticationDefaults.AuthenticationScheme;
}).AddScheme<MyAuthenticationOptions, MyAuthenticationHandler>(MyAuthenticationDefaults.AuthenticationScheme, o => {
    o.TokenValidationParameters.ValidateIssuerSigningKey = true;
    o.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Secret"]));
    o.TokenValidationParameters.ValidateIssuer = true;
    o.TokenValidationParameters.ValidIssuer = Configuration["Jwt:Issuer"];
    o.TokenValidationParameters.ValidateAudience = true;
    o.TokenValidationParameters.ValidAudience = Configuration["Jwt:Audience"];
    o.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
});

在上面的代码中,我们通过o参数来设置自定义认证选项。具体来说,我们配置了TokenValidationParameters对象,用于验证JWT Token的签名、颁发者、接收者等信息。

其中,ValidateIssuerSigningKey、IssuerSigningKey、ValidateIssuer、ValidIssuer、ValidateAudience、ValidAudience和ClockSkew属性分别对应于TokenValidationParameters类中的相应属性,可以根据具体需求进行设置。

最后,在Configure方法中调用UseAuthentication方法启用身份验证中间件:

app.UseAuthentication();

这样,我们就可以使用自定义认证处理程序进行身份验证了。

自定义授权处理程序

添加一个自定义的身份验证需求类RoleRequirement和一个对应的授权处理程序RoleHandler。

public class RoleRequirement : IAuthorizationRequirement { }

public class RoleHandler : AuthorizationHandler<RoleRequirement> {
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement) {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.Role)) {
            return Task.CompletedTask;
        }
        
        var roles = context.User.FindAll(c => c.Type == ClaimTypes.Role).Select(c => c.Value);
        
        if (roles.Any(r => r == "Admin")) {
            context.Succeed(requirement);
        }
        
        return Task.CompletedTask;
    }
}

RoleRequirement类实现了IAuthorizationRequirement接口,这个接口是所有身份验证需求类必须实现的。在这个类中,我们没有添加任何属性或方法,因为它只是一个标识性的类,表示需要具备特定角色的用户才能通过授权。

RoleHandler类继承自AuthorizationHandler类,并实现了HandleRequirementAsync方法来处理授权需求。在这个方法中,我们首先检查用户是否具有ClaimTypes.Role声明。如果没有,则返回Task.CompletedTask,表示授权失败。

如果用户具有ClaimTypes.Role声明,则从context.User对象中获取到所有的角色信息,并使用LINQ查询语句筛选出其中是否有"Admin"角色。如果有,则表示授权成功,可以调用context.Succeed(requirement)方法。

最后,无论授权成功或失败,都要返回Task.CompletedTask表示授权结果已经处理完毕。

使用方式

services.AddAuthentication(MyAuthenticationDefaults.AuthenticationScheme)
    .AddMyAuthentication(options => Configuration.GetSection("MyAuthentication").Bind(options));

services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.Requirements.Add(new RoleRequirement());
    });
});

services.AddSingleton<IAuthorizationHandler, RoleHandler>();

首先调用AddAuthentication方法添加自定义的身份验证方案,并将其作为默认的身份验证方案。然后,使用AddMyAuthentication方法来配置自定义选项,具体实现可以参考我的另一个回答。

接着,调用AddAuthorization方法添加授权策略服务,并创建一个名为"AdminOnly"的授权策略。在这个策略中,我们添加了一个RoleRequirement,表示需要具备特定角色的用户才能通过授权。

最后,我们使用AddSingleton方法将RoleHandler注册为授权处理程序,以便在授权过程中使用。

在某个Controller或Action上应用授权策略:

[Authorize(Policy = "AdminOnly")]
public class AdminController : Controller
{
    // ...
}

在这个示例中,我们将AdminController类标记为需要"AdminOnly"授权策略才能访问。当用户尝试访问该Controller时,它们必须满足RoleRequirement,即必须具备"Admin"角色才能通过授权。

这就是使用RoleHandler进行授权的基本步骤。当用户的角色信息发生变化时,授权策略会自动重新计算,以确保授权结果的准确性。

应用自定义认证和授权

要应用自定义认证和授权,可以在需要进行鉴权的API端点或控制器上,使用Authorize属性来应用策略。例如:

[HttpGet("my-action")]
[Authorize(AuthenticationSchemes = MyAuthenticationDefaults.AuthenticationScheme)]
public IActionResult MyAction() {
    return Ok("Hello World");
}

这样,只有通过自定义认证方案并获得有效身份凭证的用户才能访问MyAction方法。

完善自定义认证处理程序

在上面的代码中,我们创建了一个名为MyAuthenticationHandler的类,并重写了HandleAuthenticateAsync方法,用于验证用户身份并返回用户身份信息。为了增加可读性和可维护性,我们可以将一些散乱的逻辑片段封装到一个单独的服务中,例如IUserService服务:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

public interface IUserService
{
    Task<User> ValidateToken(string token);
}

public class UserService : IUserService
{
    private readonly IConfiguration _configuration;

    public UserService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public async Task<User> ValidateToken(string token)
    {
        var secretKey = Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"]);
        var issuer = _configuration["Jwt:Issuer"];
        var audience = _configuration["Jwt:Audience"];

        var tokenHandler = new JwtSecurityTokenHandler();

        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(secretKey),
                ValidateIssuer = true,
                ValidIssuer = issuer,
                ValidateAudience = true,
                ValidAudience = audience,
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            var jwtToken = (JwtSecurityToken)validatedToken;

            return new User
            {
                Id = int.Parse(jwtToken.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value),
                Username = jwtToken.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value,
                Role = jwtToken.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value
            };
        }
        catch (Exception)
        {
            return null;
        }
    }
}

然后,在ConfigureServices方法中注册IUserService服务:

services.AddScoped<IUserService, UserService>();

在上面的代码中,我们创建了名为UserService的服务,并实现了ValidateToken方法,用于验证JWT Token并返回用户身份信息。

然后,在MyAuthenticationHandler中注入IUserService服务,并修改HandleAuthenticateAsync方法,使用IUserService服务来验证Token并返回用户身份信息:

结论

小小的补充,如有不足请多多指教,友善发言谢谢。

posted @ 2023-04-18 21:48  白日梦想家_zery  阅读(61)  评论(0编辑  收藏  举报