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

.NET API+React/Flutter跨平台认证令牌安全管理方案咨询

适配React与Flutter的.NET API令牌管理最优方案

针对你同时开发.NET API、React网页端和Flutter移动端的场景,最优方案是双认证模式兼容——既保留网页端Cookie的安全优势,又适配移动端的Bearer令牌需求,完美平衡安全性与跨平台易用性。

核心思路

  • 网页端:使用HTTP-only Cookie存储刷新令牌(防XSS攻击),访问令牌存在内存中(避免持久化风险)
  • 移动端:使用Bearer令牌模式,访问令牌存内存,刷新令牌加密存储在系统安全容器中
  • .NET API:同时支持Cookie认证和JWT Bearer认证,根据请求来源自动适配

React网页端实现

令牌管理逻辑

  • 登录:调用API登录接口时带上credentials: 'include',API通过Set-Cookie设置HTTP-only刷新令牌,响应体返回访问令牌,前端将访问令牌存在React Context/Redux内存中
  • 请求:每次API请求将访问令牌放在Authorization: Bearer {token}头中
  • 刷新:访问令牌过期时,调用刷新接口(自动携带HTTP-only Cookie中的刷新令牌),API返回新的访问令牌后更新内存中的值
  • 退出:调用退出接口,API清除Cookie中的刷新令牌,前端清空内存中的访问令牌

关键安全点

  • 禁止将访问令牌存在localStorage,避免XSS攻击窃取
  • 生产环境强制开启Cookie的Secure和SameSite=Lax/Strict属性

示例代码(React)

import { createContext, useContext, useState } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [accessToken, setAccessToken] = useState(null);

  // 登录逻辑
  const login = async (email, password) => {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
      credentials: 'include' // 自动携带/接收Cookie
    });
    const data = await res.json();
    setAccessToken(data.accessToken);
  };

  // 刷新令牌逻辑
  const refreshToken = async () => {
    const res = await fetch('/api/auth/refresh', {
      method: 'POST',
      credentials: 'include'
    });
    const data = await res.json();
    setAccessToken(data.accessToken);
    return data.accessToken;
  };

  return (
    <AuthContext.Provider value={{ accessToken, login, refreshToken }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);

Flutter移动端实现

令牌管理逻辑

  • 登录:调用API登录接口,直接获取响应体中的访问令牌和刷新令牌;访问令牌存在内存(全局变量或状态管理),刷新令牌用flutter_secure_storage加密存储(系统密钥链/Keystore)
  • 请求:每次API请求将访问令牌放在Authorization: Bearer {token}头中
  • 刷新:访问令牌过期时,从安全存储取出刷新令牌,调用刷新接口传递令牌,获取新的访问令牌和刷新令牌后更新内存和安全存储
  • 退出:调用退出接口,清除安全存储中的刷新令牌和内存中的访问令牌

关键安全点

  • 必须用加密存储插件存储刷新令牌,禁止用SharedPreferences明文存储
  • 访问令牌仅存内存,APP重启后重新登录或刷新获取

示例代码(Flutter)

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

final storage = FlutterSecureStorage();
String? _accessToken;

// 登录
Future<void> login(String email, String password) async {
  final response = await http.post(
    Uri.parse('https://your-api.com/api/auth/login'),
    body: {'email': email, 'password': password},
  );
  final data = jsonDecode(response.body);
  _accessToken = data['accessToken'];
  await storage.write(key: 'refreshToken', value: data['refreshToken']);
}

// 刷新令牌
Future<String?> refreshToken() async {
  final refreshToken = await storage.read(key: 'refreshToken');
  if (refreshToken == null) return null;

  final response = await http.post(
    Uri.parse('https://your-api.com/api/auth/refresh'),
    body: {'refreshToken': refreshToken},
  );
  final data = jsonDecode(response.body);
  _accessToken = data['accessToken'];
  await storage.write(key: 'refreshToken', value: data['refreshToken']); // 令牌旋转
  return _accessToken;
}

// 带令牌的请求
Future<http.Response> authenticatedGet(String url) async {
  return http.get(
    Uri.parse(url),
    headers: {'Authorization': 'Bearer $_accessToken'},
  );
}

.NET API适配配置

双认证模式配置

同时启用Cookie认证和JWT Bearer认证,允许两种方式通过授权校验:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

// 添加认证服务
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.Cookie.Name = "RefreshToken";
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 生产环境强制HTTPS
    options.Cookie.SameSite = SameSiteMode.Lax;
    options.ExpireTimeSpan = TimeSpan.FromDays(7);
    options.Events = new CookieAuthenticationEvents
    {
        OnValidatePrincipal = async context =>
        {
            // 校验刷新令牌有效性(示例:从数据库查询)
            var refreshToken = context.Principal?.FindFirst("RefreshToken")?.Value;
            if (string.IsNullOrEmpty(refreshToken) || !await ValidateRefreshToken(refreshToken))
            {
                context.RejectPrincipal();
                await context.HttpContext.SignOutAsync();
            }
        }
    };
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(
            System.Text.Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]))
    };
});

