
这篇文章专门回答一个在 Minimal API 中非常关键、但很多人一开始容易糊涂的问题: 一个项目里如果有多个认证方案,该怎么办?如何选择?什么情况下采用哪种方式?
这不是一个“会不会写代码”的问题,而是一个 框架理解 + 架构设计 + 落地写法 的问题。
很多人刚学 Minimal API 时,先接触的是单一认证方案:
这当然没问题。 但一旦项目稍微真实一点,就会出现这些需求:
这时候你就不能只会:
builder.Services.AddAuthentication("Bearer").AddJwtBearer(...);你必须真正理解:
这篇文章我会按 从简单到难 的顺序来讲。
先别急着写代码,我们先看现实项目。
你有一个系统:
这时一个项目里就可能同时需要:
比如某个内部系统访问你的接口,不走用户登录,而是带一个固定 Header:
X-Internal-Key: abc123这就可能需要第三种方案:
比如 /api/profile:
那么同一个接口可能要支持:
任意一种都算通过。
本质上是:
不同来源的请求,携带身份凭证的方式不一样。
比如:
ASP.NET Core 认证框架就是用“认证方案(Scheme)”来区分这些不同的识别方式。
很多初学者会误以为:
多个认证方案 = 有多个用户系统
其实不是。
多个认证方案真正的含义是:
同一个 ASP.NET Core / Minimal API 应用中,存在多种“识别用户身份”的方式。
例如:
它们并不一定代表三套用户体系,而是三套 身份提取规则。
比如:
看请求里有没有认证 Cookie
看请求头里有没有 Bearer Token
看请求头里有没有某个内部密钥
所以“多个认证方案”的意思是:
项目里注册了多种识别方法,框架可以按规则选择其中一种或多种来执行。
这部分先看最基础的代码。
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var secretKey = "this_is_a_demo_secret_key_1234567890";
builder.Services
.AddAuthentication()
.AddCookie("Cookies", options =>
{
options.Cookie.Name = "demo.cookie";
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
})
.AddJwtBearer("Bearer", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(secretKey)),
ValidateLifetime = true
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => "公开接口");
app.Run();你会发现,这次不是:
builder.Services.AddAuthentication("Bearer")而是:
builder.Services.AddAuthentication()也就是:
先不指定默认方案,只把多个方案都注册进去。
然后分别注册:
"Cookies""Bearer"因为一旦你设置了默认方案,框架在很多场景下会优先走它。
而多个方案并存时,常常需要:
这时如果你一开始就设一个全局默认方案,很容易让行为变得不直观。
所以对于多方案项目,一个很常见的做法是:
builder.Services.AddAuthentication();然后在具体接口上明确指定方案。
这是多方案最关键的地方。
app.MapGet("/cookie-only", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Message = "你通过 Cookie 登录了",
Name = user.Identity?.Name
});
})
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Cookies");
policy.RequireAuthenticatedUser();
});这里的意思是:
"Cookies" 方案认证 app.MapGet("/jwt-only", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Message = "你通过 JWT 登录了",
Name = user.Identity?.Name
});
})
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Bearer");
policy.RequireAuthenticatedUser();
}); app.MapGet("/hybrid", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Message = "Cookie 或 JWT 任一通过即可",
Name = user.Identity?.Name
});
})
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Cookies");
policy.AuthenticationSchemes.Add("Bearer");
policy.RequireAuthenticatedUser();
});这表示:
这个接口允许用
"Cookies"或"Bearer"任意一种方案完成认证。
下面我给你一个更完整但仍然适合初学者理解的版本。
这个例子包含:
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
var secretKey = "this_is_a_demo_secret_key_1234567890";
// 注册多个认证方案
builder.Services
.AddAuthentication()
.AddCookie("Cookies", options =>
{
options.Cookie.Name = "demo.cookie";
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
};
})
.AddJwtBearer("Bearer", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(secretKey)),
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => "公开接口");
// Cookie 登录
app.MapPost("/login-cookie", async (HttpContext context) =>
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "cookie-user"),
new Claim(ClaimTypes.NameIdentifier, "1001")
};
var identity = new ClaimsIdentity(claims, "Cookies");
var principal = new ClaimsPrincipal(identity);
await context.SignInAsync("Cookies", principal);
return Results.Ok(new { message = "Cookie 登录成功" });
});
// JWT 登录
app.MapPost("/login-jwt", () =>
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "jwt-user"),
new Claim(ClaimTypes.NameIdentifier, "2001")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
claims: claims,
expires: DateTime.UtcNow.AddMinutes(30),
signingCredentials: credentials);
var jwt = new JwtSecurityTokenHandler().WriteToken(token);
return Results.Ok(new
{
access_token = jwt
});
});
// 只允许 Cookie
app.MapGet("/cookie-only", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Message = "这是 Cookie 专属接口",
Name = user.Identity?.Name
});
})
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Cookies");
policy.RequireAuthenticatedUser();
});
// 只允许 JWT
app.MapGet("/jwt-only", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Message = "这是 JWT 专属接口",
Name = user.Identity?.Name
});
})
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Bearer");
policy.RequireAuthenticatedUser();
});
// Cookie 或 JWT 都可以
app.MapGet("/both", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Message = "Cookie 或 JWT 都可以访问",
Name = user.Identity?.Name
});
})
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Cookies");
policy.AuthenticationSchemes.Add("Bearer");
policy.RequireAuthenticatedUser();
});
app.Run();这一节最重要。 你不是只要会写代码,更要知道 何时应该这样设计。
比如:
只用 Cookie
因为:
一般 不要
因为没有必要引入复杂度。
比如:
只用 JWT Bearer
因为:
一般 不要
因为单一 Bearer 已经足够清晰。
比如:
/admin/* 给后台管理页面/api/* 给前端 / App 调用Cookie + JWT 多方案
因为两个调用场景天然不同:
你可以这么分:
/admin/* 走 Cookie/api/* 走 Bearer比如:
JWT + 自定义方案
因为调用方不是“人”,而是“系统”。 系统调用未必需要用户登录流程,更适合走单独的机器认证方式。
比如:
/profile混合支持
policy.AuthenticationSchemes.Add("Cookies");
policy.AuthenticationSchemes.Add("Bearer");这类设计虽然可行,但会增加接口行为复杂度。 如果能从路由层面分开,通常更清晰。
场景 | 推荐方案 |
|---|---|
纯浏览器后台 | Cookie |
纯前后端分离 API | JWT Bearer |
后台管理 + API 共存 | Cookie + JWT |
用户认证 + 系统间调用 | JWT + 自定义 Header / ApiKey |
同一个接口服务多个客户端 | 可考虑多方案并行 |
问自己三个问题:
如果有不同调用方,往往就可能要多个方案。
多方案一多,最怕的是 Program.cs 里全乱掉。
建议这样组织。
var adminGroup = app.MapGroup("/admin")
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Cookies");
policy.RequireAuthenticatedUser();
});
var apiGroup = app.MapGroup("/api")
.RequireAuthorization(policy =>
{
policy.AuthenticationSchemes.Add("Bearer");
policy.RequireAuthenticatedUser();
});这样你的接口天然分区:
/admin/* 全走 Cookie/api/* 全走 JWT这在 Minimal API 中非常推荐。
例如:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CookieOnly", policy =>
{
policy.AuthenticationSchemes.Add("Cookies");
policy.RequireAuthenticatedUser();
});
options.AddPolicy("BearerOnly", policy =>
{
policy.AuthenticationSchemes.Add("Bearer");
policy.RequireAuthenticatedUser();
});
options.AddPolicy("CookieOrBearer", policy =>
{
policy.AuthenticationSchemes.Add("Cookies");
policy.AuthenticationSchemes.Add("Bearer");
policy.RequireAuthenticatedUser();
});
});然后接口上这么写:
app.MapGet("/cookie-data", () => "cookie data")
.RequireAuthorization("CookieOnly");
app.MapGet("/jwt-data", () => "jwt data")
.RequireAuthorization("BearerOnly");
app.MapGet("/hybrid-data", () => "hybrid data")
.RequireAuthorization("CookieOrBearer");这比把认证方案写死在每个接口里更清晰。
不是的。
如果你没有明确指定,框架会按默认方案或默认行为走。 这可能不是你想要的。
所以多方案项目里,最好明确指定每个接口或每组接口使用哪个方案。
[全局] 自动认证如果你完全不指定默认方案,也不在接口上声明具体方案,某些行为会变得不明确。 所以多方案项目要么:
这是实战中经常让人困惑的点。 比如一个接口允许:
如果两个凭证都带了,最后到底是谁生效?
这取决于认证处理和授权组合方式。 所以对于业务系统,我一般建议:
能按路由分开就分开,不要轻易让同一接口承载太多认证入口。
否则排查问题会更复杂。
Cookie 默认喜欢跳转页面;JWT 默认返回 401。 多方案项目里如果不统一处理,接口返回行为会不一致。
所以通常要显式处理 Cookie 的跳转事件。
这是最重要的一条。
很多初学者觉得:
一个项目支持 Cookie + JWT + ApiKey,好像很厉害
但实际上:
如果你只是普通后台项目,单 Cookie 就很好。 如果你只是标准 API,单 JWT 就很好。
比如:
如果调用模式天然不同,那么多方案是合理的。
比如:
/admin/*:Cookie/api/*:JWT/internal/*:ApiKey / 自定义 Header这种按路由边界分隔的做法,远比所有接口都混合支持更可维护。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。