From 1f56e4857892150e1109a93fadc487f92d955eed Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sun, 4 Aug 2019 16:54:20 +0800 Subject: WIP: Need to solve the entity framework problem. --- Timeline/Services/JwtService.cs | 363 ++++++++++++++++++++-------------------- 1 file changed, 182 insertions(+), 181 deletions(-) (limited to 'Timeline/Services/JwtService.cs') diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs index 52e892f6..94afe745 100644 --- a/Timeline/Services/JwtService.cs +++ b/Timeline/Services/JwtService.cs @@ -1,181 +1,182 @@ -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using System; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using Timeline.Configs; - -namespace Timeline.Services -{ - public class TokenInfo - { - public long Id { get; set; } - public long Version { get; set; } - } - - [Serializable] - public class JwtTokenVerifyException : Exception - { - public static class ErrorCodes - { - // Codes in -1000 ~ -1999 usually means the user provides a token that is not created by this server. - - public const int Others = -1001; - public const int NoIdClaim = -1002; - public const int IdClaimBadFormat = -1003; - public const int NoVersionClaim = -1004; - public const int VersionClaimBadFormat = -1005; - - /// - /// Corresponds to . - /// - public const int Expired = -2001; - } - - public JwtTokenVerifyException(int code) : base(GetErrorMessage(code)) { ErrorCode = code; } - public JwtTokenVerifyException(string message, int code) : base(message) { ErrorCode = code; } - public JwtTokenVerifyException(Exception inner, int code) : base(GetErrorMessage(code), inner) { ErrorCode = code; } - public JwtTokenVerifyException(string message, Exception inner, int code) : base(message, inner) { ErrorCode = code; } - protected JwtTokenVerifyException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public int ErrorCode { get; private set; } - - private static string GetErrorMessage(int errorCode) - { - switch (errorCode) - { - case ErrorCodes.Others: - return "Uncommon error, see inner exception for more information."; - case ErrorCodes.NoIdClaim: - return "Id claim does not exist."; - case ErrorCodes.IdClaimBadFormat: - return "Id claim is not a number."; - case ErrorCodes.NoVersionClaim: - return "Version claim does not exist."; - case ErrorCodes.VersionClaimBadFormat: - return "Version claim is not a number"; - case ErrorCodes.Expired: - return "Token is expired."; - default: - return "Unknown error code."; - } - } - } - - public interface IJwtService - { - /// - /// Create a JWT token for a given token info. - /// - /// The info to generate token. - /// The expire time. If null then use current time with offset in config. - /// Return the generated token. - /// Thrown when is null. - string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null); - - /// - /// Verify a JWT token. - /// Return null is is null. - /// - /// The token string to verify. - /// Return the saved info in token. - /// Thrown when is null. - /// Thrown when the token is invalid. - TokenInfo VerifyJwtToken(string token); - - } - - public class JwtService : IJwtService - { - private const string VersionClaimType = "timeline_version"; - - private readonly IOptionsMonitor _jwtConfig; - private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler(); - private readonly IClock _clock; - - public JwtService(IOptionsMonitor jwtConfig, IClock clock) - { - _jwtConfig = jwtConfig; - _clock = clock; - } - - public string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null) - { - if (tokenInfo == null) - throw new ArgumentNullException(nameof(tokenInfo)); - - var config = _jwtConfig.CurrentValue; - - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokenInfo.Id.ToString(), ClaimValueTypes.Integer64)); - identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(), ClaimValueTypes.Integer64)); - - var tokenDescriptor = new SecurityTokenDescriptor() - { - Subject = identity, - Issuer = config.Issuer, - Audience = config.Audience, - SigningCredentials = new SigningCredentials( - new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)), SecurityAlgorithms.HmacSha384), - IssuedAt = _clock.GetCurrentTime(), - Expires = expires.GetValueOrDefault(_clock.GetCurrentTime().AddSeconds(config.DefaultExpireOffset)) - }; - - var token = _tokenHandler.CreateToken(tokenDescriptor); - var tokenString = _tokenHandler.WriteToken(token); - - return tokenString; - } - - - public TokenInfo VerifyJwtToken(string token) - { - if (token == null) - throw new ArgumentNullException(nameof(token)); - - var config = _jwtConfig.CurrentValue; - try - { - var principal = _tokenHandler.ValidateToken(token, new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateIssuerSigningKey = true, - ValidateLifetime = true, - ValidIssuer = config.Issuer, - ValidAudience = config.Audience, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)) - }, out _); - - var idClaim = principal.FindFirstValue(ClaimTypes.NameIdentifier); - if (idClaim == null) - throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoIdClaim); - if (!long.TryParse(idClaim, out var id)) - throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.IdClaimBadFormat); - - var versionClaim = principal.FindFirstValue(VersionClaimType); - if (versionClaim == null) - throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoVersionClaim); - if (!long.TryParse(versionClaim, out var version)) - throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.VersionClaimBadFormat); - - return new TokenInfo - { - Id = id, - Version = version - }; - } - catch (SecurityTokenExpiredException e) - { - throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Expired); - } - catch (Exception e) - { - throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Others); - } - } - } -} +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Timeline.Configs; + +namespace Timeline.Services +{ + public class TokenInfo + { + public long Id { get; set; } + public long Version { get; set; } + } + + [Serializable] + public class JwtTokenVerifyException : Exception + { + public static class ErrorCodes + { + // Codes in -1000 ~ -1999 usually means the user provides a token that is not created by this server. + + public const int Others = -1001; + public const int NoIdClaim = -1002; + public const int IdClaimBadFormat = -1003; + public const int NoVersionClaim = -1004; + public const int VersionClaimBadFormat = -1005; + + /// + /// Corresponds to . + /// + public const int Expired = -2001; + } + + public JwtTokenVerifyException(int code) : base(GetErrorMessage(code)) { ErrorCode = code; } + public JwtTokenVerifyException(string message, int code) : base(message) { ErrorCode = code; } + public JwtTokenVerifyException(Exception inner, int code) : base(GetErrorMessage(code), inner) { ErrorCode = code; } + public JwtTokenVerifyException(string message, Exception inner, int code) : base(message, inner) { ErrorCode = code; } + protected JwtTokenVerifyException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + + public int ErrorCode { get; private set; } + + private static string GetErrorMessage(int errorCode) + { + switch (errorCode) + { + case ErrorCodes.Others: + return "Uncommon error, see inner exception for more information."; + case ErrorCodes.NoIdClaim: + return "Id claim does not exist."; + case ErrorCodes.IdClaimBadFormat: + return "Id claim is not a number."; + case ErrorCodes.NoVersionClaim: + return "Version claim does not exist."; + case ErrorCodes.VersionClaimBadFormat: + return "Version claim is not a number"; + case ErrorCodes.Expired: + return "Token is expired."; + default: + return "Unknown error code."; + } + } + } + + public interface IJwtService + { + /// + /// Create a JWT token for a given token info. + /// + /// The info to generate token. + /// The expire time. If null then use current time with offset in config. + /// Return the generated token. + /// Thrown when is null. + string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null); + + /// + /// Verify a JWT token. + /// Return null is is null. + /// + /// The token string to verify. + /// Return the saved info in token. + /// Thrown when is null. + /// Thrown when the token is invalid. + TokenInfo VerifyJwtToken(string token); + + } + + public class JwtService : IJwtService + { + private const string VersionClaimType = "timeline_version"; + + private readonly IOptionsMonitor _jwtConfig; + private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler(); + private readonly IClock _clock; + + public JwtService(IOptionsMonitor jwtConfig, IClock clock) + { + _jwtConfig = jwtConfig; + _clock = clock; + } + + public string GenerateJwtToken(TokenInfo tokenInfo, DateTime? expires = null) + { + if (tokenInfo == null) + throw new ArgumentNullException(nameof(tokenInfo)); + + var config = _jwtConfig.CurrentValue; + + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokenInfo.Id.ToString(), ClaimValueTypes.Integer64)); + identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(), ClaimValueTypes.Integer64)); + + var tokenDescriptor = new SecurityTokenDescriptor() + { + Subject = identity, + Issuer = config.Issuer, + Audience = config.Audience, + SigningCredentials = new SigningCredentials( + new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)), SecurityAlgorithms.HmacSha384), + IssuedAt = _clock.GetCurrentTime(), + Expires = expires.GetValueOrDefault(_clock.GetCurrentTime().AddSeconds(config.DefaultExpireOffset)), + NotBefore = _clock.GetCurrentTime() // I must explicitly set this or it will use the current time by default and mock is not work in which case test will not pass. + }; + + var token = _tokenHandler.CreateToken(tokenDescriptor); + var tokenString = _tokenHandler.WriteToken(token); + + return tokenString; + } + + + public TokenInfo VerifyJwtToken(string token) + { + if (token == null) + throw new ArgumentNullException(nameof(token)); + + var config = _jwtConfig.CurrentValue; + try + { + var principal = _tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateIssuerSigningKey = true, + ValidateLifetime = true, + ValidIssuer = config.Issuer, + ValidAudience = config.Audience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey)) + }, out _); + + var idClaim = principal.FindFirstValue(ClaimTypes.NameIdentifier); + if (idClaim == null) + throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoIdClaim); + if (!long.TryParse(idClaim, out var id)) + throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.IdClaimBadFormat); + + var versionClaim = principal.FindFirstValue(VersionClaimType); + if (versionClaim == null) + throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoVersionClaim); + if (!long.TryParse(versionClaim, out var version)) + throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.VersionClaimBadFormat); + + return new TokenInfo + { + Id = id, + Version = version + }; + } + catch (SecurityTokenExpiredException e) + { + throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Expired); + } + catch (Exception e) + { + throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Others); + } + } + } +} -- cgit v1.2.3