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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:26:03