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