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

三层服务架构下OAuth2授权码流程Session异常问题咨询

授权码流程跨服务会话丢失问题

架构概述

  • 第一部分:基于C# Web API的自定义授权服务器,遵循授权码流程返回令牌,负责用户信息处理。
  • 第二部分:基于TypeScript的React前端,包含授权服务器管理面板和登录表单页面。
  • 第三部分:ASP.NET Core MVC应用(单实例),兼具前后端功能。

问题复现流程

  1. 访问https://third.com/verysecretpage时,授权过滤器检查Session中是否存在令牌,若不存在则重定向到React登录页https://second:3000/auth?clientId=b4db6042-ae11-47ca-a1e7-c737fb0ee7f1&state=987654321,过滤器代码如下:
public void OnAuthorization(AuthorizationFilterContext context)
{
    if (IsAnonymousAction(context)) return;

    string? accessToken = context.HttpContext.Session.GetString("accessToken");

    if (string.IsNullOrEmpty(accessToken))
        context.Result = new RedirectResult($"https://second:3000/auth?clientId=b4db6042-ae11-47ca-a1e7-c737fb0ee7f1&state=987654321");
}

private static bool IsAnonymousAction(AuthorizationFilterContext filterContext)
{
    return filterContext.ActionDescriptor.EndpointMetadata.Any(em => em.GetType() == typeof(AllowAnonymousAttribute));
}
  1. 在React登录页提交用户名密码后,向授权服务器发起请求,代码如下:
async function handleSubmit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    const username = formData.get("username")!.toString();
    const password = formData.get("password")!.toString();
    
    const fetchURL = "https://first.com/auth-code";
    const data = {
        "responseType": "code",
        // 此处传递查询参数中的clientId和state
        "clientId": clientId,
        "state": state
    };
    await fetch(fetchURL, {
        method: 'POST',
        body: JSON.stringify(data),
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Basic ' + btoa(`${username}:${password}`),
        },
        redirect: "follow"
    }).then(
        response => { window.location.href = "https://third.com/verysecretpage" });
}
  1. 授权服务器返回302重定向到https://third.com/receive-tokens,该端点成功获取并保存令牌到Session,代码如下:
[Route("/receive-tokens")]
[AllowAnonymous]
[EnableCors("Interface")]
public async Task<IActionResult> ReceiveTokens(string code, string? status = null)
{
    var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Post, "https://first.com/token");
    var content = new StringContent("{" + $"\n  \"grantType\": \"authorization_code\",\n  \"code\": \"{code}\",\n  \"clientId\": \"b4db6042-ae11-47ca-a1e7-c737fb0ee7f1\"\n" + "}", null, "application/json");
    request.Content = content;
    var response = await client.SendAsync(request);
    response.EnsureSuccessStatusCode();
    // 授权服务器通过HttpOnly Cookie发送RefreshToken,此处获取
    var refreshToken = CookieGetter.GetRefreshTokenCookie(response);
    string responseContents = await response.Content.ReadAsStringAsync();
    var accessTokenResponse = JsonSerializer.Deserialize<AccessTokenResponse>(responseContents, jsonSerializerOptions)!;
    var tokensResponse = new TokensResponse { AccessTokenResponse = accessTokenResponse, RefreshToken = refreshToken };

    auth.SaveTokenValues(tokensResponse);

    return Ok();
}

// 来自Models中的Auth类
public void SaveTokenValues(TokensResponse tokensResponse)
{
    _contextAccessor.HttpContext!.Session.SetString("accessToken", tokensResponse.AccessTokenResponse.AccessToken);
    _contextAccessor.HttpContext.Session.SetString("expiresAt", tokensResponse.AccessTokenResponse.ExpiresAt.ToString());
    _contextAccessor.HttpContext.Session.SetString("refreshToken", tokensResponse.RefreshToken);
}

// 上述用到的部分类
public class TokensResponse
{
    public required AccessTokenResponse AccessTokenResponse { get; set; }
    public required string RefreshToken { get; set; }

}

public class AccessTokenResponse
{
    public required string TokenType { get; set; }

    public required string AccessToken { get; set; }

    public required DateTime ExpiresAt { get; set; }
}

Session配置:

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(10);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});
  1. React收到200响应后重定向到https://third.com/verysecretpage,但此时Session中无令牌,导致循环重定向。

疑问点

  • ASP.NET Core的Session是否仅在当前请求上下文有效,跨域名/服务后无法延续?
  • 是否应该改用ASP.NET Core内置授权机制?
  • 有没有跨服务/域名保存会话的方法?
  • 能否让React不等待receive-tokens的响应,直接重定向到MVC页面完成令牌保存?
  • 是否必须将登录页放在ASP.NET Core端,不能单独用React客户端?

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 20:18:08