使用 JWT 进行 ASP.NET Core Web API 的身份验证与角色授权

发布:2024-09-20 10:28 阅读:32 点赞:0

引言

JSON Web Token (JWT) 是一种开放标准 (RFC 7519),定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息,信息以 JSON 对象的形式呈现。由于 JWT 是数字签名的,因此可以被验证和信任。JWT 可以使用密钥(HMAC 算法)或公钥/私钥对(RSA 或 ECDSA)进行签名。

在本文中,我们将讨论以下内容:

  • ASP.NET Core Web API 的身份验证
  • 使用 JWT 令牌
  • 基于角色的授权
  • 使用 ASP.NET 的低级身份验证功能,而不使用 ASP.NET Core Identity
  • 我们将遵循仓库模式进行实现

准备工作

在开始之前,请确保您已安装以下工具:

  • .NET Core 6 SDK
  • Visual Studio 2022
  • Postman

步骤实现

一. 创建新的 .NET Core 6.0 Web API 项目

  1. 打开 Visual Studio,选择“新建项目”。
  2. 选择“ASP.NET Core Web 应用”。
  3. 选择“API”模板,并确保目标框架设置为 .NET 6.0。

二. 项目结构

创建完成后,您的项目结构应该如下所示:

- Custom_Jwt_Token_Example
  - Controllers
  - Helper
  - Models
  - Services
  - appsettings.json

三. 安装 JWT 包

在项目中打开 NuGet 包管理控制台,输入以下命令安装 JWT 相关库:

Install-Package System.IdentityModel.Tokens.Jwt

四. 通过 Web API 端点进行用户身份验证

接下来,我们需要通过询问凭据来验证用户,生成令牌并将其返回给 API 客户端。这通常发生在控制器的操作方法或中间件端点处理程序中。

using Custom_Jwt_Token_Example.Models;
using Custom_Jwt_Token_Example.Services;
using Microsoft.AspNetCore.Mvc;

namespace Custom_Jwt_Token_Example.Controllers
{
    [Route("api/[controller]")]
    public class AuthenticationController : Controller
    {
        private readonly IAuthenticationService authenticationService;

        public AuthenticationController(IAuthenticationService authenticationService) {
            this.authenticationService = authenticationService;
        }

        [HttpPost]
        [Route("Login")]
        public AuthenticateResponse Login(AuthenticateRequest model)
        {
            return this.authenticationService.Authenticate(model);
        }
    }
}

五. 实现仓库接口

创建 IAuthentication 接口,作为身份验证功能的抽象层。

using Custom_Jwt_Token_Example.Models;

namespace Custom_Jwt_Token_Example.Services
{
    public interface IAuthenticationService
    {
        AuthenticateResponse Authenticate(AuthenticateRequest model);
    }
}

创建 IUserService 接口,作为用户功能的抽象层。

using Custom_Jwt_Token_Example.Models;

namespace Custom_Jwt_Token_Example.Services
{
    public interface IUserService
    {
        User GetById(int id);
        IEnumerable GetAll();
    }
}

六. 用户服务实现

使用 UserService 类的 GetById 方法来获取用户详细信息。

using Custom_Jwt_Token_Example.Models;

namespace Custom_Jwt_Token_Example.Services
{
    public class UserService : IUserService
    {
        private List _users = new List {
            new User {
                Id = 1, FirstName = "mytest", Role = new List{ Role.Customer }, LastName = "User", Username = "mytestuser", Password = "test123"
            },
            new User {
                Id = 2, FirstName = "mytest2", LastName = "User2", Username = "test", Password = "test"
            }
        };

        public IEnumerable GetAll()
        {
            return _users;
        }

        public User GetById(int id)
        {
            return _users.FirstOrDefault(x => x.Id == id);
        }
    }
}

七. 实现身份验证服务

创建 AuthenticationService 类来实现 IAuthenticationService 接口。

using Custom_Jwt_Token_Example.Helper;
using Custom_Jwt_Token_Example.Models;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace Custom_Jwt_Token_Example.Services
{
    public class AuthenticationService : IAuthenticationService
    {
        private List _users = new List {
            new User {
                Id = 1, FirstName = "mytest", LastName = "User", Username = "mytestuser", Role = new List{ Role.Customer }, Password = "test123"
            }
        };

        private readonly AppSettings _appSettings;

        public AuthenticationService(IOptions appSettings)
        {
            _appSettings = appSettings.Value;
        }

        public AuthenticateResponse Authenticate(AuthenticateRequest model)
        {
            var user = _users.SingleOrDefault(x => x.Username == model.UserName && x.Password == model.Password);
            if (user == nullreturn null;
            var token = generateToken(user);
            return new AuthenticateResponse() { Token = token };
        }

        private string generateToken(User user)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Key));
            var credential = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            List claims = new List()
            {
                new Claim("Id", Convert.ToString(user.Id)),
                new Claim(JwtRegisteredClaimNames.Sub, user.Username),
                new Claim(JwtRegisteredClaimNames.Email, "test@gmail.com"),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
            };

            foreach (var role in user.Role)
            {
                claims.Add(new Claim("Role", Convert.ToString(role)));
            }

            var token = new JwtSecurityToken(_appSettings.Issuer, _appSettings.Issuer, claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credential);

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
}

八. 创建中间件

接下来,我们创建一个中间件组件,用于从请求头中获取令牌,验证令牌并将其附加到用户上下文中。

using Custom_Jwt_Token_Example.Services;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;

namespace Custom_Jwt_Token_Example.Helper
{
    public class JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly AppSettings _appSettings;

        public JwtMiddleware(RequestDelegate next, IOptions appSettings)
        {
            this._next = next;
            this._appSettings = appSettings.Value;
        }

