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 完成的,不需要开发人员自己编写代码


相关文章
|
11月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
11月前
|
自然语言处理 算法 API
阿里云增值税发票识别NET Rest API调用示例
本文介绍了使用NET代码调用阿里云增值税发票识别API的实现方式。通过示例代码,详细展示了如何构造请求、设置签名以及发送HTTP请求的具体步骤。代码中涵盖了请求参数的处理、签名生成逻辑(如HMAC-SHA256算法)以及调用API后的结果处理。此外,还提供了运行结果的截图和参考文档链接,帮助开发者更好地理解和应用该接口。
1353 4
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
952 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
|
人工智能 Java API
ai-api-union项目,适配各AI厂商api
本项目旨在实现兼容各大模型厂商API的流式对话和同步对话接口,现已支持智谱、豆包、通义、通义版DeepSeek。项目地址:[https://gitee.com/alpbeta/ai-api-union](https://gitee.com/alpbeta/ai-api-union)。通过`ChatController`类暴露两个接口,入参为`ChatRequest`,包含会话ID、大模型标识符和聊天消息列表。流式对话返回`Flux&lt;String&gt;`,同步调用返回`String`
|
自然语言处理 API 开发者
DeepSeek-Free-API:DeepSeekV3免费的api接口,需要使用api方式的同学可以参考一下这个项目,可以收藏起来试一下
嗨,大家好,我是小华同学。今天为大家介绍一个开源项目——DeepSeek V3 Free 服务。该项目基于 DeepSeek-V3 R1 大模型,提供免费、高性能的 API,支持高速流式输出、多轮对话、联网搜索和深度思考等功能。适用于智能客服、内容创作、教育辅助等场景。部署方式灵活,支持 Docker、Docker-compose、Render、Vercel 和原生部署。欢迎关注我们,获取更多优质开源项目和高效工作学习方法。
3233 15
|
开发框架 数据可视化 .NET
.NET 中管理 Web API 文档的两种方式
.NET 中管理 Web API 文档的两种方式
340 14
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
427 9
|
存储 开发框架 .NET
.NET 8 实现无实体库表 API 部署服务
【10月更文挑战第12天】在.NET 8中,可通过以下步骤实现无实体库表的API部署:首先安装.NET 8 SDK及开发工具,并选用轻量级Web API框架如ASP.NET Core;接着创建新项目并设计API,利用内存数据结构模拟数据存储;最后配置项目设置并进行测试与部署。此方法适用于小型项目或临时解决方案,但对于大规模应用仍需考虑持久化存储以确保数据可靠性与可扩展性。
222 3
|
7月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
587 4
|
11月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!