ASP.NET Core 标识(Identity)结构系列(三):在 ASP.NET Core Web API 项目中运用标识(Identity)结构进行身份验证
前语: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); } }
-
(可选)假如数据表还没创立,履行数据库搬迁指令
- 参阅前文《ASP.NET Core 标识(Identity)结构系列(一):怎么运用标识(Identity)创立用户和人物?》
-
创立登录恳求的参数实体类 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/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();
总结
- 假如其间某个操作办法不想被验证,能够在这个操作办法上增加 [AllowAnonymous] 特性
- 关于客户端取得的 JWT,在前端项目中,能够把令牌保存到 Cookie、LocalStorage 等方位,然后在后续恳求中重复运用,而关于移动App、PC客户端,能够把令牌保存到装备文件中或许本地文件数据库中。当履行【退出登录】操作的时分,咱们只需在客户端本地把 JWT 删去即可。
- 在发送恳求的时分,只需依照 HTTP 的要求,把 JWT 依照 "Bearer {JWT Token}" 格局放到姓名为 Authorization 的恳求报文头中即可
- 从 Authorization 中取出令牌,而且进行校验、解析,然后把解析成果填充到 User 特点中,这一切都是 ASP.NET Core 完结的,不需求开发人员自己编写代码
往期精彩
- C# 静态类,高手不想让你知道的 15 个本相
- 封装一个 C# 规模判别函数,从此离别重复编写规模判别代码的烦恼
- 用 C# Stopwatch 计时,让代码功能飞起来!
- 轻装上阵,Visual Studio LocalDB:.NET 程序员的本地数据库神器
- 封装一个C#全能根底数据类型转化器,一招处理一切根底类型转化烦恼
- 闲话 .NET(7):.NET Core 能筛选 .NET FrameWork 吗?
- 常用的 4 种 ORM 结构(EF Core,SqlSugar,FreeSql,Dapper)比照总结
- C# AutoMapper 10个常用办法总结
- C# 7个办法比较两个目标是否持平
- C# 去掉字符串最终一个字符的 4 种办法