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

ASP.NET Core 6+Angular应用IIS部署:会话超时与登录跳转修复

问题

我开发了一个基于ASP.NET Core 6、前端使用Angular的Web应用,并实现了基于Microsoft.AspNetCore.Authentication.AuthenticationHandler的自定义认证处理器,对接第三方自定义API完成认证。当前应用初始化代码如下:

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContextFactory<DatabaseContextName>();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

builder.Services.AddAuthentication(CustomAuthDefaults.AuthenticationScheme)
   .AddScheme<CustomAuthSchemeOptions, CustomAuthHandler>(
        CustomAuthDefaults.AuthenticationScheme, options => { });
builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddControllers();
builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromHours(8);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});

WebApplication app = builder.Build();

app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

// Backend-Routes for the treatment of 404-Errors
app.Map("api/{**slug}", HandleApiFallback);

// Fallback-Route to the Frontend
app.MapFallbackToFile("{**slug}", "index.html");

app.Run();

static Task HandleApiFallback(HttpContext context)
{
    context.Response.StatusCode = StatusCodes.Status404NotFound;
    return Task.CompletedTask;
}

Angular前端已配置HttpInterceptor,在用户未认证时跳转至登录页,该逻辑在Kestrel主机上正常运行,但部署到IIS后完全失效:

  • IIS忽略AddSession配置的8小时会话超时,实际几分钟就过期
  • 会话过期后阻断所有HTTP请求,刷新页面时返回HTTP 401错误并显示空白页,而非跳转至登录页

尝试过在AddAuthentication后添加.AddCookie(options => options.LoginPath = "/login"),或配置builder.Services.ConfigureApplicationCookie(options => options.LoginPath = "/login"),均未解决问题。需要配置应用确保IIS下会话超时正确,并修复会话过期后的登录跳转问题。

解决方案

一、修复IIS下的会话超时问题

  1. 同步IIS应用池超时与Session配置
    IIS应用池默认闲置超时为20分钟,超时后会回收进程,直接清空内存中的Session(当前使用DistributedMemoryCache是进程内缓存)。需修改应用池设置:
  • 打开IIS管理器,找到对应应用的应用池
  • 右键→高级设置→进程模型→闲置超时(分钟),设置为480分钟(8小时)
  • 同时调整“回收”选项中的固定时间间隔为超过8小时(或设为0禁用)
  1. 补全会话Cookie的过期配置
    当前仅设置了Session的IdleTimeout,但Cookie默认是会话级(关闭浏览器即失效),需显式匹配Cookie过期时间:
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromHours(8);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
    // 添加Cookie过期时间,与IdleTimeout保持一致
    options.Cookie.Expires = TimeSpan.FromHours(8);
});
  1. 改用持久化分布式缓存(可选但推荐)
    DistributedMemoryCache依赖进程内存,IIS进程回收会丢失所有缓存数据。若要提升可靠性,可替换为Redis或SQL Server缓存:
// 示例:使用Redis缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "AppInstance";
});

二、修复会话过期后的登录跳转问题

自定义认证处理器未处理401响应的跳转逻辑,AddCookie配置仅对Cookie认证方案生效,需针对自定义Scheme做适配:

  1. 重写自定义认证处理器的HandleChallengeAsync方法
    在CustomAuthHandler中添加逻辑,区分API请求与页面请求的响应方式:
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
    // API请求返回401,让前端Interceptor处理跳转
    if (Context.Request.Path.StartsWithSegments("/api"))
    {
        Response.StatusCode = StatusCodes.Status401Unauthorized;
        await Response.WriteAsJsonAsync(new { IsAuthenticated = false, RedirectTo = "/login" });
    }
    // 页面请求直接重定向到前端登录页
    else
    {
        Response.Redirect("/login");
    }
}
  1. 调整授权策略,允许匿名访问静态资源与登录页
    避免授权拦截前端静态文件和登录路由:
builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes(CustomAuthDefaults.AuthenticationScheme)
        .Build();
});

// 中间件中跳过静态资源和登录页的授权检查
app.Use(async (context, next) =>
{
    var path = context.Request.Path.Value;
    if (path.StartsWith("/css/") || path.StartsWith("/js/") || path.StartsWith("/assets/") || path == "/login" || path == "/")
    {
        await next();
        return;
    }
    await next();
});
  1. 确认前端Interceptor逻辑正确
    确保Angular拦截器捕获401后执行跳转:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private router: Router) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          localStorage.removeItem('authToken');
          this.router.navigate(['/login']);
        }
        return throwError(() => error);
      })
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 04:20:17