前言: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的密钥、逾期时间
- {
- "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"
- }
- }
复制代码 - 创建JWT设置实体类 JWTOptions
- public class JWTOptions
- {
- public string SigningKey { get; set; }
- public int ExpireSeconds { get; set; }
- }
复制代码 - 打开 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
- };
- });
复制代码 - 打开 Program.cs 文件,在 app.UseAuthorization 之前,添加身份验证中心件
- // 使用 Authentication 中间件,放在 UseAuthorization 之前
- app.UseAuthentication();
复制代码 - 创建继承 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>
- {
- }
复制代码 - 创建继承 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);
- }
- }
复制代码 - (可选)如果数据表还没创建,执行数据库迁移命令
- 创建登录请求的参数实体类 LoginRequest
- public record LoginRequest(string UserName, string Password);
复制代码 - 打开登录请求控制器,编写 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);
- }
- }
- }
复制代码 - 打开别的控制器,在类上添加 [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}");
- }
- }
- }
复制代码 - 打开 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);
- });
复制代码 启动项目,进行测试
- 首先访问/Test1/Login,获取 JWT Token,复制下这个值
- 然后访问/Test2/Hello,不带 JWT Token,将收到 401 信息
- 在 Swagger 上的 Authorization 输入 JWT Token,重新访问/Test2/Hello,将返回正确的效果
- 如果是在 Postman 等第三方,要在 Header 上加上参数 Authorization=bearer
附录:完整的 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/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 之前
- 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企服之家,中国第一个企服评测及商务社交产业平台。 |