滑动续期与自动刷新:
Web:访问令牌 10 分钟;活跃时自动轮换刷新令牌;连续无操作 30 分钟后清除登录并跳转登录页。 App:会话窗口 3 天;打开 App、恢复前台或请求接口时自动刷新并重新顺延 3 天。 普通登录和 SSO 使用同一策略。 刷新令牌只以 SHA-256 摘要入库,每次刷新都会轮换,旧令牌无法再次使用;退出登录会吊销刷新令牌。 网络临时故障不会误清登录状态,多标签页同时刷新也做了竞争处理。
This commit is contained in:
@@ -18,7 +18,7 @@ namespace Jiaowu.Api.Controllers;
|
||||
public sealed class AuthController(
|
||||
AppDbContext db,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
ITokenService tokenService,
|
||||
IAuthSessionService authSessionService,
|
||||
IAppCache cache) : ControllerBase
|
||||
{
|
||||
[AllowAnonymous]
|
||||
@@ -135,8 +135,11 @@ public sealed class AuthController(
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[EnableRateLimiting("public-auth")]
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<LoginResponse>> Login(LoginRequest request)
|
||||
public async Task<ActionResult<LoginResponse>> Login(
|
||||
LoginRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.FindByNameAsync(request.UserName);
|
||||
if (user is null || !user.IsEnabled)
|
||||
@@ -166,15 +169,47 @@ public sealed class AuthController(
|
||||
await userManager.UpdateAsync(user);
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
|
||||
return new LoginResponse(
|
||||
tokenService.Create(user, roles),
|
||||
new CurrentUserResponse(
|
||||
user.Id,
|
||||
user.UserName!,
|
||||
user.DisplayName,
|
||||
roles,
|
||||
user.CollegeId,
|
||||
EffectiveDataScopeResolver.Resolve(roles).ToString()));
|
||||
var session = await authSessionService.CreateAsync(
|
||||
user,
|
||||
roles,
|
||||
request.IsNativeApp
|
||||
? AuthenticationClientType.App
|
||||
: AuthenticationClientType.Web,
|
||||
cancellationToken);
|
||||
return CreateLoginResponse(session);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[EnableRateLimiting("token-refresh")]
|
||||
[HttpPost("refresh")]
|
||||
public async Task<ActionResult<LoginResponse>> Refresh(
|
||||
RefreshTokenRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var session = await authSessionService.RefreshAsync(
|
||||
request.RefreshToken,
|
||||
cancellationToken);
|
||||
if (session is null)
|
||||
{
|
||||
return Unauthorized(new ProblemDetails
|
||||
{
|
||||
Title = "登录已过期",
|
||||
Detail = "登录已过期或刷新令牌已失效,请重新登录。",
|
||||
Status = StatusCodes.Status401Unauthorized
|
||||
});
|
||||
}
|
||||
|
||||
return CreateLoginResponse(session);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout(
|
||||
RefreshTokenRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await authSessionService.RevokeAsync(request.RefreshToken, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
@@ -212,11 +247,29 @@ public sealed class AuthController(
|
||||
Detail = detail,
|
||||
Status = status
|
||||
});
|
||||
|
||||
internal static LoginResponse CreateLoginResponse(AuthSessionResult session) =>
|
||||
new(
|
||||
session.AccessToken,
|
||||
session.AccessTokenExpiresAt,
|
||||
session.RefreshToken,
|
||||
session.SessionExpiresAt,
|
||||
new CurrentUserResponse(
|
||||
session.User.Id,
|
||||
session.User.UserName!,
|
||||
session.User.DisplayName,
|
||||
session.Roles,
|
||||
session.User.CollegeId,
|
||||
EffectiveDataScopeResolver.Resolve(session.Roles).ToString()));
|
||||
}
|
||||
|
||||
public sealed record LoginRequest(
|
||||
[Required, MaxLength(100)] string UserName,
|
||||
[Required, MaxLength(100)] string Password);
|
||||
[Required, MaxLength(100)] string Password,
|
||||
bool IsNativeApp = false);
|
||||
|
||||
public sealed record RefreshTokenRequest(
|
||||
[Required, MinLength(40), MaxLength(200)] string RefreshToken);
|
||||
|
||||
public sealed record StudentActivationRequest(
|
||||
[Required, MaxLength(50)] string Name,
|
||||
@@ -227,7 +280,12 @@ public sealed record StudentActivationRequest(
|
||||
Guid AdministrativeClassId,
|
||||
[Required, MinLength(8), MaxLength(100)] string Password);
|
||||
|
||||
public sealed record LoginResponse(string Token, CurrentUserResponse User);
|
||||
public sealed record LoginResponse(
|
||||
string Token,
|
||||
DateTime AccessTokenExpiresAt,
|
||||
string RefreshToken,
|
||||
DateTime SessionExpiresAt,
|
||||
CurrentUserResponse User);
|
||||
|
||||
public sealed record CurrentUserResponse(
|
||||
Guid Id,
|
||||
|
||||
Reference in New Issue
Block a user