(三).net core jwt+IAuthorizationFilter(权限过滤器)+IFilterMetadata(自定义过滤器)实现请求接口的权限认证
1:Startup.cs里面的ConfigureServices方法里面添加全局过滤器
services.AddMvc(options =>
{
options.Filters.Add(typeof(ApiAuthorizeFilter));
});
2:权限过滤器整体校验
public class ApiAuthorizeFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
//如果是调用的登陆接口不需要做任何验证,就不往下面走了,直接return了
if (context.Filters
.Where(item => item is ILoginUserFilter)
.FirstOrDefault() is ILoginUserFilter LogintokenFilter)
{
return;
}
//当调用的接口是加了[AllowAnonymous]特性的,绕过权限验证的
if (context.ActionDescriptor.EndpointMetadata.Any(item => item is IAllowAnonymous))
{
//当调用的接口是加了[FixedToken]特性的都会走这里,FixedToken继承了AllowAnonymous,所以会走IAllowAnonymous
//(过期token也能访问接口,但必须是最新的那次登陆的token,也就是单设备登陆验证)
if (context.Filters
.Where(item => item is IFixedTokenFilter)
.FirstOrDefault() is IFixedTokenFilter tokenFilter)
{
//绕过了权限认证的,需要手动去验证token的合法性
tokenFilter.OnAuthorization(context);
return;
}
else
{
//果如只加了[AllowAnonymous]没有加[FixedToken]标签的,就直接绕后任何权限,表示可以直接访问的接口
return;
}
}
IMemoryCache cache = context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;
string token = cache.Get<string>("newjwttoken");
//用户token错误或者过期,或者token不等于存储的最新的那次token
//(token过期或者不等于最新的那次登陆的token都不能访问接口,与token对比的作用是验证单设备登陆)
if (!context.HttpContext.User.Identity.IsAuthenticated
|| (
token != ((ClaimsIdentity)context.HttpContext.User.Identity)?.BootstrapContext?.ToString())
)
{
context.Result = new ContentResult()
{
Content = new { result = "denied", status = false, code = (int)HttpStatusCode.Unauthorized, message = "登陆已过期" }.Serialize(),
ContentType = "application/json",
StatusCode = (int)HttpStatusCode.Unauthorized
};
return;
}
//获取new Claim (JwtRegisteredClaimNames.Exp,exp)里面设置的过期时间
DateTime expDate = context.HttpContext.User.Claims.Where(x => x.Type == JwtRegisteredClaimNames.Exp)
.Select(x => x.Value).FirstOrDefault().GetTimeSpmpToDate();
//动态标识刷新token,过期时间是120分钟,过期时间减去当前时间=40分种,
//就给前端返回一个pms_exp的标识,提醒前端要过期了,让前端重新刷新token,并且呢时候访问的接口不能是调用刷新token的接口
if ((expDate - DateTime.Now).TotalMinutes < 120 / 3 && context.HttpContext.Request.Path != replaceTokenPath)
{
context.HttpContext.Response.Headers.Add("pms_exp", "1");
}
}
//刷新token访问的接口
private static readonly string replaceTokenPath = "/api/Sys_User/replaceToken";
}
3:自定义过滤器严重token合法性(此处token可以过期,但是必须是最后那次登陆的token而且有效)
/// <summary>
/// 自定义过滤器
/// </summary>
public interface IFixedTokenFilter : IFilterMetadata
{
AuthorizationFilterContext OnAuthorization(AuthorizationFilterContext context);
}
//这里继承了IAllowAnonymous,所以只要加上[FixedToken]就相当于拥有了[AllowAnonymous]的特性
public class FixedTokenAttribute : Attribute, IFixedTokenFilter, IAllowAnonymous
{
public AuthorizationFilterContext OnAuthorization(AuthorizationFilterContext context)
{
string fixedtoken = string.Empty;
//token过期或者token是无效随便输入的token,要求token过期也要能使用,就需要手动对token合法性进行验证
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
fixedtoken = context.HttpContext.Request.Headers["Authorization"];
fixedtoken = fixedtoken?.Replace("Bearer ", "");
//验证传过来的token不允许为空
if (string.IsNullOrEmpty(fixedtoken))
{
//context.Result就表示httpcontext已经终结了,不会在往下走了
context.Result = new ContentResult()
{
Content = new { result = "denied", status = false, code = (int)HttpStatusCode.Unauthorized, message = "没有传入token" }.Serialize(),
ContentType = "application/json",
StatusCode = (int)HttpStatusCode.Unauthorized
};
return context;
}
string userId = JwtHelper.GetUserId(fixedtoken);
if (string.IsNullOrWhiteSpace(userId))
{
//context.Result就表示httpcontext已经总结了,不会在往下走了
context.Result = new ContentResult()
{
Content = new { result = "denied", status = false, code = (int)HttpStatusCode.Unauthorized, message = "token不正确" }.Serialize(),
ContentType = "application/json",
StatusCode = (int)HttpStatusCode.Unauthorized
};
return context;
}
}
else
{
//页面传入的token和jwt生成token一致,并且token没有失效,可以用下面方式直接取token
fixedtoken = ((ClaimsIdentity)context.HttpContext.User.Identity)?.BootstrapContext?.ToString();
}
IMemoryCache cache= context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;
string token= cache.Get<string>("newjwttoken");
//判断当前用户的token与缓存的token是否相同(与缓存token做对比的作用是验证单机登陆,不允许多台设备同时登陆)
if (token != "Bearer "+ fixedtoken )
{
context.Result = new ContentResult()
{
Content = new { result = "denied", status = false, code = (int)HttpStatusCode.Unauthorized, message = "token已失效" }.Serialize(),
ContentType = "application/json",
StatusCode = (int)HttpStatusCode.Unauthorized
};
return context;
}
return context;
}
}
4:调用登陆接口的时候不需要验证,需要ILoginUserFilter 来标识
public interface ILoginUserFilter : IFilterMetadata
{
}
public class LoginUserAttribute : Attribute, ILoginUserFilter
{
}
注意点:
自定义过滤器里面定义的方法必须要手动调用才会有效(IFilterMetadata),系统过滤器(IAuthorizationFilter,IActionFilter,IExceptionFilter)不用去调用,直接重新里面的的固定方法就可以了
#region 可以用Claim存HttpContext.User值,取值用HttpContext.User.FindFirstValue("xx")
//var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Jti, "11"), new Claim("ww", "22") };
//context.HttpContext.User.AddIdentity(new ClaimsIdentity(claims));
//string name=context.HttpContext.User.FindFirstValue("ww");
//string name1 = HttpContext.User.FindFirstValue("ww");
#endregion
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)