// 添加授权服务
builder.Services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build();
});

// 跨域配置(针对React网页端)
builder.Services.AddCors(options =>
{
    options.AddPolicy("ReactCors", policy =>
    {
        policy.WithOrigins("https://your-react-app.com")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials(); // 允许携带Cookie
    });
});

var app = builder.Build();

// 启用中间件
app.UseHttpsRedirection();
app.UseCors("ReactCors");
app.UseAuthentication();
app.UseAuthorization();

// 登录接口示例
app.MapPost("/api/auth/login", async (LoginRequest req, HttpContext context, ITokenService tokenService) =>
{
    // 校验用户凭证
    var user = await _userService.ValidateUser(req.Email, req.Password);
    if (user == null) return Results.Unauthorized();

    // 生成令牌
    var accessToken = tokenService.GenerateAccessToken(user);
    var refreshToken = tokenService.GenerateRefreshToken(user);

    // 网页端:设置HTTP-only Cookie
    if (context.Request.Headers["User-Agent"].ToString().Contains("Mozilla"))
    {
        context.Response.Cookies.Append("RefreshToken", refreshToken, new CookieOptions
        {
            HttpOnly = true,
            Secure = true,
            SameSite = SameSiteMode.Lax,
            Expires = DateTime.UtcNow.AddDays(7)
        });
        return Results.Ok(new { AccessToken = accessToken });
    }
    // 移动端:返回双令牌
    else
    {
        return Results.Ok(new { AccessToken = accessToken, RefreshToken = refreshToken });
    }
});

// 刷新令牌接口示例
app.MapPost("/api/auth/refresh", async (HttpContext context, ITokenService tokenService) =>
{
    string? refreshToken;
    // 从Cookie或请求体获取刷新令牌
    if (context.Request.Cookies.ContainsKey("RefreshToken"))
    {
        refreshToken = context.Request.Cookies["RefreshToken"];
    }
    else
    {
        var body = await context.Request.ReadFromJsonAsync<RefreshRequest>();
        refreshToken = body?.RefreshToken;
    }

    if (string.IsNullOrEmpty(refreshToken)) return Results.Unauthorized();

    // 校验刷新令牌并获取用户信息
    var user = await tokenService.ValidateRefreshToken(refreshToken);
    if (user == null) return Results.Unauthorized();

    // 生成新令牌
    var newAccessToken = tokenService.GenerateAccessToken(user);
    var newRefreshToken = tokenService.GenerateRefreshToken(user);

    // 更新Cookie或返回新令牌
    if (context.Request.Cookies.ContainsKey("RefreshToken"))
    {
        context.Response.Cookies.Append("RefreshToken", newRefreshToken, new CookieOptions
        {
            HttpOnly = true,
            Secure = true,
            SameSite = SameSiteMode.Lax,
            Expires = DateTime.UtcNow.AddDays(7)
        });
        return Results.Ok(new { AccessToken = newAccessToken });
    }
    else
    {
        return Results.Ok(new { AccessToken = newAccessToken, RefreshToken = newRefreshToken });
    }
});

app.Run();

关键安全增强

  • 刷新令牌旋转:每次刷新返回新的刷新令牌,旧令牌失效,降低泄露风险
  • 刷新令牌绑定用户ID和设备标识,存储在数据库中,校验时核对
  • 访问令牌设置短有效期(15-30分钟),减少泄露影响范围

安全与易用性总结

  • 安全性:网页端用HTTP-only Cookie防XSS,移动端用系统加密存储防本地窃取;令牌旋转、短有效期进一步降低风险
  • 易用性:前端无需复杂适配,网页端依赖浏览器自动处理Cookie,移动端用标准Bearer模式;API统一接口,仅需根据请求来源调整返回逻辑

内容的提问来源于stack exchange,提问作者Bunny

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 06:17:14