.NET API中IsSignedIn始终返回true的问题求助
问题:调用Logout接口后IsSignedIn仍返回true的原因及解决办法
我确信自己忽略了某个明显的要点,想请教为何在我的.NET API中,调用Logout接口后,IsSignedIn仍始终返回true?
相关代码
AuthenticationController.cs
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using Registry_Backend.DTO; using Registry_Backend.Models; using Registry_Backend.Shared; using System.IdentityModel.Tokens.Jwt; using System.Net; using System.Security.Claims; using System.Text; namespace Registry_Backend.Controllers { [ApiController] [Route("api/[controller]")] public class AuthenticationController : ControllerBase { private readonly SignInManager<IdentityUser> signInManager; private readonly RegistryContext dbContext; private readonly UserManager<IdentityUser> userManager; public AuthenticationController(RegistryContext dbContext, SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager) { this.dbContext = dbContext; this.signInManager = signInManager; this.userManager = userManager; } [HttpPost("Login")] [ProducesResponseType(typeof(JWTTokenResponse), StatusCodes.Status200OK)] public async Task<IActionResult> LoginAsync([FromBody] LoginData loginData) { if (loginData is null) { throw new AppException("Invalid user request!!!"); } else { var result = await signInManager.PasswordSignInAsync(loginData.UserName, loginData.Password, false, false); if (result.Succeeded) { var user = await userManager.FindByNameAsync(loginData.UserName); if (user != null) { var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManagerExtension.AppSetting["JWT:Secret"])); var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); var tokeOptions = new JwtSecurityToken( issuer: ConfigurationManagerExtension.AppSetting["JWT:ValidIssuer"], audience: ConfigurationManagerExtension.AppSetting["JWT:ValidAudience"], claims: new List<Claim>(), expires: DateTime.Now.AddMinutes(6), signingCredentials: signinCredentials); var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions); return Ok(new JWTTokenResponse { Token = tokenString, UserId = user.Id }); } } } return Unauthorized(); } [HttpPost("Logout")] [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] public async Task<IActionResult> LogoutAsync() { await signInManager.SignOutAsync(); return Ok("OK"); } [HttpGet("IsLoggedIn")] [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] public async Task<IActionResult> IsLoggedIn([FromQuery] string userId) { var user = await userManager.FindByIdAsync(userId); if(user != null) { var claims = await signInManager.CreateUserPrincipalAsync(user); return Ok(signInManager.IsSignedIn(claims)); } return Ok(false); } } }
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Registry_Backend; using Registry_Backend.Models; using Registry_Backend.Shared; using System.Text; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Scheme = "Bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Name = "Authorization", Description = "Bearer Authentication with JWT Token", Type = SecuritySchemeType.Http }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }, new List < string > () } }); }); builder.Services.AddCors(options => { options.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); builder.Services.AddDbContext<RegistryContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => options.Lockout.AllowedForNewUsers = false ).AddEntityFrameworkStores<RegistryContext>(); builder.Services.AddAuthentication(opt => { opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = ConfigurationManagerExtension.AppSetting["JWT:ValidIssuer"], ValidAudience = ConfigurationManagerExtension.AppSetting["JWT:ValidAudience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManagerExtension.AppSetting["JWT:Secret"])) }; }); var app = builder.Build(); app.UseCors("AllowAll"); app.UseMiddleware<ErrorHandlerMiddleware>(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
问题原因分析
Logout接口无效:
signInManager.SignOutAsync()是针对Cookie认证的注销逻辑,但你的API使用的是JWT认证。JWT是无状态令牌,服务端不会存储用户会话信息,所以这个方法无法清除JWT的认证状态。IsLoggedIn逻辑错误:你通过
signInManager.CreateUserPrincipalAsync(user)手动创建了一个用户主体,再调用IsSignedIn(claims)判断登录状态。但IsSignedIn方法的正确用法是检查当前请求上下文(HttpContext.User)中的用户是否已认证,而你手动创建的主体和当前请求的认证状态完全无关,所以无论用户是否注销,这个方法都会返回true。
解决方案
1. 修正Logout接口
JWT的注销只能由客户端完成:客户端需要删除本地存储的JWT令牌(比如localStorage、sessionStorage),服务端无需做额外操作。修改Logout接口如下:
[HttpPost("Logout")] [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] public IActionResult LogoutAsync() { // JWT无状态,通知客户端删除令牌即可 return Ok("请客户端删除本地存储的JWT令牌"); }
2. 修正IsLoggedIn接口
需要通过当前请求的认证状态判断,要求客户端携带JWT令牌访问该接口:
[HttpGet("IsLoggedIn")] [Authorize] // 必须携带有效JWT才能访问 public IActionResult IsLoggedIn() { // 直接判断当前请求的用户是否已认证 return Ok(User.Identity.IsAuthenticated); }
3. 额外优化(可选)
如果需要主动失效JWT令牌,可以实现令牌黑名单机制(比如用Redis存储已注销的令牌ID,直到令牌过期),但你的令牌过期时间只有6分钟,短期令牌可以依赖过期时间,无需额外实现。
内容的提问来源于stack exchange,提问作者Ferenc Dajka
相关产品推荐
相关产品推荐