        public async Task Invoke(HttpContext context, IUserService userService)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
            if (token != null)
                attachUserToContext(context, userService, token);
            await _next(context);
        }

        private void attachUserToContext(HttpContext context, IUserService userService, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Key));
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateAudience = true,
                    ValidateIssuer = true,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = key,
                    ClockSkew = TimeSpan.Zero,
                    ValidIssuer = _appSettings.Issuer,
                    ValidAudience = _appSettings.Issuer
                }, out SecurityToken validateToken);

                var jwtToken = (JwtSecurityToken)validateToken;
                var userId = int.Parse(jwtToken.Claims.FirstOrDefault(_ => _.Type == "Id").Value);
                context.Items["User"] = userService.GetById(userId);
            }
            catch
            {
                // 处理异常
            }
        }
    }
}

九. 授权装饰器

在 API 端点上添加 [Authorization] 特性,每次调用 API 端点之前,都会调用 OnAuthorization 方法。

using Custom_Jwt_Token_Example.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Custom_Jwt_Token_Example.Helper
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class Authorization : AttributeIAuthorizationFilter
    {
        private readonly IList _roles;

        public Authorization(params Role[] roles)
        {
            this._roles = roles ?? new Role[] { };
        }

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var isRolePermission = false;
            User user = (User)context.HttpContext.Items["User"];
            if (user == null)
            {
                context.Result = new JsonResult(new { Message = "Unauthorized" })
                { StatusCode = StatusCodes.Status401Unauthorized };
            }
            else if (this._roles.Any())
           



 {
                foreach (var userRole in user.Role)
                {
                    foreach (var authRole in this._roles)
                    {
                        if (userRole == authRole)
                        {
                            isRolePermission = true;
                        }
                    }
                }

                if (!isRolePermission)
                    context.Result = new JsonResult(new { Message = "Unauthorized" })
                    { StatusCode = StatusCodes.Status401Unauthorized };
            }
        }
    }
}

十. 装饰 API 端点

使用 [Authorization(Role.Customer)] 特性装饰 API 端点。

using Custom_Jwt_Token_Example.Helper;
using Custom_Jwt_Token_Example.Models;
using Microsoft.AspNetCore.Mvc;

namespace Custom_Jwt_Token_Example.Controllers
{
    [Authorization(Role.Customer)]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[] {
            "Freezing""Bracing""Chilly""Cool""Mild""Warm""Balmy""Hot""Sweltering""Scorching"
        };

        private readonly ILogger _logger;

        public WeatherForecastController(ILogger logger)
        {
            _logger = logger;
        }

        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable Get()
        {
            return Enumerable.Range(15).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-2055),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

十一. 配置 Appsettings.json

在 appsettings.json 文件中添加 JWT 令牌密钥和发行者部分。

{
  "Logging": {
    "LogLevel": {
      "Default""Information",
      "Microsoft.AspNetCore""Warning"
    }
  },
  "AllowedHosts""*",
  "AppSettings": {
    "Key""986ghgrgtru989ASdsaerew13434545435",
    "Issuer""TestIssuer"
  }
}

十二. 添加中间件

最后,我们需要使用 Use<> 扩展方法注册自定义中间件。

using Custom_Jwt_Token_Example.Helper;
using Custom_Jwt_Token_Example.Services;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.Configure(builder.Configuration.GetSection("AppSettings"));
builder.Services.AddScoped();
builder.Services.AddScoped();

var app = builder.Build();
app.UseMiddleware();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

十三. 创建身份验证请求和响应类

创建 AuthenticateResponse 类用于令牌值设置和获取。

namespace Custom_Jwt_Token_Example.Models
{
    public class AuthenticateResponse
    {
        public string Token { getset; }
    }
}

创建 AuthenticateRequest 类,用于发送用户名和密码的身份验证请求。

using System.ComponentModel.DataAnnotations;

namespace Custom_Jwt_Token_Example.Models
{
    public class AuthenticateRequest
    {
        [Required]
        public string UserName { getset; }

        [Required]
        public string Password { getset; }
    }
}

十四. 创建角色枚举和用户类

创建一个角色枚举,用于存储管理员和客户用户。

namespace Custom_Jwt_Token_Example.Models
{
    public enum Role
    {
        Admin,
        Customer
    }
}

创建一个用户类,用于存储用户信息。

using System.Text.Json.Serialization;

namespace Custom_Jwt_Token_Example.Models
{
    public class User
    {
        public int Id { getset; }
        public string FirstName { getset; }
        public string LastName { getset; }
        public string Username { getset; }
        public List Role { getset; }
        
        [JsonIgnore]
        public string Password { getset; }
    }
}

十五. 创建 AppSettings 类

创建一个 AppSettings 类,用于存储密钥和发行者值。

namespace Custom_Jwt_Token_Example.Helper
{
    public class AppSettings
    {
        public string Key { getset; }
        public string Issuer { getset; }
    }
}

十六. 示例的运行效果说明

  1. 用户登录:用户通过 /api/authentication/login 端点发送用户名和密码进行身份验证,成功后返回 JWT 令牌。
  2. 访问受保护的资源:用户在访问受保护的 API(如 /weatherforecast)时,需要在请求头中包含 Bearer 令牌,成功后返回天气预报数据。
  3. 角色授权:根据用户角色(如 Admin 或 Customer)限制用户访问某些 API 端点,未授权的用户会收到 401 状态码的响应。

通过以上步骤,您将能够在 ASP.NET Core Web API 中成功实现 JWT 身份验证和基于角色的授权,从而提升应用程序的安全性和灵活性。