ASP.NET Core 标识(Identity)框架系列(三):在 ASP.NET Core Web API ...

打印 上一主题 下一主题

主题 986|帖子 986|积分 2958


前言:JWT实现登录的流程


  • 客户端向服务器端发送用户名、密码等请求登录。
  • 服务器端校验用户名、密码,如果校验乐成,则从数据库中取出这个用户的ID、角色等用户相关信息。
  • 服务器端采取只有服务器端才知道的密钥来对用户信息的 JSON 字符串进行署名,形成署名数据。
  • 服务器端把用户信息的 JSON 字符串和署名拼接到一起形成JWT,然后发送给客户端。
  • 客户端保存服务器端返回的 JWT,而且在客户端每次向服务器端发送请求的时候都带上这个 JWT。
  • 每次服务器端收到欣赏器请求中携带的 JWT 后,服务器端用密钥对JWT的署名进行校验,如果校验乐成,服务器端则从 JWT 中的 JSON 字符串中读取出用户的信息。
Step By Step 步骤


  • 创建一个 Asp.Net Core WebApi 项目
  • 引用以下 Nuget 包:
    Microsoft.AspNetCore.Authentication.JwtBearer
    Microsoft.AspNetCore.Identity.EntityFrameworkCore
    Microsoft.EntityFrameworkCore.SqlServer
    Microsoft.EntityFrameworkCore.Tools

  • 打开 appsettings.json 文件,设置数据库连接字符串和JWT的密钥、逾期时间
    1. {
    2.   "Logging": {
    3.         "LogLevel": {
    4.           "Default": "Information",
    5.           "Microsoft.AspNetCore": "Warning"
    6.         }
    7.   },
    8.   "AllowedHosts": "*",
    9.   "ConnectionStrings": {
    10.         "Default": "Server=(localdb)\\mssqllocaldb;Database=IdentityTestDB;Trusted_Connection=True;MultipleActiveResultSets=true"
    11.   },
    12.   "JWT": {
    13.         "SigningKey": "fasdfad&9045dafz222#fadpio@0232",
    14.         "ExpireSeconds": "86400"
    15.   }
    16. }
    复制代码
  • 创建JWT设置实体类 JWTOptions
    1. public class JWTOptions
    2. {
    3.         public string SigningKey { get; set; }
    4.         public int ExpireSeconds { get; set; }
    5. }
    复制代码
  • 打开 Program.cs 文件,在 builder.Build 之前,编写代码对 JWT 进行设置
    1. // 注入 JWT 配置
    2. services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
    3. // 注入 JwtBearer 配置
    4. services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    5.         .AddJwtBearer(x => {
    6.                 var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
    7.                 byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
    8.                 var secKey = new SymmetricSecurityKey(keyBytes);
    9.                 x.TokenValidationParameters = new()
    10.                 {
    11.                         ValidateIssuer = false,
    12.                         ValidateAudience = false,
    13.                         ValidateLifetime = true,
    14.                         ValidateIssuerSigningKey = true,
    15.                         IssuerSigningKey = secKey
    16.                 };
    17.         });
    复制代码
  • 打开 Program.cs 文件,在 app.UseAuthorization 之前,添加身份验证中心件
    1. // 使用 Authentication 中间件,放在 UseAuthorization 之前
    2. app.UseAuthentication();
    复制代码
  • 创建继承 IdentityRole 的 User 和 Role 实体类
    1. using Microsoft.AspNetCore.Identity;
    2. public class User: IdentityUser<long>
    3. {
    4.         public DateTime CreationTime { get; set; }
    5.         public string? NickName { get; set; }
    6. }
    7. public class Role: IdentityRole<long>
    8. {
    9. }
    复制代码
  • 创建继承 IdentityDbContext 的上下文类
    1. using Microsoft.EntityFrameworkCore;
    2. using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    3. public class IdDbContext: IdentityDbContext<User, Role, long>
    4. {
    5.         public IdDbContext(DbContextOptions<IdDbContext> options) : base(options)
    6.         {
    7.         }
    8.         protected override void OnModelCreating(ModelBuilder modelBuilder)
    9.         {
    10.                 base.OnModelCreating(modelBuilder);
    11.                 modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    12.         }
    13. }
    复制代码
  • (可选)如果数据表还没创建,执行数据库迁移命令

  • 创建登录请求的参数实体类 LoginRequest
    1. public record LoginRequest(string UserName, string Password);
    复制代码
  • 打开登录请求控制器,编写 Login API,在其中创建 JWT
    1. using Microsoft.AspNetCore.Identity;
    2. using Microsoft.AspNetCore.Mvc;
    3. using Microsoft.Extensions.Options;
    4. using Microsoft.IdentityModel.Tokens;
    5. using System.IdentityModel.Tokens.Jwt;
    6. using System.Security.Claims;
    7. using System.Text;
    8. namespace ASPNETCore_JWT1.Controllers
    9. {
    10.         [ApiController]
    11.         [Route("[controller]/[action]")]
    12.         public class Test1Controller : ControllerBase
    13.         {
    14.                 private readonly UserManager<User> userManager;
    15.                 //注入 UserManager
    16.                 public Test1Controller(UserManager<User> userManager)
    17.                 {
    18.                         this.userManager = userManager;
    19.                 }
    20.                 // 生成 JWT
    21.                 private static string BuildToken(IEnumerable<Claim> claims, JWTOptions options)
    22.                 {
    23.                         DateTime expires = DateTime.Now.AddSeconds(options.ExpireSeconds);
    24.                         byte[] keyBytes = Encoding.UTF8.GetBytes(options.SigningKey);
    25.                         var secKey = new SymmetricSecurityKey(keyBytes);
    26.                         var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);
    27.                         var tokenDescriptor = new JwtSecurityToken(
    28.                                 expires: expires, signingCredentials:
    29.                                 credentials,
    30.                                 claims: claims);
    31.                         var result = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    32.                         return result;
    33.                 }
    34.                 // 在方法中注入 IOptions<JWTOptions>
    35.                 // 只需要返回 JWT Token 即可,其它的身份验证中间件会处理
    36.                 [HttpPost]
    37.                 public async Task<IActionResult> Login(
    38.                         LoginRequest req,
    39.                         [FromServices] IOptions<JWTOptions> jwtOptions)
    40.                 {
    41.                         string userName = req.UserName;
    42.                         string password = req.Password;
    43.                         var user = await userManager.FindByNameAsync(userName);
    44.                         if (user == null)
    45.                         {
    46.                                 return NotFound($"用户名不存在{userName}");
    47.                         }
    48.                         if (await userManager.IsLockedOutAsync(user))
    49.                         {
    50.                                 return BadRequest("LockedOut");
    51.                         }
    52.                         var success = await userManager.CheckPasswordAsync(user, password);
    53.                         if (!success)
    54.                         {
    55.                                 return BadRequest("Failed");
    56.                         }
    57.                        
    58.                         var claims = new List<Claim>();
    59.                         claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
    60.                         claims.Add(new Claim(ClaimTypes.Name, user.UserName));
    61.                         var roles = await userManager.GetRolesAsync(user);
    62.                         foreach (string role in roles)
    63.                         {
    64.                                 claims.Add(new Claim(ClaimTypes.Role, role));
    65.                         }
    66.                         var jwtToken = BuildToken(claims, jwtOptions.Value);
    67.                         return Ok(jwtToken);
    68.                 }
    69.         }
    70. }
    复制代码
  • 打开别的控制器,在类上添加 [Authorize] 这个特性
    1. using Microsoft.AspNetCore.Mvc;
    2. using System.Security.Claims;
    3. using Microsoft.AspNetCore.Authorization;
    4. namespace ASPNETCore_JWT1.Controllers
    5. {
    6.         // [Authorize] 特性标识此控制器的方法需要身份授权才能访问
    7.         // 授权中间件会处理其它的
    8.         [ApiController]
    9.         [Route("[controller]/[action]")]
    10.         [Authorize]
    11.         public class Test2Controller : Controller
    12.         {
    13.                 [HttpGet]
    14.                 public IActionResult Hello()
    15.                 {
    16.                         // ControllerBase中定义的ClaimsPrincipal类型的User属性代表当前登录用户的身份信息
    17.                         // 可以通过ClaimsPrincipal的Claims属性获得当前登录用户的所有Claim信息
    18.                         // this.User.Claims
    19.                        
    20.                         string id = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;
    21.                         string userName = this.User.FindFirst(ClaimTypes.Name)!.Value;
    22.                         IEnumerable<Claim> roleClaims = this.User.FindAll(ClaimTypes.Role);
    23.                         string roleNames = string.Join(",", roleClaims.Select(c => c.Value));
    24.                         return Ok($"id={id},userName={userName},roleNames ={roleNames}");
    25.                 }
    26.         }
    27. }
    复制代码
  • 打开 Program.cs 文件,设置 Swagger,支持发送 Authorization 报文头
    1. // 配置 Swagger 支持 Authorization
    2. builder.Services.AddSwaggerGen(c => {
    3.         var scheme = new OpenApiSecurityScheme()
    4.         {
    5.                 Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
    6.                 Reference = new OpenApiReference
    7.                 {
    8.                         Type = ReferenceType.SecurityScheme,
    9.                         Id = "Authorization"
    10.                 },
    11.                 Scheme = "oauth2",
    12.                 Name = "Authorization",
    13.                 In = ParameterLocation.Header,
    14.                 Type = SecuritySchemeType.ApiKey
    15.         };
    16.         c.AddSecurityDefinition("Authorization", scheme);
    17.         var requirement = new OpenApiSecurityRequirement();
    18.         requirement[scheme] = new List<string>();
    19.         c.AddSecurityRequirement(requirement);
    20. });
    复制代码
