You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在极简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 });
}

关键说明

  1. 明确身份验证方案:指定JWT为默认验证/挑战方案,避免系统自动选择的不确定性
  2. 完善JWT验证逻辑:添加签名密钥、发行者、受众等核心参数,确保JWT能被正确验证
  3. 实现登录接口:验证内存账户凭据,生成包含用户身份声明的JWT令牌
  4. 补全中间件链:UseAuthorization是身份验证生效的必要环节,需放在UseAuthentication之后
  5. 优化身份信息展示:将httpContext.User拆分为更易读的结构,直观显示认证状态、用户名和声明

验证方式

  1. 发送POST请求到/login,JSON请求体:{"Username":"wim","Password":"123456"},会返回JWT令牌
  2. 调用任意Echo路由时,在请求头添加Authorization: Bearer <你的JWT令牌>,此时Echo响应的User部分会显示已认证状态、用户名和声明信息

内容的提问来源于stack exchange,提问作者Wim ten Brink

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 18:03:16