Copy using Jose;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
namespace BlazorServerTutor.Services
{
/// <summary>
/// 請使用 AddSingleton Injection.
/// </summary>
public class AccountService
{
#region resource
public record Ticket
{
public Guid ticketId;
public string userId;
public string returnUrl;
public DateTime expires;
}
public record AuthUser
{
public string UserId { get; init; }
public string UserName { get; init; }
public string DeptId { get; init; }
public string DeptName { get; init; }
public string Email { get; init; }
public string[] Roles { get; init; }
public string[] AuthFuncList { get; init; }
public Guid AuthGuid { get; set; }
public DateTimeOffset IssuedUtc { get; set; }
public DateTimeOffset ExpiresUtc { get; set; }
}
#endregion
//# Injection Member
readonly ILogger<AccountService> _logger;
readonly IConfiguration _config;
readonly IMemoryCache _cache;
/// <summary>
/// 門票緩衝池
/// </summary>
readonly Dictionary<Guid, Ticket> _ticketPool = new();
readonly object _lockObj = new object();
/// <summary>
/// ctor
/// </summary>
public AccountService(ILogger<AccountService> logger, IConfiguration config, IMemoryCache cache)
{
_logger = logger;
_config = config;
_cache = cache;
}
protected string PackageTicketToken(Guid ticketId)
{
// 有AES256加密與HASH, 密碼需32字元256bit
byte[] key256 = Encoding.UTF8.GetBytes(_config["Jwt:Key256"]);
string token = JWT.Encode(ticketId, key256, JweAlgorithm.A256KW, JweEncryption.A256CBC_HS512);
return token;
}
protected Guid UnpackageTicketToken(string token)
{
// 有AES256加密與HASH, 密碼需32字元256bit
byte[] key256 = Encoding.UTF8.GetBytes(_config["Jwt:Key256"]);
var ticketId = JWT.Decode<Guid>(token, key256);
return ticketId;
}
/// <summary>
/// 認證檢查
/// </summary>
public string Authenticate(string userId, string credential, string vcode, string returnUrl)
{
if (String.IsNullOrWhiteSpace(userId))
return null;
if (String.IsNullOrWhiteSpace(credential))
return null;
//## verify vcode;
if (!"123456".Equals(vcode))
return null;
//## 驗證帳號與密碼
// ...先假設成功...
//## 製作 ticket
var ticket = new Ticket
{
ticketId = Guid.NewGuid(),
userId = userId,
returnUrl = returnUrl,
expires = DateTime.Now.AddSeconds(5)
};
lock (_lockObj)
{
_ticketPool.Add(ticket.ticketId, ticket);
}
//# success
string ticketToken = this.PackageTicketToken(ticket.ticketId);
return ticketToken;
}
/// <summary>
/// 取得授權資料
/// </summary>
public AuthUser Authorize(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
throw new ArgumentNullException(nameof(userId));
AuthUser authUser = null;
// 先假設驗證一定成功
if (userId == "admin")
{
authUser = new AuthUser
{
UserId = userId,
UserName = "管海邊",
DeptId = "D638",
DeptName = "資訊部",
Email = "admin@mail.server",
Roles = new[] { "Admin", "User" },
AuthFuncList = new[] { "AP0101", "AP0102", "AP0201" },
AuthGuid = Guid.NewGuid(),
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(20)
};
}
else
{
authUser = new AuthUser
{
UserId = userId,
UserName = "郝聰明",
DeptId = "D638",
DeptName = "資訊部",
Email = "smart@mail.server",
Roles = new[] { "User" },
AuthFuncList = new[] { "AP0101", "AP0102", "AP0201" },
AuthGuid = Guid.NewGuid(),
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(20)
};
}
lock (_lockObj)
{
///
///※ 授權資料建議存入Database,可用 MemoryCache 加速。
///
_cache.Set<AuthUser>($"AuthData:{userId}", authUser);
/// 此處開發測試中,暫不考慮資料庫部份。
}
// success
_logger.LogInformation($"Authenticate {userId}");
return authUser;
}
public AuthUser GetAuthDataFromPool(string userId)
{
lock (_lockObj)
{
var authUser = _cache.Get<AuthUser>($"AuthData:{userId}");
// 若已過時,則清除
if (authUser != null && DateTime.UtcNow > authUser.ExpiresUtc)
{
_cache.Remove($"AuthData:{userId}");
return null;
}
return authUser;
}
}
/// <summary>
/// 取出(登入認證)門票
/// </summary>
public Ticket TakeOutTicket(string ticketToken)
{
Guid ticketId = this.UnpackageTicketToken(ticketToken);
lock (_lockObj)
{
Ticket ticket;
_ticketPool.Remove(ticketId, out ticket);
return ticket;
}
}
/// <summary>
/// 解開 ClaimsIdentity,解開使用者的識別聲明資訊。
/// </summary>
public AuthUser UnpackUserClaimsData(System.Security.Principal.IIdentity identity)
{
var claimsIdentity = (System.Security.Claims.ClaimsIdentity)identity;
var authUserJson = claimsIdentity.FindFirst(System.Security.Claims.ClaimTypes.UserData).Value;
return JsonSerializer.Deserialize<AuthUser>(authUserJson);
}
/// <summary>
/// 封裝 ClaimsIdentity: 將使用者的登入資訊包裝成 ClaimsIdentity 以用於 Cookie-Base Auth.。
/// </summary>
public ClaimsIdentity PackUserClaimsData(AuthUser authUser)
{
// 使用者聲明
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, authUser.UserId),
new Claim(ClaimTypes.Sid, authUser.AuthGuid.ToString()), // 登入識別序號
new Claim(ClaimTypes.GivenName, authUser.UserName) // 顯示名稱
};
// 『角色』可能有多個
foreach (string role in authUser.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
return claimsIdentity;
}
}
}