ASP.NET Core 标识(Identity)框架系列(三):在 ASP.NET Core Web API 项目中使用标识(Identity)框架进行身份验证

简介: ASP.NET Core 标识(Identity)框架系列(三):在 ASP.NET Core Web API 项目中使用标识(Identity)框架进行身份验证

前言:JWT实现登录的流程

  1. 客户端向服务器端发送用户名、密码等请求登录。
  2. 服务器端校验用户名、密码,如果校验成功,则从数据库中取出这个用户的ID、角色等用户相关信息。
  3. 服务器端采用只有服务器端才知道的密钥来对用户信息的 JSON 字符串进行签名,形成签名数据。
  4. 服务器端把用户信息的 JSON 字符串和签名拼接到一起形成JWT,然后发送给客户端。
  5. 客户端保存服务器端返回的 JWT,并且在客户端每次向服务器端发送请求的时候都带上这个 JWT。
  6. 每次服务器端收到浏览器请求中携带的 JWT 后,服务器端用密钥对JWT的签名进行校验,如果校验成功,服务器端则从 JWT 中的 JSON 字符串中读取出用户的信息。

Step By Step 步骤

  1. 创建一个 Asp.Net Core WebApi 项目
  2. 引用以下 Nuget 包:

Microsoft.AspNetCore.Authentication.JwtBearer

Microsoft.AspNetCore.Identity.EntityFrameworkCore

Microsoft.EntityFrameworkCore.SqlServer

Microsoft.EntityFrameworkCore.Tools

3.打开 appsettings.json 文件,配置数据库连接字符串和JWT的密钥、过期时间

{
  "Logging": {
  "LogLevel": {
    "Default": "Information",
    "Microsoft.AspNetCore": "Warning"
  }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
  "Default": "Server=(localdb)\\mssqllocaldb;Database=IdentityTestDB;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "JWT": {
  "SigningKey": "fasdfad&9045dafz222#fadpio@0232",
  "ExpireSeconds": "86400"
  }
}

4.创建JWT配置实体类 JWTOptions

public class JWTOptions
{
  public string SigningKey { get; set; }
  public int ExpireSeconds { get; set; }
}

5.打开 Program.cs 文件,在 builder.Build 之前,编写代码对 JWT 进行配置

// 注入 JWT 配置
services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
// 注入 JwtBearer 配置
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddJwtBearer(x => { 
    var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
    byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
    var secKey = new SymmetricSecurityKey(keyBytes);
    x.TokenValidationParameters = new()
    {
      ValidateIssuer = false,
      ValidateAudience = false,
      ValidateLifetime = true,
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = secKey
    };
  });

6.打开 Program.cs 文件,在 app.UseAuthorization 之前,添加身份验证中间件

// 使用 Authentication 中间件,放在 UseAuthorization 之前
app.UseAuthentication();

7.创建继承 IdentityRole 的 User 和 Role 实体类

using Microsoft.AspNetCore.Identity;
public class User: IdentityUser<long>
{
  public DateTime CreationTime { get; set; }
  public string? NickName { get; set; }
}
public class Role: IdentityRole<long>
{
}

8.创建继承 IdentityDbContext 的上下文类

using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
public class IdDbContext: IdentityDbContext<User, Role, long>
{
  public IdDbContext(DbContextOptions<IdDbContext> options) : base(options)
  {
  }
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    base.OnModelCreating(modelBuilder);
    modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
  }
}

9.(可选)如果数据表还没创建,执行数据库迁移命令

10.创建登录请求的参数实体类 LoginRequest

public record LoginRequest(string UserName, string Password);

11.打开登录请求控制器,编写 Login API,在其中创建 JWT

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace ASPNETCore_JWT1.Controllers
{
  [ApiController]
  [Route("[controller]/[action]")]
  public class Test1Controller : ControllerBase
  {
    private readonly UserManager<User> userManager;
    //注入 UserManager
    public Test1Controller(UserManager<User> userManager)
    {
      this.userManager = userManager;
    }
    // 生成 JWT
    private static string BuildToken(IEnumerable<Claim> claims, JWTOptions options)
    {
      DateTime expires = DateTime.Now.AddSeconds(options.ExpireSeconds);
      byte[] keyBytes = Encoding.UTF8.GetBytes(options.SigningKey);
      var secKey = new SymmetricSecurityKey(keyBytes);
      var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);
      var tokenDescriptor = new JwtSecurityToken(
        expires: expires, signingCredentials: 
        credentials, 
        claims: claims);
      var result = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor); 
      return result;
    }
    // 在方法中注入 IOptions<JWTOptions> 
    // 只需要返回 JWT Token 即可,其它的身份验证中间件会处理
    [HttpPost]
    public async Task<IActionResult> Login(
      LoginRequest req,
      [FromServices] IOptions<JWTOptions> jwtOptions)
    {
      string userName = req.UserName;
      string password = req.Password;
      var user = await userManager.FindByNameAsync(userName);
      if (user == null)
      {
        return NotFound($"用户名不存在{userName}");
      }
      if (await userManager.IsLockedOutAsync(user))
      {
        return BadRequest("LockedOut");
      }
      var success = await userManager.CheckPasswordAsync(user, password);
      if (!success)
      {
        return BadRequest("Failed");
      }
      
      var claims = new List<Claim>();
      claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
      claims.Add(new Claim(ClaimTypes.Name, user.UserName));
      var roles = await userManager.GetRolesAsync(user);
      foreach (string role in roles)
      {
        claims.Add(new Claim(ClaimTypes.Role, role));
      }
      var jwtToken = BuildToken(claims, jwtOptions.Value);
      return Ok(jwtToken);
    }
  }
}

