Next.js 14(App Router)与ASP.NET Core 认证方案咨询
Next.js 客户端/服务器组件 + ASP.NET Core Web API 混合认证最佳实践
一、核心方案合理性确认
你的思路完全可行:客户端组件用JWT(无状态、适配前端异步请求场景),服务器组件用Cookie(浏览器自动携带,适合SSR/SSG下的服务端请求),两者可共享ASP.NET Core的统一认证体系,不用拆分用户身份逻辑。
二、ASP.NET Core 后端配置
1. 双认证方案初始化
同时启用Cookie和JWT Bearer认证,默认用Cookie适配服务器组件请求,JWT作为客户端组件的备选方案:
services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddCookie(options => { options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 生产环境强制HTTPS options.Cookie.SameSite = SameSiteMode.Strict; options.ExpireTimeSpan = TimeSpan.FromHours(8); options.SlidingExpiration = true; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = Configuration["Jwt:Issuer"], ValidAudience = Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"])) }; });
2. 统一用户身份Claims
确保两种认证方式返回的ClaimsPrincipal结构一致,后端业务逻辑无需区分认证来源:
.AddCookie(options => { options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = context => { // 统一添加自定义Claim,比如用户角色、权限 context.Principal?.AddClaim(new Claim("Role", "User")); return Task.CompletedTask; } }; }) .AddJwtBearer(options => { options.Events = new JwtBearerEvents { OnTokenValidated = context => { // 和Cookie认证保持一致的Claims处理逻辑 context.Principal?.AddClaim(new Claim("Role", "User")); return Task.CompletedTask; } }; });
3. 统一登录接口设计
登录成功后同时返回JWT令牌并设置认证Cookie,满足前后端不同组件的认证需求:
[HttpPost("login")] public async Task<IActionResult> Login(LoginRequest request) { // 验证用户账号密码逻辑 var user = await _userService.ValidateUser(request.Username, request.Password); if (user == null) return Unauthorized(); var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username) }; // 生成JWT令牌 var jwtToken = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddHours(8), signingCredentials: new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"])), SecurityAlgorithms.HmacSha256) ); var token = new JwtSecurityTokenHandler().WriteToken(jwtToken); // 设置认证Cookie var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddHours(8), IsPersistent = request.RememberMe }); return Ok(new { Token = token }); }
三、Next.js 前端实现
1. 服务器组件认证处理
服务器组件直接发起请求时,浏览器会自动携带认证Cookie,只需配置跨域凭证:
// next.config.js module.exports = { async rewrites() { return [ { source: '/api/:path*', destination: 'https://your-aspnet-api-domain.com/api/:path*' } ]; }, async headers() { return [ { source: '/api/:path*', headers: [ { key: 'Access-Control-Allow-Credentials', value: 'true' } ] } ]; } };
服务器组件内直接请求:
async function UserProfileServerComponent() { const res = await fetch('/api/user/profile', { credentials: 'include' }); if (!res.ok) throw new Error('Failed to fetch user data'); const user = await res.json(); return <div>当前登录用户:{user.username}</div>; }
2. 客户端组件认证处理
- JWT存储:将登录返回的JWT存入
sessionStorage(避免持久化带来的XSS风险,若需记住登录可配合HttpOnly刷新令牌) - 请求拦截器:自动为请求添加JWT头部:
import axios from 'axios'; const apiClient = axios.create({ baseURL: '/api' }); apiClient.interceptors.request.use(config => { const token = sessionStorage.getItem('jwtToken'); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }); // 使用示例 apiClient.get('/user/settings').then(res => console.log(res.data));
- 过期处理:监听JWT过期时间,到期前调用刷新令牌接口获取新JWT,失败则跳转登录页。
四、安全核心注意事项
- XSS防护:客户端存储JWT优先用
sessionStorage,开启CSP内容安全策略过滤恶意脚本;禁止将敏感信息存入localStorage - CSRF防护:ASP.NET Core默认启用Cookie的CSRF防护,服务器组件请求需确保携带CSRF令牌;JWT认证无需CSRF,但要限制请求来源域名
- HTTPS强制:生产环境必须全站HTTPS,防止令牌、Cookie被窃听
- 令牌刷新:实现刷新令牌机制,将刷新令牌存入HttpOnly Cookie,避免XSS窃取
- 权限校验:后端所有接口添加
[Authorize]属性,基于Claims做细粒度权限控制,绝不依赖前端权限判断
五、统一登出逻辑
后端提供登出接口,同时清除认证Cookie:
[HttpPost("logout")] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Ok(); }
客户端登出时调用接口并清除本地JWT:
async function handleLogout() { await apiClient.post('/auth/logout'); sessionStorage.removeItem('jwtToken'); window.location.href = '/login'; }
内容的提问来源于stack exchange,提问作者Michel
相关产品推荐
相关产品推荐

