如何在极简API中添加身份验证?含实验项目代码及分步需求
极简Web API身份验证实现(步骤1)
我搭建了一个'Echo'服务,用于学习Web应用与Web API原理,所有路由都会返回包含请求/响应信息的JSON结果,已实现添加响应头的header、返回Cookie的cookie等实验性路由。
现在我想从零理解身份验证组件和JWT的原理,不想直接复制现成的AddAuthentication/AddJwtBearer代码。当前用内存字典accounts存储有效账户,暂不使用数据库,后续计划接入第三方OAuth认证。我的学习步骤分为:
- 在极简项目中添加身份验证
- 创建需要身份验证的路由
- 添加授权功能
- 创建需要授权的路由
- 集成Identity
- 创建使用Identity的路由
现在先解决步骤1,同时希望Echo响应的JSON中user/identity能显示有效信息。以下是更简洁的实现方案:
核心修改与完整代码
要让身份验证真正生效,需完成配置签名密钥、实现登录接口、完善中间件链这几个核心动作,以下是修改后的完整代码:
using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); // 内存存储有效账户 var accounts = new Dictionary<string, string>() { { "wim", "123456" }, { "test", "abc123" } }; // 配置身份验证:JWT为默认方案,Cookie作为备选 builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "EchoService", ValidAudience = "EchoUsers", // 签名密钥:生产环境需存配置文件,避免硬编码 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-strong-secret-key-here-keep-it-safe")) }; }) .AddCookie(options => { options.LoginPath = "/login"; options.AccessDeniedPath = "/forbidden"; }); // 提前引入授权服务(后续步骤依赖,同时是身份验证生效的必要环节) builder.Services.AddAuthorization(); var app = builder.Build(); app.UseHsts(); app.UseAuthentication(); app.UseAuthorization(); // 必须在UseAuthentication之后执行 app.MapWhen(ContainsPhp, HandlePhp()); // 登录接口:验证凭据并生成JWT令牌 app.MapPost("/login", (LoginRequest request) => { if (accounts.TryGetValue(request.Username, out var password) && password == request.Password) { // 创建用户身份声明 var claims = new[] { new Claim(ClaimTypes.Name, request.Username), new Claim(ClaimTypes.Role, "User") // 示例角色,为后续授权做准备 }; // 生成JWT令牌 var token = new JwtSecurityToken( issuer: "EchoService", audience: "EchoUsers", claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-strong-secret-key-here-keep-it-safe")), SecurityAlgorithms.HmacSha256 ) ); return Results.Ok(new { Token = new JwtSecurityTokenHandler().WriteToken(token) }); } return Results.Unauthorized(); }); // 原有Echo路由(保持不变) app.MapGet("/", (HttpContext context) => Echo(context)); app.MapGet("/cookie/{name}/{*values}", (HttpContext context, string name, string values) => { foreach (var value in values.Split("/")) { context.Response.Cookies.Append($"{name}.{value}", value); } return Echo(context); }); app.MapGet("/header/{name}/{*values}", (HttpContext context, string name, string values) => { context.Response.Headers[name] = values.Split("/").ToArray(); return Echo(context); }); app.MapGet("{name}.html", (HttpContext context) => Echo(context)); app.MapGet("/{one}/{two}/{three}.{four}", (HttpContext context, string one, string two, string three, string four, [FromQuery] string five, [FromQuery] string six) => { context.Response.Headers["one"] = one; context.Response.Headers["two"] = two; context.Response.Headers["three"] = three; context.Response.Headers["four"] = four; context.Response.Headers["five"] = five; context.Response.Headers["six"] = six; return Echo(context); }); app.MapGet("/{*rest}", (HttpContext context) => Echo(context)); app.MapGet("/echo/", (HttpContext context) => Echo(context)); app.MapGet("/echo/{*rest}", (HttpContext context) => Echo(context)); app.MapGet("{path}.html", (HttpContext context) => Echo(context)); app.Run(); // 辅助类:登录请求模型 public class LoginRequest { public string Username { get; set; } public string Password { get; set; } } // 原有辅助方法(保持不变) bool ContainsPhp(HttpContext context) => context.Request.Path.Value?.ToLower().Contains(".php") ?? false; Action<IApplicationBuilder> HandlePhp() => applicationBuilder => applicationBuilder.Run((context) => Task.Run(() => context.Response.Redirect("https://www.php.net/"))); // 优化Echo的User输出,更清晰展示身份信息 IResult Echo(HttpContext httpContext) { return Results.Json(new { Request = new { host = httpContext.Request.Host, method = httpContext.Request.Method, path = httpContext.Request.Path, pathBase = httpContext.Request.PathBase, route = httpContext.Request.RouteValues, scheme = httpContext.Request.Scheme, Query = new { query = httpContext.Request.Query, queryString = httpContext.Request.QueryString, }, }, response = new { statusCode = httpContext.Response.StatusCode, cookies = httpContext.Response.Cookies, contentType = httpContext.Response.ContentType, }, headers = new { response = httpContext.Response.Headers, request = httpContext.Request.Headers, }, Connection = new { protocol = httpContext.Request.Protocol, localIpAddress = httpContext.Connection.LocalIpAddress?.ToString(), localPort = httpContext.Connection.LocalPort, remoteIpAddress = httpContext.Connection.RemoteIpAddress?.ToString(), remotePort = httpContext.Connection.RemotePort, }, User = new { IsAuthenticated = httpContext.User.Identity.IsAuthenticated, Name = httpContext.User.Identity.Name, Claims = httpContext.User.Claims.Select(c => new { c.Type, c.Value }) }, items = httpContext.Items, }, new(JsonSerializerDefaults.Web) { WriteIndented = true }); }
关键说明
- 明确身份验证方案:指定JWT为默认验证/挑战方案,避免系统自动选择的不确定性
- 完善JWT验证逻辑:添加签名密钥、发行者、受众等核心参数,确保JWT能被正确验证
- 实现登录接口:验证内存账户凭据,生成包含用户身份声明的JWT令牌
- 补全中间件链:
UseAuthorization是身份验证生效的必要环节,需放在UseAuthentication之后 - 优化身份信息展示:将
httpContext.User拆分为更易读的结构,直观显示认证状态、用户名和声明
验证方式
- 发送POST请求到
/login,JSON请求体:{"Username":"wim","Password":"123456"},会返回JWT令牌 - 调用任意Echo路由时,在请求头添加
Authorization: Bearer <你的JWT令牌>,此时Echo响应的User部分会显示已认证状态、用户名和声明信息
内容的提问来源于stack exchange,提问作者Wim ten Brink
相关产品推荐
相关产品推荐

