身份验证
This commit is contained in:
@@ -1,40 +1,139 @@
|
||||
using System.Threading;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.HTTP;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace NBF;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[ServiceFilter(typeof(SceneContextFilter))]
|
||||
public class AuthController : ControllerBase
|
||||
public class AuthController : NBControllerBase
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, CaptchaInfo> CaptchaCache = new();
|
||||
private static readonly TimeSpan CaptchaLifetime = TimeSpan.FromMinutes(10);
|
||||
private readonly Scene _scene;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数依赖注入
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
public AuthController(Scene scene)
|
||||
{
|
||||
_scene = scene;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("login")]
|
||||
public async FTask<IActionResult> Login()
|
||||
[AllowAnonymous]
|
||||
public async FTask<IActionResult> Login([FromQuery] string account, [FromQuery] string code)
|
||||
{
|
||||
await DingTalkHelper.SendCAPTCHA("123456");
|
||||
if (string.IsNullOrWhiteSpace(account) || string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
await FTask.CompletedTask;
|
||||
return Error(ErrorCode.ArgsError, "account or code is empty");
|
||||
}
|
||||
|
||||
var key = account.Trim();
|
||||
if (!CaptchaCache.TryGetValue(key, out var captchaInfo))
|
||||
{
|
||||
await FTask.CompletedTask;
|
||||
return Error(ErrorCode.ArgsError, "captcha not found");
|
||||
}
|
||||
|
||||
if (captchaInfo.ExpireAtUtc <= DateTime.UtcNow)
|
||||
{
|
||||
CaptchaCache.TryRemove(key, out _);
|
||||
await FTask.CompletedTask;
|
||||
return Error(ErrorCode.ArgsError, "captcha expired");
|
||||
}
|
||||
|
||||
if (!string.Equals(captchaInfo.Code, code.Trim(), StringComparison.Ordinal))
|
||||
{
|
||||
await FTask.CompletedTask;
|
||||
return Error(ErrorCode.ArgsError, "captcha invalid");
|
||||
}
|
||||
|
||||
CaptchaCache.TryRemove(key, out _);
|
||||
var (token, expireAtUtc) = GenerateJwtToken(key);
|
||||
|
||||
await FTask.CompletedTask;
|
||||
return Ok($"Hello from the Fantasy controller! _scene.SceneType:{_scene.SceneType} _scene.SceneType:{_scene.SceneConfigId}");
|
||||
return Success(new
|
||||
{
|
||||
token,
|
||||
expireAtUtc
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("code")]
|
||||
public async FTask<IActionResult> SendCode()
|
||||
[AllowAnonymous]
|
||||
public async FTask<IActionResult> SendCode([FromQuery] string account)
|
||||
{
|
||||
await DingTalkHelper.SendCAPTCHA("123456");
|
||||
await FTask.CompletedTask;
|
||||
return Ok($"Hello from the Fantasy controller! _scene.SceneType:{_scene.SceneType} _scene.SceneType:{_scene.SceneConfigId}");
|
||||
if (string.IsNullOrWhiteSpace(account))
|
||||
{
|
||||
await FTask.CompletedTask;
|
||||
return Error(ErrorCode.ArgsError, "account is empty");
|
||||
}
|
||||
|
||||
var key = account.Trim();
|
||||
var captchaCode = RandomNumberGenerator.GetInt32(0, 1_000_000).ToString("D6");
|
||||
var expireAtUtc = DateTime.UtcNow.Add(CaptchaLifetime);
|
||||
|
||||
CaptchaCache[key] = new CaptchaInfo
|
||||
{
|
||||
Code = captchaCode,
|
||||
ExpireAtUtc = expireAtUtc
|
||||
};
|
||||
|
||||
await DingTalkHelper.SendCAPTCHA(account, captchaCode);
|
||||
|
||||
return Success();
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public IActionResult Me()
|
||||
{
|
||||
var account = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty;
|
||||
return Success(new
|
||||
{
|
||||
account
|
||||
});
|
||||
}
|
||||
|
||||
private static (string Token, DateTime ExpireAtUtc) GenerateJwtToken(string account)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var expireAtUtc = now.Add(ApiJwtHelper.TokenLifetime);
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, account),
|
||||
new Claim(ClaimTypes.Name, account),
|
||||
new Claim(ClaimTypes.Role, "Player")
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = expireAtUtc,
|
||||
NotBefore = now,
|
||||
Issuer = ApiJwtHelper.Issuer,
|
||||
Audience = ApiJwtHelper.Audience,
|
||||
SigningCredentials = new SigningCredentials(
|
||||
ApiJwtHelper.CreateSigningKey(),
|
||||
SecurityAlgorithms.HmacSha256)
|
||||
});
|
||||
|
||||
return (tokenHandler.WriteToken(token), expireAtUtc);
|
||||
}
|
||||
|
||||
private sealed class CaptchaInfo
|
||||
{
|
||||
public string Code { get; init; } = string.Empty;
|
||||
public DateTime ExpireAtUtc { get; init; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user