12.打开其它控制器,在类上添加 [Authorize] 这个特性

using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
namespace ASPNETCore_JWT1.Controllers
{
  // [Authorize] 特性标识此控制器的方法需要身份授权才能访问
  // 授权中间件会处理其它的
  [ApiController]
  [Route("[controller]/[action]")]
  [Authorize]
  public class Test2Controller : Controller
  {
    [HttpGet]
    public IActionResult Hello()
    {
      // ControllerBase中定义的ClaimsPrincipal类型的User属性代表当前登录用户的身份信息
      // 可以通过ClaimsPrincipal的Claims属性获得当前登录用户的所有Claim信息
      // this.User.Claims
      
      string id = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;
      string userName = this.User.FindFirst(ClaimTypes.Name)!.Value;
      IEnumerable<Claim> roleClaims = this.User.FindAll(ClaimTypes.Role);
      string roleNames = string.Join(",", roleClaims.Select(c => c.Value));
      return Ok($"id={id},userName={userName},roleNames ={roleNames}");
    }
  }
}

13.打开 Program.cs 文件,配置 Swagger,支持发送 Authorization 报文头

// 配置 Swagger 支持 Authorization
builder.Services.AddSwaggerGen(c => {
  var scheme = new OpenApiSecurityScheme()
  {
    Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
    Reference = new OpenApiReference
    {
      Type = ReferenceType.SecurityScheme,
      Id = "Authorization"
    },
    Scheme = "oauth2",
    Name = "Authorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.ApiKey
  };
  c.AddSecurityDefinition("Authorization", scheme);
  var requirement = new OpenApiSecurityRequirement();
  requirement[scheme] = new List<string>();
  c.AddSecurityRequirement(requirement);
});

启动项目,进行测试

  1. 首先访问/Test1/Login,获取 JWT Token,复制下这个值
  2. 然后访问/Test2/Hello,不带 JWT Token,将收到 401 信息
  3. 在 Swagger 上的 Authorization 输入 JWT Token,重新访问/Test2/Hello,将返回正确的结果
  • 如果是在 Postman 等第三方,要在 Header 上加上参数 Authorization=bearer {JWT Token}

附录:完整的 Program 代码(重点注意代码中的注释

using Microsoft.AspNetCore.Identity;
using Microsoft.OpenApi.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Text;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
// 配置 Swagger 支持 Authorization
builder.Services.AddSwaggerGen(c => {
    var scheme = new OpenApiSecurityScheme()
    {
        Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
        Reference = new OpenApiReference
        {
            Type = ReferenceType.SecurityScheme,
            Id = "Authorization"
        },
        Scheme = "oauth2",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey
    };
    c.AddSecurityDefinition("Authorization", scheme);
    var requirement = new OpenApiSecurityRequirement();
    requirement[scheme] = new List<string>();
    c.AddSecurityRequirement(requirement);
});
var services = builder.Services;
// 注册数据库服务
services.AddDbContext<IdDbContext>(opt => {
    string connStr = builder.Configuration.GetConnectionString("Default")!;
    opt.UseSqlServer(connStr);
});
// 注册数据保护服务
services.AddDataProtection();
// 注册 IdentityCore 服务
services.AddIdentityCore<User>(options => { 
    options.Password.RequireDigit = false;
    options.Password.RequireLowercase = false;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireUppercase = false;
    options.Password.RequiredLength = 6;
    options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
    options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
});
// 注入UserManager、RoleManager等服务
var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services);
idBuilder.AddEntityFrameworkStores<IdDbContext>()
    .AddDefaultTokenProviders()
    .AddRoleManager<RoleManager<Role>>()
    .AddUserManager<UserManager<User>>();
// 注入 JWT 配置
services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
// 注入 JwtBearer 配置
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(x => { 
        var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
        byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
        var secKey = new SymmetricSecurityKey(keyBytes);
        x.TokenValidationParameters = new()
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = secKey
        };
    });
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// 使用 Authentication 中间件,放在 UseAuthorization 之前
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

