目录
前言
StartUp中ConfigureServices注册
Appsetting配置,参数需自定义
完整登陆接口如下
用到一个自用封装类
客户端请求
结语
本文主要讲述了在.net core下实现JWT授权以及Token失效之后如何刷新Token,关于JWT请参考如下文章JWT,TODO:萌新劝退,本文需要具有一定的基础知识。照抄可能你会什么都不懂。
以下正文:
#region jwt
//读取Jwt 配置文件
services.Configure(Configuration.GetSection("JwtSettings"));
//绑定到实例中
var jwt = new JwtSetting();
Configuration.Bind("JwtSettings", jwt);
//添加身份验证
services.AddAuthentication(options =>
{
//认证middleware配置
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
//Token颁发机构
ValidIssuer = jwt.Issuer,
//颁发给谁
ValidAudience = jwt.Audience,
//这里的key要进行加密
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwt.SecretKey)),
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ClockSkew = TimeSpan.Zero,//校验时间是否过期时,设置的时钟偏移量
};
//捕捉令牌过期
o.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("act", "expired");
}
return Task.CompletedTask;
}
};
});
#endregion
"JwtSettings": {
"Issuer": "",
"Audience": "",
"SecretKey": "",
"AccessTokenExpiresMinutes": "35",
"RefreshTokenAudience": "RefreshTokenAudience",
"RefreshTokenExpiresMinutes": "240" //4Сʱ
}
JwtSetting类
///
/// Jwt
///
public class JwtSetting
{
///
/// token是谁颁发的
///
public string Issuer { get; set; }
///
/// token可以给那些客户端使用
///
public string Audience { get; set; }
///
/// 加密的key(SecretKey必须大于16个,是大于,不是大于等于)
///
public string SecretKey { get; set; }
///
/// token过期时间
///
public string AccessTokenExpiresMinutes { get; set; }
///
/// refresh Token
///
public string RefreshTokenAudience { get; set; }
///
/// refresh Token exprise
///
public string RefreshTokenExpiresMinutes { get; set; }
}
StartUp中Configure注入身份授权,需在UseMvc之前
//身份授权认证
app.UseAuthentication();
登入方法
///
/// 获取Token
///
///
///
private async Task GetTokenAsync(Users users)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(jwt.SecretKey);
var authTime = DateTime.Now;
var expiresAt = authTime.AddMinutes(double.Parse(jwt.AccessTokenExpiresMinutes));
var tokenDescripor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(JwtClaimTypes.Audience,jwt.Audience),
new Claim(JwtClaimTypes.Issuer,jwt.Issuer),
new Claim(JwtClaimTypes.NickName, users.NickName),
new Claim(JwtClaimTypes.Id, users.Id.ToString()),
new Claim(JwtClaimTypes.Name, users.UserName),
new Claim(JwtClaimTypes.Role,users.Roles.Id.ToString())
}),
Expires = expiresAt,
//对称秘钥SymmetricSecurityKey
//签名证书(秘钥,加密算法)SecurityAlgorithms
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescripor);
var tokenString = tokenHandler.WriteToken(token);
#region refreshToken
//获取刷新token
var refresh = GenerateRefreshToken();
//获取缓存的token keys
var reTokens = await _cache.GetRefreshTokens();
await _cache.RemoveAsync(CacheKey.RefreshTokens);
if (!reTokens.Any(t => t.Issuer.Id == users.Id))
{
reTokens.Add(new RefreshToken()
{
Token = refresh,
Exprise = DateTime.Now.AddMinutes(double.Parse(jwt.RefreshTokenExpiresMinutes)),
Issuer = users
});
await _cache._cache.SetObjectAsync("RefreshTokens", reTokens);
}
else
{
reTokens.ForEach(delegate (RefreshToken refreshToken)
{
if (refreshToken.Issuer.Id == users.Id)
{
refreshToken.Token = refresh;
refreshToken.Exprise = DateTime.Now.AddMinutes(double.Parse(jwt.RefreshTokenExpiresMinutes));
}
});
await _cache._cache.SetObjectAsync("RefreshTokens", reTokens);
}
#endregion
return new AccountRes
{
AccessToken = tokenString,
RefreshToken = refresh,
TokenType = "Bearer",
Profile = new Profiles
{
Name = users.UserName,
NickName = users.NickName,
Auth_Time = authTime,
Expires_At = expiresAt
}
};
}
///
/// 生成refresh Token
///
///
private string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
注意:登入方法同时生成了一个RefreshToken,它将在Token失效时,进行调用生产新的Token并返回结果集
对外接口:Token失效获取新的Token
///
/// 刷新Token
///
///
[HttpPost]
[Route("RefreshToken")]
[AllowAnonymous]
[Filter.SkipAuthorization]
public async Task RefreshToken()
{
try
{
var token = GetParams("token");
if (string.IsNullOrEmpty(token))
return Result.Fail(HttpStatusCode.NoContent, "invalid refreshToken !");
var reTokens = await _cache.GetRefreshTokens();
var tokenUser = reTokens?.SingleOrDefault(t => t.Token.ToLower().Equals(token.ToLower()));
var obj = new AccountRes();
if (tokenUser != null)
{
//Token存在并且有效
if (tokenUser.Token.ToLower().Equals(token.ToLower()) && DateTime.Now < tokenUser.Exprise)
{
obj = await GetTokenAsync(tokenUser.Issuer);
}
}
else
return Result.Fail(HttpStatusCode.NotModified, "refreshtoken error !");
if (obj != null && !string.IsNullOrEmpty(obj.AccessToken))
return Result.Success(obj);
else
return Result.Fail(HttpStatusCode.NotModified, "refreshtoken error !");
}
catch (Exception c)
{
_logger.LogError(c, $"【{GetCurrentUser(_context).NickName}】刷新Token失败。");
return Result.Fail("refresh token error !");
}
}
注:[AllowAnonymous] 允许此接口不经授权即可访问。[Filter.SkipAuthorization]我重写的对外接口拦截器,需要自用
///
/// 登录
///
///
[Route("Login")]
[AllowAnonymous]
[HttpPost]
public async Task Login()
{
try
{
string username = GetParams("username");
string pwd = GetParams("pwd");
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(pwd))
return Result.Fail(HttpStatusCode.NoContent, "invalid username or pwd !");
if (username.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) >= 0 || username.Reg())
return Result.Fail(HttpStatusCode.NonAuthoritativeInformation, "invalid username !");
var user = await _context.Users.Where(t => t.UserName.Equals(username) && t.PassWord.ToUpper().Equals(pwd.Md5().ToUpper()))?.Include(a => a.Roles).FirstOrDefaultAsync();
if (user != null)
{
//Jwt登录
var obj = await GetTokenAsync(user);
#region 写入登录日志
//写入登录日志
var ip = HttpContext.Connection.RemoteIpAddress.ToString();
_context.Loginlogs.Add(new Loginlog()
{
Users = user,
LoginIp = ip,
LoginAddress = Ip.GetAddress(ip)
});
await _context.SaveChangesAsync();
#endregion
return Result.Success(obj);
}
else
return Result.Fail(HttpStatusCode.NotFound, "incorrect user name or password !");
}
catch (Exception c)
{
_logger.LogError(c, $"【{GetParams("username")}】登入系统出错。");
return Result.Fail("login system error !");
}
}
//
/// 返回结果集
///
public class Result
{
internal const string DefaultExceptionMessage = "未知错误!";
///
/// 构造方法
///
public Result()
{
this.Code = HttpStatusCode.NotFound;
this.Msg = "操作失败";
this.Error = "";
this.Url = "";
}
///
/// 返回成功的对象
///
///
///
public Result Success(string msg)
{
if (string.IsNullOrEmpty(msg)) msg = "操作成功";
return new Result
{
Code = HttpStatusCode.OK,
Msg = msg,
Error = "",
Url = ""
};
}
///
/// 返回成功的对象
///
///
public static Result Success()
{
return Success(string.Empty);
}
///
/// 返回成功的带结果集的对象
///
///
///
///
///
public static Result Success(string msg, T data)
{
if (string.IsNullOrWhiteSpace(msg)) msg = "操作成功!";
return new Result
{
Code = HttpStatusCode.OK,
Msg = msg,
Error = "",
Url = "",
Data = data
};
}
///
/// 返回成功的带结果集的对象
///
///
///
///
public static Result Success(T data)
{
return Success(string.Empty, data);
}
///
/// 返回失败的带结果集的对象
///
///
///
///
///
public static Result Fail(string msg, T data)
{
if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
return new Result
{
Code = HttpStatusCode.BadRequest,
Msg = msg,
Error = "",
Url = "",
Data = data
};
}
///
/// 返回失败的对象
///
///
///
///
public static Result Fail(HttpStatusCode code, string msg)
{
if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
return new Result
{
Code = code,
Msg = msg,
Error = "",
Url = ""
};
}
///
/// 返回失败的带结果集的对象
///
///
///
///
///
///
public static Result Fail(HttpStatusCode code, string msg, T data)
{
if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
return new Result
{
Code = code,
Msg = msg,
Error = "",
Url = "",
Data = data
};
}
///
/// 返回失败的带结果集的对象
///
///
///
///
public static Result Fail(string msg)
{
return Fail(msg, default);
}
///
/// 返回失败的对象
///
///
///
public static Result Fail(string msg)
{
if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
return new Result
{
Code = HttpStatusCode.BadRequest,
Msg = msg,
Error = "",
Url = ""
};
}
#region 扩展方法
///
/// 异步返回结果集
///
///
///
///
public static async Task> GetAsync(T data)
{
return await Task>.Factory.StartNew(() =>
{
try
{
return Success(data);
}
catch (Exception c)
{
return Fail(c.ToString());
}
});
}
///
/// 异步返回结果集
///
///
///
///
///
public static async Task> GetAsync(string msg, T data)
{
return await Task>.Factory.StartNew(() =>
{
try
{
return Success(msg, data);
}
catch (Exception c)
{
return Fail(c.ToString());
}
});
}
///
/// 异步返回结果集
///
///
public static async Task GetAsync()
{
return await Task.Factory.StartNew(() =>
{
try
{
return Success();
}
catch (Exception c)
{
return Fail(c.ToString());
}
});
}
#endregion
///
/// 状态码
///
public HttpStatusCode Code { get; set; }
///
/// 消息
///
public string Msg { get; set; }
///
/// 错误
///
public string Error { get; set; }
///
/// 错误Url
///
public string Url { get; set; }
}
public class Result : Result
{
private T _data = default;
///
/// 构造方法
///
public Result()
: base()
{
_data = default;
}
///
/// 操作结果业务数据
///
public T Data
{
get
{
if (typeof(T).BaseType == typeof(IEnumerator))
_data = Activator.CreateInstance();
return _data;
}
set
{
_data = value;
}
}
///
/// 返回成功的带消息提示对象
///
///
///
///
public static Result Success(string msg, T data)
{
return Success(msg, data);
}
///
/// 返回成功的带消息提示对象
///
///
///
public static Result Success(T data)
{
return Success(string.Empty, data);
}
///
/// 返回失败的带消息提示对象
///
///
///
///
public static Result Fail(string msg, T data)
{
return Fail(msg, data);
}
///
/// 返回失败的带消息提示对象
///
///
///
public static new Result Fail(string msg)
{
return Fail(msg, default);
}
public static Result Fail(HttpStatusCode code, string msg,T data)
{
return Fail(code, msg, data);
}
}
建议将服务端的登陆返回结果存放到客户端cookie中,使用cookie拉取登陆之后返回的Token
客户端逻辑如上图,请自行实现。
Todo:客户端同步缓存你的登陆信息,Token过期捕捉到然后发起RefreshToken请求。登陆方法中部分我自用逻辑,建议参考实现。
写在最后,文中有错误之处请在下方留言。转载需注明出处
个人博客:欢迎关注我的博客