diff options
author | 杨宇千 <crupest@outlook.com> | 2019-07-20 23:47:41 +0800 |
---|---|---|
committer | 杨宇千 <crupest@outlook.com> | 2019-07-20 23:47:41 +0800 |
commit | 615ffca61fcc90b11b04c8d115018a26a4a63a33 (patch) | |
tree | b0f75d1c1613a2686e62edb95d858021beae9f01 | |
parent | eb50fc7ad1252d0e397e969e1a7eb33720150b00 (diff) | |
download | timeline-615ffca61fcc90b11b04c8d115018a26a4a63a33.tar.gz timeline-615ffca61fcc90b11b04c8d115018a26a4a63a33.tar.bz2 timeline-615ffca61fcc90b11b04c8d115018a26a4a63a33.zip |
WIP: Change the JwtService.
-rw-r--r-- | Timeline/Authenticate/AuthHandler.cs | 4 | ||||
-rw-r--r-- | Timeline/Configs/JwtConfig.cs | 6 | ||||
-rw-r--r-- | Timeline/Services/JwtService.cs | 62 |
3 files changed, 49 insertions, 23 deletions
diff --git a/Timeline/Authenticate/AuthHandler.cs b/Timeline/Authenticate/AuthHandler.cs index 71d8aeaa..63442481 100644 --- a/Timeline/Authenticate/AuthHandler.cs +++ b/Timeline/Authenticate/AuthHandler.cs @@ -23,9 +23,7 @@ namespace Timeline.Authenticate /// </summary> public string TokenQueryParamKey { get; set; } = "token"; - public TokenValidationParameters TokenValidationParameters { get; - set; } - = new TokenValidationParameters(); + public TokenValidationParameters TokenValidationParameters { get; set; } = new TokenValidationParameters(); } class AuthHandler : AuthenticationHandler<AuthOptions> diff --git a/Timeline/Configs/JwtConfig.cs b/Timeline/Configs/JwtConfig.cs index 9550424e..1b395650 100644 --- a/Timeline/Configs/JwtConfig.cs +++ b/Timeline/Configs/JwtConfig.cs @@ -5,5 +5,11 @@ namespace Timeline.Configs public string Issuer { get; set; } public string Audience { get; set; } public string SigningKey { get; set; } + + /// <summary> + /// Set the default value of expire offset of jwt token. + /// Unit is second. Default is 3600 seconds, aka 1 hour. + /// </summary> + public long DefaultExpireOffset { get; set; } = 3600; } } diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs index 2139ba56..e7f5690d 100644 --- a/Timeline/Services/JwtService.cs +++ b/Timeline/Services/JwtService.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System; using System.IdentityModel.Tokens.Jwt; -using System.Linq; using System.Security.Claims; using System.Text; using Timeline.Configs; @@ -13,30 +12,45 @@ namespace Timeline.Services public class TokenInfo { public string Name { get; set; } - public string[] Roles { get; set; } + public long Version { get; set; } + } + + [Serializable] + public class JwtTokenVerifyException : Exception + { + public JwtTokenVerifyException() { } + public JwtTokenVerifyException(string message) : base(message) { } + public JwtTokenVerifyException(string message, Exception inner) : base(message, inner) { } + protected JwtTokenVerifyException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } public interface IJwtService { /// <summary> - /// Create a JWT token for a given user info. + /// Create a JWT token for a given token info. /// </summary> /// <param name="tokenInfo">The info to generate token.</param> + /// <param name="expires">The expire time. If null then use current time with offset in config.</param> /// <returns>Return the generated token.</returns> - string GenerateJwtToken(TokenInfo tokenInfo); + string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null); /// <summary> /// Verify a JWT token. /// Return null is <paramref name="token"/> is null. /// </summary> /// <param name="token">The token string to verify.</param> - /// <returns>Return null if <paramref name="token"/> is null or token is invalid. Return the saved info otherwise.</returns> + /// <returns>Return null if <paramref name="token"/> is null. Return the saved info otherwise.</returns> + /// <exception cref="JwtTokenVerifyException">Thrown when the token is invalid.</exception> TokenInfo VerifyJwtToken(string token); } public class JwtService : IJwtService { + private const string VersionClaimType = "timeline_version"; + private readonly IOptionsMonitor<JwtConfig> _jwtConfig; private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler(); private readonly ILogger<JwtService> _logger; @@ -47,30 +61,28 @@ namespace Timeline.Services _logger = logger; } - public string GenerateJwtToken(TokenInfo tokenInfo) + public string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null) { if (tokenInfo == null) throw new ArgumentNullException(nameof(tokenInfo)); if (tokenInfo.Name == null) - throw new ArgumentException("Name is null.", nameof(tokenInfo)); - if (tokenInfo.Roles == null) - throw new ArgumentException("Roles is null.", nameof(tokenInfo)); + throw new ArgumentException("Name of token info is null.", nameof(tokenInfo)); - var jwtConfig = _jwtConfig.CurrentValue; + var config = _jwtConfig.CurrentValue; var identity = new ClaimsIdentity(); identity.AddClaim(new Claim(identity.NameClaimType, tokenInfo.Name)); - identity.AddClaims(tokenInfo.Roles.Select(role => new Claim(identity.RoleClaimType, role))); + identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(), ClaimValueTypes.Integer64)); var tokenDescriptor = new SecurityTokenDescriptor() { Subject = identity, - Issuer = jwtConfig.Issuer, - Audience = jwtConfig.Audience, + Issuer = config.Issuer, + Audience = config.Audience, SigningCredentials = new SigningCredentials( - new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtConfig.SigningKey)), SecurityAlgorithms.HmacSha384), + new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)), SecurityAlgorithms.HmacSha384), IssuedAt = DateTime.Now, - Expires = DateTime.Now.AddDays(1) + Expires = expires.GetValueOrDefault(DateTime.Now.AddSeconds(config.DefaultExpireOffset)) }; var token = _tokenHandler.CreateToken(tokenDescriptor); @@ -97,18 +109,28 @@ namespace Timeline.Services ValidIssuer = config.Issuer, ValidAudience = config.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)) - }, out SecurityToken validatedToken); + }, out _); + + var versionClaim = principal.FindFirstValue(VersionClaimType); + if (versionClaim == null) + throw new JwtTokenVerifyException("Version claim does not exist."); + if (!long.TryParse(versionClaim, out var version)) + throw new JwtTokenVerifyException("Can't convert version claim into a integer number."); return new TokenInfo { Name = principal.Identity.Name, - Roles = principal.FindAll(ClaimTypes.Role).Select(c => c.Value).ToArray() + Version = version }; } - catch (Exception e) + catch (SecurityTokenException e) { - _logger.LogInformation(e, "Token validation failed! Token is {} .", token); - return null; + throw new JwtTokenVerifyException("Validate token failed caused by a SecurityTokenException. See inner exception.", e); + } + catch (ArgumentException e) // This usually means code logic error. + { + _logger.LogError(e, "Arguments passed to ValidateToken are bad."); + throw e; } } } |