aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-07-20 23:47:41 +0800
committer杨宇千 <crupest@outlook.com>2019-07-20 23:47:41 +0800
commit615ffca61fcc90b11b04c8d115018a26a4a63a33 (patch)
treeb0f75d1c1613a2686e62edb95d858021beae9f01
parenteb50fc7ad1252d0e397e969e1a7eb33720150b00 (diff)
downloadtimeline-615ffca61fcc90b11b04c8d115018a26a4a63a33.tar.gz
timeline-615ffca61fcc90b11b04c8d115018a26a4a63a33.tar.bz2
timeline-615ffca61fcc90b11b04c8d115018a26a4a63a33.zip
WIP: Change the JwtService.
-rw-r--r--Timeline/Authenticate/AuthHandler.cs4
-rw-r--r--Timeline/Configs/JwtConfig.cs6
-rw-r--r--Timeline/Services/JwtService.cs62
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;
}
}
}