IdentityModel Microsoft.AspNetCore.Authorization.JwtBearer
"Authentication": { "JwtBearer": { "IsEnabled": "true", "SecurityKey": "JWTStudyWebsite_DI20DXU3", "Issuer": "JWTStudy", "Audience": "JWTStudyWebsite" } }
public static class JwtConfiguration { public static void AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration) { if (bool.Parse(configuration["Authentication:JwtBearer:IsEnabled"])) { services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "JwtBearer"; options.DefaultChallengeScheme = "JwtBearer"; }).AddJwtBearer("JwtBearer", options => { options.Audience = configuration["Authentication:JwtBearer:Audience"]; options.TokenValidationParameters = new TokenValidationParameters { // The signing key must match! ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.ASCII.GetBytes(configuration["Authentication:JwtBearer:SecurityKey"])), // Validate the JWT Issuer (iss) claim ValidateIssuer = true, ValidIssuer = configuration["Authentication:JwtBearer:Issuer"], // Validate the JWT Audience (aud) claim ValidateAudience = true, ValidAudience = configuration["Authentication:JwtBearer:Audience"], // Validate the token expiry ValidateLifetime = true, // If you want to allow a certain amount of clock drift, set that here ClockSkew = TimeSpan.Zero }; }); } } }
services.AddJwtConfiguration(Configuration);
說明:用戶首次使用用戶名和密碼登陸,生成AccessToken和RefreshToken, 其中AccessToken的有效時間爲30分鐘,RefreshToken的有效時間爲60分鐘。
可能的狀況json
private string GetAccessToken(SessionUser user) { var claims = new[] { new Claim(JwtClaimTypes.Id, user.Id.ToString()), new Claim(JwtClaimTypes.Name, user.Name), new Claim(JwtClaimTypes.Role, "user") }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(_configuration["Authentication:JwtBearer:SecurityKey"])); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( _configuration["Authentication:JwtBearer:Issuer"], _configuration["Authentication:JwtBearer:Audience"], claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); }
[HttpPost] public IActionResult Post([FromBody]LoginModel model) { if (!string.IsNullOrWhiteSpace(model.Account) && !string.IsNullOrWhiteSpace(model.Pw)) { var user = new SessionUser { Id = 1, Name = "admin", Role = "user" }; var refreshToken = Guid.NewGuid().ToString("N"); var refreshTokenExpiredTime = DateTime.Now.AddMinutes(60); var cacheKey = $"RefreshToken:{refreshToken}"; var cacheValue = JsonConvert.SerializeObject(user); _cache.SetString(cacheKey, cacheValue, new DistributedCacheEntryOptions { AbsoluteExpiration = refreshTokenExpiredTime }); return Ok(new { AccessToken = GetAccessToken(user), Code = 200, RefreshTokenExpired = DateTimeHelper.ConvertToLong(refreshTokenExpiredTime), RefreshToken = refreshToken }); } return Ok(new { Code = 0, Token = "" }); }
[Authorize] [HttpPost("Refresh")] public IActionResult Refresh(RefreshTokenRequest request) { var token = request.Token; var cacheStr = _cache.GetString($"RefreshToken:{token}"); if (string.IsNullOrWhiteSpace(cacheStr)) { return Ok(new { Code = 0, Message = "Token不存在或已過時" }); } var cacheUser = JsonConvert.DeserializeObject<SessionUser>(cacheStr); var userId = User.Claims.First(c => c.Type == JwtClaimTypes.Id); if (userId == null || cacheUser.Id.ToString() != userId.Value) { return Ok(new { Code = 0, Message = "用戶不匹配" }); } var refreshToken = Guid.NewGuid().ToString("N"); var cacheKey = $"RefreshToken:{refreshToken}"; var refreshTokenExpiredTime = DateTime.Now.AddMinutes(60); _cache.SetString(cacheKey, cacheStr, new DistributedCacheEntryOptions { AbsoluteExpiration = DateTime.Now.AddMinutes(30) }); return Ok(new { AccessToken = GetAccessToken(cacheUser), Code = 200, RefreshTokenExpired = DateTimeHelper.ConvertToLong(refreshTokenExpiredTime), RefreshToken = refreshToken }); }
public class LoginModel { [Required] public string Account { get; set; } [Required] public string Pw { get; set; } } public class SessionUser { public int Id { get; set; } public string Name { get; set; } public string Role { get; set; } }
public class DateTimeHelper { /// <summary> /// DateTime轉時間戳 /// </summary> /// <param name="date"></param> /// <returns></returns> public static long ConvertToLong(DateTime date) { var startTime = TimeZoneInfo.ConvertTimeFromUtc(new DateTime(1970, 1, 1), TimeZoneInfo.Utc); return (new DateTimeOffset(date).UtcTicks - startTime.Ticks) / 10000; } /// <summary> /// 時間戳轉DateTime /// </summary> /// <param name="timestamp"></param> /// <returns></returns> public static DateTime ConvertToDateTime(long timestamp) { var startTime = TimeZoneInfo.ConvertTimeFromUtc(new DateTime(1970, 1, 1), TimeZoneInfo.Local); return startTime.Add(new TimeSpan(timestamp * 10000)); } }
[Route("api/[controller]")] [ApiController] public class AccessTokenController : ControllerBase { private readonly IConfiguration _configuration; private readonly IDistributedCache _cache; private readonly UserService _service; public AccessTokenController(IConfiguration configuration, IDistributedCache cache, UserService service) { _configuration = configuration; _cache = cache; _service = service; } /// <summary> /// 登陸,獲取後原來RefreshToken將失效。 /// AccessToken有效時間30分鐘 /// RefreshToken有效時間60分鐘 /// </summary> /// <param name="model"></param> /// <returns></returns> [HttpPost] public ActionResult Post([FromBody]LoginModel model) { var result = _service.Login(model.Account, model.Pw); if (result.Code != 200) { return Ok(new {Code = 0, Message = result.Message}); } var user = new SessionUser { Id = result.Body.Id, Name = result.Body.NickName, Role = "user" }; var refreshToken = Guid.NewGuid().ToString("N"); var refreshTokenExpiredTime = DateTime.Today.AddDays(7); var cacheKey = $"RefreshToken:{refreshToken}"; var cacheValue = JsonConvert.SerializeObject(user); _cache.SetString(cacheKey, cacheValue, new DistributedCacheEntryOptions { AbsoluteExpiration = refreshTokenExpiredTime }); return Ok(new { AccessToken = GetAccessToken(user), Code = 200, RefreshTokenExpired = DateTimeHelper.ConvertToLong(refreshTokenExpiredTime), RefreshToken = refreshToken }); } /// <summary> /// 刷新AccessToken /// </summary> /// <param name="request">刷新的請求 {"token": "refresh_token"}</param> /// <returns></returns> [Authorize] [HttpPost("Refresh")] public IActionResult Refresh(RefreshTokenRequest request) { var token = request.Token; var cacheStr = _cache.GetString($"RefreshToken:{token}"); if (string.IsNullOrWhiteSpace(cacheStr)) { return Ok(new { Code = 0, Message = "Token不存在或已過時" }); } var cacheUser = JsonConvert.DeserializeObject<SessionUser>(cacheStr); var userId = User.Claims.First(c => c.Type == JwtClaimTypes.Id); if (userId == null || cacheUser.Id.ToString() != userId.Value) { return Ok(new { Code = 0, Message = "用戶不匹配" }); } var refreshToken = Guid.NewGuid().ToString("N"); var cacheKey = $"RefreshToken:{refreshToken}"; var refreshTokenExpiredTime = DateTime.Today.AddDays(7); _cache.SetString(cacheKey, cacheStr, new DistributedCacheEntryOptions { AbsoluteExpiration = refreshTokenExpiredTime }); return Ok(new { AccessToken = GetAccessToken(cacheUser), Code = 200, RefreshTokenExpired = DateTimeHelper.ConvertToLong(refreshTokenExpiredTime), RefreshToken = refreshToken }); } /// <summary> /// 經過SessionUser獲取AccessToken /// </summary> /// <param name="user"></param> /// <returns></returns> private string GetAccessToken(SessionUser user) { var claims = new[] { new Claim(JwtClaimTypes.Id, user.Id.ToString()), new Claim(JwtClaimTypes.Name, user.Name), new Claim(JwtClaimTypes.Role, "user") }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(_configuration["Authentication:JwtBearer:SecurityKey"])); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( _configuration["Authentication:JwtBearer:Issuer"], _configuration["Authentication:JwtBearer:Audience"], claims, expires: DateTime.Now.AddHours(2), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); } /// <summary> /// 刷新AccessToken的請求 /// </summary> public class RefreshTokenRequest { /// <summary> /// RefreshToken,登陸後獲取 /// </summary> public string Token { get; set; } } }