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

Angular12+DotNet实现闲置超时弹窗后关闭标签页自动登出

解决闲置超时弹窗未操作关闭标签页自动登出及多标签会话同步问题

Angular 前端实现

1. 替换阻塞式confirm为自定义非阻塞弹窗

原生confirm是同步阻塞逻辑,页面卸载时无法触发后续登出操作。改用Angular自定义弹窗组件(或Material的MatDialog),同时标记弹窗状态,监听页面卸载事件。

示例代码:

import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Component, OnInit } from '@angular/core';
import { MsalService } from '@azure/msal-angular';

// 自定义会话超时弹窗组件
@Component({
  template: `
    <h3>会话过期提醒</h3>
    <p>您的会话已过期,请选择操作:</p>
    <div style="display: flex; gap: 1rem;">
      <button (click)="handleRefresh()">刷新页面</button>
      <button (click)="handleLogout()">登出账号</button>
    </div>
  `
})
export class SessionTimeoutDialogComponent {
  constructor(private dialogRef: MatDialogRef<SessionTimeoutDialogComponent>) {}

  handleRefresh() {
    this.dialogRef.close('refresh');
  }

  handleLogout() {
    this.dialogRef.close('logout');
  }
}

// 业务组件中集成逻辑
@Component({ /* 组件元数据 */ })
export class YourComponent implements OnInit {
  private isTimeoutDialogActive = false;

  constructor(
    private dialog: MatDialog,
    private msalService: MsalService,
    private document: Document
  ) {}

  ngOnInit(): void {
    // 监听闲置超时事件
    this.onidletimeout.subscribe(() => {
      this.isTimeoutDialogActive = true;
      const dialogRef = this.dialog.open(SessionTimeoutDialogComponent);

      dialogRef.afterClosed().subscribe(result => {
        this.isTimeoutDialogActive = false;
        if (result === 'refresh') {
          this.document.location.reload();
        } else if (result === 'logout') {
          this.triggerLogout();
        }
      });
    });

    // 监听页面卸载事件,弹窗激活时强制登出
    window.addEventListener('beforeunload', () => {
      if (this.isTimeoutDialogActive) {
        this.triggerLogout();
      }
    });
  }

  private triggerLogout(): void {
    this.msalService.logout();
    // 发送跨标签页登出通知
    this.broadcastLogoutSignal();
  }

  private broadcastLogoutSignal(): void {
    // 用BroadcastChannel实现跨标签页通信
    const channel = new BroadcastChannel('session-sync');
    channel.postMessage('logout');
    channel.close();
  }
}

2. 多标签页会话同步

通过BroadcastChannel监听跨标签页的登出信号,收到信号后自动清理当前标签页会话:

// 在AppComponent或核心服务中初始化监听
export class AppComponent implements OnInit {
  constructor(private msalService: MsalService) {}

  ngOnInit(): void {
    const channel = new BroadcastChannel('session-sync');
    channel.onmessage = (event) => {
      if (event.data === 'logout') {
        this.msalService.logout();
      }
    };
  }
}

.NET 后端实现

1. 对齐会话与Token过期时间

确保后端会话、JWT Token的过期时间与前端900秒(15分钟)一致:

会话配置(Program.cs/.NET 6+)

builder.Services.AddSession(options => {
  options.IdleTimeout = TimeSpan.FromSeconds(900);
  options.Cookie.HttpOnly = true;
  options.Cookie.IsEssential = true;
  options.Cookie.SameSite = SameSiteMode.None;
  options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

// 注册会话中间件
app.UseSession();

JWT Token配置

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddJwtBearer(options => {
    options.TokenValidationParameters = new TokenValidationParameters {
      ValidateIssuer = true,
      ValidateAudience = true,
      ValidateLifetime = true,
      ValidIssuer = builder.Configuration["Jwt:Issuer"],
      ValidAudience = builder.Configuration["Jwt:Audience"],
      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
      ClockSkew = TimeSpan.Zero // 严格验证过期时间,关闭时钟偏差容错
    };
  });

// 生成Token时设置过期时间
var tokenDescriptor = new SecurityTokenDescriptor {
  Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, user.UserName) }),
  Expires = DateTime.UtcNow.AddSeconds(900),
  SigningCredentials = new SigningCredentials(
    new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
    SecurityAlgorithms.HmacSha256Signature
  ),
  Issuer = builder.Configuration["Jwt:Issuer"],
  Audience = builder.Configuration["Jwt:Audience"]
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);

2. API会话有效性验证

在接口中添加会话/Token校验,过期则返回401,触发前端登出逻辑:

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase {
  [HttpGet("user-info")]
  public IActionResult GetUserInfo() {
    // 检查会话有效性
    if (!HttpContext.Session.IsAvailable || HttpContext.Session.GetString("UserId") == null) {
      return Unauthorized();
    }
    // 业务逻辑
    return Ok(new { Name = HttpContext.Session.GetString("UserName") });
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 21:36:32