总结

  1. 如果其中某个操作方法不想被验证,可以在这个操作方法上添加 [AllowAnonymous] 特性
  2. 对于客户端获得的 JWT,在前端项目中,可以把令牌保存到 Cookie、LocalStorage 等位置,从而在后续请求中重复使用,而对于移动App、PC客户端,可以把令牌保存到配置文件中或者本地文件数据库中。当执行【退出登录】操作的时候,我们只要在客户端本地把 JWT 删除即可。
  3. 在发送请求的时候,只要按照 HTTP 的要求,把 JWT 按照 “Bearer {JWT Token}” 格式放到名字为 Authorization 的请求报文头中即可
  4. 从 Authorization 中取出令牌,并且进行校验、解析,然后把解析结果填充到 User 属性中,这一切都是 ASP.NET Core 完成的,不需要开发人员自己编写代码


相关文章
|
4月前
|
存储 数据安全/隐私保护
.NET Core 究竟隐藏着怎样的神秘力量,能实现强身份验证与数据加密?
【8月更文挑战第28天】在数字化时代,数据安全与身份验证至关重要。.NET Core 提供了强大的工具,如 Identity 框架,帮助我们构建高效且可靠的身份验证系统,并支持高度定制化的用户模型和认证逻辑。此外,通过 `System.Security.Cryptography` 命名空间,.NET Core 还提供了丰富的加密算法和工具,确保数据传输和存储过程中的安全性。以下是一个简单的示例,展示如何使用 .NET Core 的 Identity 框架实现用户注册和登录功能。
42 3
|
21天前
|
开发框架 .NET 程序员
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
Autofac 是一个轻量级的依赖注入框架,专门为 .NET 应用程序量身定做,它就像是你代码中的 "魔法师",用它来管理对象的生命周期,让你的代码更加模块化、易于测试和维护
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
|
3月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
3月前
|
开发框架 .NET 中间件
ASP.NET Core Web 开发浅谈
本文介绍ASP.NET Core,一个轻量级、开源的跨平台框架,专为构建高性能Web应用设计。通过简单步骤,你将学会创建首个Web应用。文章还深入探讨了路由配置、依赖注入及安全性配置等常见问题,并提供了实用示例代码以助于理解与避免错误,帮助开发者更好地掌握ASP.NET Core的核心概念。
111 3
|
3月前
|
开发框架 前端开发 .NET
VB.NET中如何利用ASP.NET进行Web开发
在VB.NET中利用ASP.NET进行Web开发是一个常见的做法,特别是在需要构建动态、交互式Web应用程序时。ASP.NET是一个由微软开发的开源Web应用程序框架,它允许开发者使用多种编程语言(包括VB.NET)来创建Web应用程序。
67 5
|
4月前
|
UED 存储 自然语言处理
【语言无界·体验无疆】解锁Vaadin应用全球化秘籍:从代码到文化,让你的应用畅游世界每一个角落!
【8月更文挑战第31天】《国际化与本地化实战:构建多语言支持的Vaadin应用》详细介绍了如何使用Vaadin框架实现应用的国际化和本地化,提升用户体验和市场竞争力。文章涵盖资源文件的创建与管理、消息绑定与动态加载、日期和数字格式化及文化敏感性处理等方面,通过具体示例代码和最佳实践,帮助开发者构建适应不同语言和地区设置的Vaadin应用。通过这些步骤,您的应用将更加灵活,满足全球用户需求。
62 0
|
4月前
|
API 数据库 UED
全面解析构建高性能API的秘诀:运用Entity Framework Core与异步编程提升Web应用响应速度及并发处理能力的详细指南与实践案例
【8月更文挑战第31天】本文详细介绍了如何利用 Entity Framework Core (EF Core)的异步编程特性构建高性能 API。通过创建基于 EF Core 的 .NET Core Web API 项目,配置数据库上下文,并定义领域模型,文章展示了如何使用异步方法进行数据查询、加载相关实体及事务处理。具体代码示例涵盖了 GET、POST、PUT 和 DELETE 操作,全面展示了 EF Core 异步编程的优势,有助于提升 API 的响应速度和处理能力。
58 0
|
4月前
|
开发框架 监控 .NET
开发者的革新利器:ASP.NET Core实战指南,构建未来Web应用的高效之道
【8月更文挑战第28天】本文探讨了如何利用ASP.NET Core构建高效、可扩展的Web应用。ASP.NET Core是一个开源、跨平台的框架,具有依赖注入、配置管理等特性。文章详细介绍了项目结构规划、依赖注入配置、中间件使用及性能优化方法,并讨论了安全性、可扩展性以及容器化的重要性。通过这些技术要点,开发者能够快速构建出符合现代Web应用需求的应用程序。
70 0
|
4月前
|
开发框架 .NET 开发工具
【Azure 应用服务】App Service 的.NET Version选择为.NET6,是否可以同时支持运行ASP.NET V4.8的应用呢?
【Azure 应用服务】App Service 的.NET Version选择为.NET6,是否可以同时支持运行ASP.NET V4.8的应用呢?
|
4月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
56 0