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

登录后Bearer Token的安全存储及HTTP请求携带方法

问题解答

一、安全存储Bearer Token的方案及实现示例

1. HttpOnly Cookie存储(Web应用优先推荐)

HttpOnly Cookie能有效抵御XSS攻击,是Web场景下最安全的Token存储方式之一。修改你的登录方法,获取Token后存入Cookie:

[HttpPost]
public async Task<ActionResult<string>> Login([FromForm] LoginViewModel model)
{
    var httpClient = _httpClientFactory.CreateClient("HousingWebAPI");           
    var body = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json");
    using (var posting = await httpClient.PostAsync("Login/login", body))
    {              
        posting.EnsureSuccessStatusCode();
        var content = await posting.Content.ReadAsStringAsync();
        // 反序列化API返回的认证结果
        var authResult = JsonConvert.DeserializeObject<AuthResult>(content);
        
        // 配置Cookie参数
        var cookieOptions = new CookieOptions
        {
            HttpOnly = true, // 禁止前端JS读取,防范XSS
            Secure = true, // 仅HTTPS传输,生产环境必须启用
            SameSite = SameSiteMode.Strict, // 防范CSRF攻击
            Expires = DateTimeOffset.UtcNow.AddHours(1) // 与API返回的Token有效期保持一致
        };
        Response.Cookies.Append("AuthToken", authResult.Token, cookieOptions);
        
        return Ok(content);
    }
    return NotFound();
}

// 用于反序列化API返回结果的模型类
public class AuthResult
{
    public string Token { get; set; }
    // 可按需添加过期时间、用户名等其他字段
}

2. 数据库存储(多设备持久化场景)

如果需要支持用户多设备登录,可将Token关联用户ID存入数据库,同时用Cookie存储Refresh Token用于续期:

// 登录时的存储逻辑示例
var authResult = JsonConvert.DeserializeObject<AuthResult>(content);
var userToken = new UserToken
{
    UserId = 当前用户ID, // 从登录结果或会话中获取用户ID
    AccessToken = authResult.Token,
    ExpiresAt = DateTime.UtcNow.AddHours(1),
    RefreshToken = Guid.NewGuid().ToString()
};
// 写入数据库
await _userTokenRepository.AddAsync(userToken);

// 将Refresh Token存入HttpOnly Cookie
var refreshCookieOptions = new CookieOptions
{
    HttpOnly = true,
    Secure = true,
    SameSite = SameSiteMode.Strict,
    Expires = DateTimeOffset.UtcNow.AddDays(7)
};
Response.Cookies.Append("RefreshToken", userToken.RefreshToken, refreshCookieOptions);

二、在PostData方法中添加Authorization头

从存储位置(以Cookie为例)读取Token,添加到HttpClient的请求头即可:

public async Task<ActionResult<string>> PostData([FromForm] DataTableAjaxPostModel model)
{
    // 从Cookie中获取Token
    var token = Request.Cookies["AuthToken"];
    if (string.IsNullOrEmpty(token))
    {
        return Unauthorized("未获取到认证Token");
    }

    using var client = new HttpClient();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    // 添加Bearer Token到Authorization头
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    
    var body = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json");
              
    var response = await client.PostAsync("https://localhost:7193/HousingWebAPI/GetData", body);
    if (response.IsSuccessStatusCode)
    {
        var content = await response.Content.ReadAsStringAsync();
        return Ok(content);
    }
    return NotFound();
}

优化建议:使用HttpClientFactory创建客户端

避免每次请求创建新HttpClient,复用IHttpClientFactory可以防止Socket资源耗尽问题:

private readonly IHttpClientFactory _httpClientFactory;

// 构造函数注入HttpClientFactory
public YourController(IHttpClientFactory httpClientFactory)
{
    _httpClientFactory = httpClientFactory;
}

public async Task<ActionResult<string>> PostData([FromForm] DataTableAjaxPostModel model)
{
    var token = Request.Cookies["AuthToken"];
    if (string.IsNullOrEmpty(token))
    {
        return Unauthorized("未获取到认证Token");
    }

    var client = _httpClientFactory.CreateClient("HousingWebAPI"); // 使用预配置的命名客户端
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    
    var body = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json");
              
    // 命名客户端已配置BaseAddress,无需写完整URL
    var response = await client.PostAsync("HousingWebAPI/GetData", body);
    if (response.IsSuccessStatusCode)
    {
        var content = await response.Content.ReadAsStringAsync();
        return Ok(content);
    }
    return NotFound();
}

内容的提问来源于stack exchange,提问作者Jose Daniel Navarro Brito

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:43:23