启动项目,进行测试


  • 首先访问/Test1/Login,获取 JWT Token,复制下这个值
  • 然后访问/Test2/Hello,不带 JWT Token,将收到 401 信息
  • 在 Swagger 上的 Authorization 输入 JWT Token,重新访问/Test2/Hello,将返回正确的效果

    • 如果是在 Postman 等第三方,要在 Header 上加上参数 Authorization=bearer

附录:完整的 Program 代码(重点注意代码中的注释
  1. 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/swashbucklebuilder.Services.AddEndpointsApiExplorer();// 设置 Swagger 支持 Authorizationbuilder.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();    c.AddSecurityRequirement(requirement);});var services = builder.Services;// 注册数据库服务services.AddDbContext(opt => {    string connStr = builder.Configuration.GetConnectionString("Default")!;    opt.UseSqlServer(connStr);});// 注册数据掩护服务services.AddDataProtection();// 注册 IdentityCore 服务services.AddIdentityCore(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()    .AddDefaultTokenProviders()    .AddRoleManager()    .AddUserManager();// 注入 JWT 设置services.Configure(builder.Configuration.GetSection("JWT"));// 注入 JwtBearer 设置services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)    .AddJwtBearer(x => {         var jwtOpt = builder.Configuration.GetSection("JWT").Get();        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 之前
  2. app.UseAuthentication();app.UseAuthorization();app.MapControllers();app.Run();
复制代码
总结


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



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表