JWT权限验证,兼容多方式验证
前言
许久没写博文了,整合下这段时间所学吧,前进路上总要停下来回顾下学习成果。
本篇记录下项目的权限验证,WebApi项目中用权限验证来保证接口安全总是需要的,然而权限验证的方式多种多样,博主在项目中使用的多的也就是JWT了,一般都是写完之后万年不动~~
所以,本篇算是对鉴权授权的回顾与总结
JWT
至于什么是JWT(https://jwt.io/),只要不是小白都知道吧,不知道的去看下JWT的结构原理这些,偷偷补下课,JWT(JSON Web Token)名字可看出来这是Json格式的web凭证,也就是一个令牌,只有拿到这个Token才能访问到接口,否则请求接口之后会返回401HTTP状态码,401状态码表示未授权,而想要拿到服务器的Token,必须通过服务器验证,一般这个验证来自登录之后返回出来,如果是开发平台一般是通过AppId和Secrect来获取到Token,获取到Token后将Token添加到请求头中,服务器收到请求后,获取到请求头的Token后一验证,“诶!~是我发布的Token,通过!”,随后才能进入控制器。
引入
先把JWT引入到项目中来,目前最新版本为稳定版5.0.2
nuget : Microsoft.AspNetCore.Authentication.JwtBearer
鉴权授权
首先要知道沃恩需要什么样的鉴权策略,在生成Token时的策略就必须保持一致。在WebApi中我们不知道是谁在访问服务器,当然想要知道还是可以的,这时可以通过Token将用户信息传到服务器,我们知道JWT的负载信息除了已经准备好的"sub"、"name"、"iat"这些信息,我们还能自定义我们需要的字段,比如登录人的UserId,UserName,AppId等……
首先需要一个接口 IAuthService
public interface IAuthService
{
/// <summary>
/// 判断权限
/// </summary>
/// <param name="token"></param>
/// <param name="path"></param>
/// <returns></returns>
Task<bool> PermissionAsync(string token, string path); /// <summary>
/// 获取用户
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task SetUserAsync(string token);
}
以后所有的权限类型都可以使用这个接口,先将JWT需要的类包装下,这样就能直接使用了
public static class JwtUtils
{
/// <summary>
/// 生成token
/// </summary>
/// <param name="claims"></param>
/// <returns></returns>
public static string CreateToken(IEnumerable<Claim> claims, string securityKey)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512);
var securityToken = new JwtSecurityToken(
issuer: null,
audience: null,
claims: claims,
//expires: DateTime.Now.AddMinutes(settings.ExpMinutes),
signingCredentials: creds);
var token = new JwtSecurityTokenHandler().WriteToken(securityToken);
return token;
} /// <summary>
/// 生成Jwt
/// </summary>
/// <param name="userName"></param>
/// <param name="roleName"></param>
/// <param name="userId"></param>
/// <returns></returns>
public static string GenerateToken(string userId, string securityKey)
{
//声明claim
var claims = new Claim[] {
new Claim(JwtRegisteredClaimNames.Typ,"JWT"),
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Iat,DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMonths(2).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), //过期时间
};
return CreateToken(claims, securityKey);
} ///// <summary>
///// 刷新token
///// </summary>
///// <returns></returns>
//public static string RefreshToken(string oldToken)
//{
// var pl = GetPayload(oldToken);
// //声明claim
// var claims = new Claim[] {
// new Claim(JwtRegisteredClaimNames.Sub, pl?.UserName),
// new Claim(JwtRegisteredClaimNames.Jti, pl?.UserId),
// new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToUnixDate().ToString(), ClaimValueTypes.Integer64),//签发时间
// new Claim(JwtRegisteredClaimNames.Nbf, DateTime.UtcNow.ToUnixDate().ToString(), ClaimValueTypes.Integer64),//生效时间
// new Claim(JwtRegisteredClaimNames.Exp, DateTime.Now.AddMinutes(settings.ExpMinutes).ToUnixDate().ToString(), ClaimValueTypes.Integer64), //过期时间
// new Claim(JwtRegisteredClaimNames.Iss, settings.Issuer),
// new Claim(JwtRegisteredClaimNames.Aud, settings.Audience),
// new Claim(ClaimTypes.Name, pl?.UserName),
// new Claim(ClaimTypes.Role, pl?.RoleId),
// new Claim(ClaimTypes.Sid, pl?.UserId)
// }; // return IsExp(oldToken) ? CreateToken(claims) : null;
//} /// <summary>
/// 从token中获取用户身份
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public static IEnumerable<Claim> GetClaims(string token)
{
var handler = new JwtSecurityTokenHandler();
var securityToken = handler.ReadJwtToken(token);
return securityToken?.Claims;
} /// <summary>
/// 从Token中获取用户身份
/// </summary>
/// <param name="token"></param>
/// <param name="securityKey">securityKey明文,Java加密使用的是Base64</param>
/// <returns></returns>
public static ClaimsPrincipal GetPrincipal(string token, string securityKey)
{
try
{
var handler = new JwtSecurityTokenHandler();
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
ValidateLifetime = false
};
return handler.ValidateToken(token, tokenValidationParameters, out SecurityToken validatedToken);
}
catch (Exception ex)
{ return null;
}
} /// <summary>
/// 校验Token
/// </summary>
/// <param name="token">token</param>
/// <returns></returns>
public static bool CheckToken(string token, string securityKey)
{
var principal = GetPrincipal(token, securityKey);
if (principal is null)
{
return false;
}
return true;
} /// <summary>
/// 获取Token中的载荷数据
/// </summary>
/// <param name="token">token</param>
/// <returns></returns>
public static JwtPayload GetPayload(string token)
{
var jwtHandler = new JwtSecurityTokenHandler();
JwtSecurityToken securityToken = jwtHandler.ReadJwtToken(token);
return new JwtPayload
{
sub = securityToken.Payload[JwtRegisteredClaimNames.Sub]?.ToString(),
exp = DateTimeOffset.FromUnixTimeSeconds(long.Parse(securityToken.Payload[JwtRegisteredClaimNames.Exp].ToString())).ToLocalTime().DateTime,
iat = securityToken.Payload[JwtRegisteredClaimNames.Iat]?.ToString()
};
} /// <summary>
/// 获取Token中的载荷数据
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <param name="token">token</param>
/// <returns></returns>
public static T GetPayload<T>(string token)
{
var jwtHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(token);
return JsonConvert.DeserializeObject<T>(jwtToken.Payload.SerializeToJson());
} /// <summary>
/// 判断token是否过期
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public static bool IsExp(string token)
{
return false;
//return GetPrincipal(token)?.Claims.First(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value?.TimeStampToDate() < DateTime.Now;
//return GetPayload(token).ExpTime < DateTime.Now;
}
} /// <summary>
/// Jwt载荷信息
/// </summary>
public class JwtPayload
{
public string sub { get; set; } public string iat { get; set; } public DateTime exp { get; set; }
}
JWT服务实现
public class AuthSettings
{
public string Secret { get; set; }
public string Issuer { get; set; }
public double Expire { get; set; }
}
public class AuthServiceImpl : IAuthService
{
private readonly AuthSettings _authSettings;
private readonly LoginUser _currentUser; public AuthServiceImpl(IOptions<AuthSettings> authSettings, LoginUser currentUser)
{
_authSettings = authSettings.Value;
_currentUser = currentUser;
}
public Task<bool> PermissionAsync(string token)
{
return Task.FromResult(JwtUntil.CheckToken(token, _authSettings.Secret));
} public Task SetUserAsync(string token)
{
var payload = JwtUntil.GetPayload(token);
_currentUser.UserId = payload.UserId;
_currentUser.RoleType = payload.Role;
return Task.CompletedTask;
}
}
说到这还没有注册JWT,我们先注册到项目中,验证策略自己定,记得要先注入下服务
services.AddScoped<IAuthService, AuthServiceImpl>(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;//元数据地址或权限是否需要https
x.SaveToken = true;//是否将存储信息保存在token中
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateLifetime = true,//是否验证过期时间
LifetimeValidator = (notBefore, expire, securityToken, validationparameters) =>
{
bool t = DateTime.UtcNow < expire;
return t;
},
ValidateAudience = false,//是否验证被发布者 ValidateIssuer = true,//是否验证发布者
ValidIssuer = configuration["AuthSettings:Issuer"], ValidateIssuerSigningKey = true,//是否验证签名
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["AuthSettings:Secret"]))
};
});
#region 授权鉴权
/授权
app.UseAuthentication();
//鉴权
app.UseAuthorization();
#endregion
接下来就是设置当前登录用户,获取当前登录用户:发布时在 claims中加入自定义的UserId等信息,授权时获取当前Token解析claims中用户信息属性即可获取到当前请求用户。
本项目中使用的是一个中间件
public sealed class LoginMiddlerware
{
private readonly RequestDelegate _next; public LoginMiddlerware(RequestDelegate next)
{
_next = next;
} /// <summary>
/// 设置登录用户
/// </summary>
/// <param name="context"></param>
/// <param name="_authService"></param>
/// <returns></returns>
public async Task InvokeAsync(HttpContext context, IEnumerable<IAuthService> _authService)
{
string token = GetToken();
if (!string.IsNullOrEmpty(token))
{
if (token.StartsWith("Bearer", StringComparison.OrdinalIgnoreCase) || token.Contains('.'))
{
await _authService.First(a => a.ServiceName == nameof(JwtAuthServiceImpl)).SetUserAsync(token);
}
else if (token.StartsWith("App", StringComparison.OrdinalIgnoreCase))
{
await _authService.First(a => a.ServiceName == nameof(AppAuthServiceImpl)).SetUserAsync(token);
}
else
{
await _authService.First(a => a.ServiceName == nameof(OauthAuthServiceImpl)).SetUserAsync(token);
}
}
await _next.Invoke(context);
string GetToken()
{
string token = context.Request.Headers["token"];
if (string.IsNullOrEmpty(token))
{
token = context.Request.Query["token"];
}
return token;
}
}
}
到这里JWt就差不多讲完了,接下来是使用,使用时无非就是在控制器或方法上打上标记,如要同时兼容多种授权方式,可以自己写一个Attribute
/// <summary>
/// 自定义授权验证特性
/// </summary>
public class RequiresPermissionsAttribute : TypeFilterAttribute
{
public RequiresPermissionsAttribute(ClaimType claimType, string claimValue = "") : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] { new Claim(claimType.ToString(), claimValue) };
}
} public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly Claim _claim;
readonly IEnumerable<IAuthService> _authService;
private readonly WinkSignSettings _winkSignSettings; public ClaimRequirementFilter(Claim claim, IEnumerable<IAuthService> authService, IOptions<WinkSignSettings> winkSignSettings)
{
_claim = claim;
_authService = authService;
_winkSignSettings = winkSignSettings.Value;
} public void OnAuthorization(AuthorizationFilterContext context)
{
ControllerActionDescriptor controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor != null)
{
var skipAuthorization = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.Any(a => a.GetType().Equals(typeof(AllowAnonymousAttribute)));
if (skipAuthorization)
{
return;
}
} ClaimType claimType = Enum.Parse<ClaimType>(_claim.Type);
bool permission = false; string token = GetToken();
if (string.IsNullOrEmpty(token))
{
context.Result = new UnauthorizedResult();
return;
}
if (claimType == ClaimType.JwtOrOauth2)
{
//根据Token类型选择认证方式
if (token.Any(t => t == '.'))
{
claimType = ClaimType.JWT;
}
else
{
claimType = ClaimType.Oauth2;
}
} permission = claimType switch
{
ClaimType.Oauth2 or ClaimType.Cookie =>
_authService.First(a => a.ServiceName == nameof(OauthAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
ClaimType.JWT =>
_authService.First(a => a.ServiceName == nameof(JwtAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
ClaimType.Key =>
_authService.First(a => a.ServiceName == nameof(KeyAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
ClaimType.App =>
_authService.First(a => a.ServiceName == nameof(AppAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
}; if (!permission)
{
context.Result = new UnauthorizedResult();
return;
} string GetToken()
{
string token = context.HttpContext.Request.Headers["token"];
if (string.IsNullOrEmpty(token))
{
token = context.HttpContext.Request.Query["token"];
}
if (string.IsNullOrEmpty(token))
{
context.HttpContext.Request.Cookies.TryGetValue("token", out token);
}
return token;
}
}
} public enum ClaimType
{
Oauth2,
JWT,
Cookie,
Key,
App,
JwtOrOauth2
}
这样就能实现多个方式同时存在,想用哪个就用哪个了,只要实现 IAuthService 接口就行
JWT权限验证,兼容多方式验证的更多相关文章
- 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之五 || Swagger的使用 3.3 JWT权限验证【必看】
前言 关于JWT一共三篇 姊妹篇,内容分别从简单到复杂,一定要多看多想: 一.Swagger的使用 3.3 JWT权限验证[修改] 二.解决JWT权限验证过期问题 三.JWT完美实现权限与接口的动态分 ...
- Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之五 || Swagger的使用 3.3 JWT权限验证【必看】
本文梯子 本文3.0版本文章 前言 1.如何给接口实现权限验证? 零.生成 Token 令牌 一.JWT ——自定义中间件 0.Swagger中开启JWT服务 1:API接口授权策略 2.自定义认证之 ...
- 简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)
前言: shiro因为其简单.可靠.实现方便而成为现在最常用的安全框架,那么这篇文章除了会用简洁明了的方式讲一下基于spring的shiro详细配置和登录注销功能使用之外,也会根据惯例在文章最后总结一 ...
- ASP.NET Core Web API + Angular 仿B站(三)后台配置 JWT 的基于 token 的验证
前言: 本系列文章主要为对所学 Angular 框架的一次微小的实践,对 b站页面作简单的模仿. 本系列文章主要参考资料: 微软文档: https://docs.microsoft.com/zh-cn ...
- Dubbo学习系列之九(Shiro+JWT权限管理)
村长让小王给村里各系统来一套SSO方案做整合,隔壁的陈家村流行使用Session+认证中心方法,但小王想尝试点新鲜的,于是想到了JWT方案,那JWT是啥呢?JavaWebToken简称JWT,就是一个 ...
- spring-boot-plus集成Shiro+JWT权限管理
SpringBoot+Shiro+JWT权限管理 Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以 ...
- 10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用
什么是JWT JSON Web Token(JWT)是目前最流行的跨域身份验证.分布式登录.单点登录等解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的 ...
- Spring MVC 项目搭建 -6- spring security 使用自定义Filter实现验证扩展资源验证,使用数据库进行配置
Spring MVC 项目搭建 -6- spring security使用自定义Filter实现验证扩展url验证,使用数据库进行配置 实现的主要流程 1.创建一个Filter 继承 Abstract ...
- asp.net 身份验证-Form 身份验证
一. .net身份验证简介 1.身份验证就是检测用户是否登录及所访问的资源是否有权限.当我们在访问一个受保护网络资源时,往往需要输入用户名.密码信息,或通过其他证书.第三方身份验证等方式.验证(Aut ...
- 2017年3月14日-----------乱码新手自学.net 之Authorize特性与Forms身份验证(登陆验证、授权小实例)
有段时间没写博客了,最近工作比较忙,能敲代码的时间也不多. 我一直有一个想法,想给单位免费做点小软件,一切思路都想好了,但是卡在一个非常基础的问题上:登陆与授权. 为此,我看了很多关于微软提供的Ide ...
随机推荐
- Python之pandas操作
中文网:https://www.pypandas.cn/ Pandas 是 Python 的核心数据分析支持库,提供了快速.灵活.明确的数据结构,旨在简单.直观地处理关系型.标记型数据.Pandas ...
- 新型大语言模型的预训练与后训练范式,苹果的AFM基础语言模型
前言:大型语言模型(LLMs)的发展历程可以说是非常长,从早期的GPT模型一路走到了今天这些复杂的.公开权重的大型语言模型.最初,LLM的训练过程只关注预训练,但后来逐步扩展到了包括预训练和后训练在内 ...
- 基于云主机的ModelArts模型训练实践,让开发环境化繁为简
本文分享自华为云社区<[开发者空间实践]云主机安装Docker并制作自定义镜像在ModelArts平台做模型训练>,作者: 开发者空间小蜜蜂. 1.1 案例介绍 在AI业务开发以及运行的过 ...
- Element Plus组件库el-table单元格内容超出时tooltip显示优化
前情 公司有经常需要做一些后台管理页面,我们选择了Element Plus,它是基于 Vue 3,面向设计师和开发者的组件库,是Vue框架生态中比较火的UI组件库,组件库丰富易用,组件链接:一个 Vu ...
- [原创] Realtek RTL8195A WIFI历史漏洞分析和新漏洞挖掘
前言 本文主要分析vdoo发现的一些RTL8195A WIFI模块的漏洞. 环境搭建 下载最新的SDK https://github.com/ambiot/amb1_arduino/blob/mast ...
- openEuler欧拉系统重置root密码
步骤: 系统启动时,出现如下页面,按e进入内核编辑模式 进入如下页面 按下光标后,找到linux开头这一行,修改ro为rw,并在行尾添加init=/bin/sh,修改后效果如下,在crtl+x保存后开 ...
- Advanced .NET Remoting: 第 9 章 4.改变编程模型
Advanced .NET Remoting: 第 9 章 4.改变编程模型 前面的所有连接器在 .NET Remoting 应用程序的服务器端和客户端两方面增强功能.可插拔的连接器架构不仅支持创建连 ...
- 【前端】【H5 API】addEventListener监听网络状态的变动
WebviewObject Webview窗口对象,用于操作加载HTML页面的窗口 属性 id:webview窗口的标识 方法:监听 addEventListener 添加事件监听器 wobj.add ...
- checker jenkins 启动配置
chmod -R 755 bin scp target/*.jar ubuntu@x:/home/ubuntu/checker/ scp config/*.yml ubuntu@x:/home/ubu ...
- Python的OCR工具pytesseract解决TesseractNotFoundError: tesseract is not installed or it's not in your PATH. See README file for more information环境变量问题
pytesseract是基于Python的OCR工具, 底层使用的是Google的Tesseract-OCR 引擎,支持识别图片中的文字,支持jpeg, png, gif, bmp, tiff等图片格 ...