AspNetCore3.0 和 JWT
添加NuGet引用
IdentityModel
Microsoft.AspNetCore.Authorization.JwtBearer
在appsettings.json中添加JwtBearer配置
"Authentication": {
"JwtBearer": {
"IsEnabled": "true",
"SecurityKey": "JWTStudyWebsite_DI20DXU3",
"Issuer": "JWTStudy",
"Audience": "JWTStudyWebsite"
}
}
创建JWT服务注册扩展
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
};
});
}
}
}
在startup>ConfigureServices中注册服务
services.AddJwtConfiguration(Configuration);
创建AccessTokenController
说明:用户首次使用用户名和密码登录,生成AccessToken和RefreshToken,
其中AccessToken的有效时间为30分钟,RefreshToken的有效时间为60分钟。
可能的情况
- AccessToken没有过期
- AccessToken已过期,RefreshToken未过期
- RefreshToken已过期
一、首先创建一个方法,用于生成AccessToken
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);
}
二、通过用户名密码获取AccessToken
[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 = "" });
}
三、通过RefreshToken获取新的AccessToken
[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; }
}
}