过滤器与中间件很相似,过滤器(Filters)可在管道(pipeline)特定阶段(particular stage)前或后执行操作,可以将过滤器视为拦截器(interceptors)。在.NET MVC开发中,权限验证是非常重要的一部分。通过使用授权过滤器可以很方便地实现权限验证功能。这篇主要分享授权过滤器的使用。
过滤器总共有五种,Authorization Filter(授权过滤器),Resource Filter(资源过滤器),Action Filter(操作过滤器),Exception Filter(异常过滤器),Result Filter(结果过滤器)。它们在管道里有先后的执行顺序,最先执行的是授权过滤器。
最先执行,用于判断用户是否授权。如果未授权,则直接结束当前请求。这种类型的过滤器实现了 IAsyncAuthorizationFilter 或IAuthorizationFilter 接口。
在Authorization过滤器后执行,并在执行其他过滤器 (除Authorization过滤器外)之前和之后执行。由于它在Action之前执行,因而可以用来对请求判断,根据条件来决定是否继续执行Action。这种类型过滤器实现了 IAsyncResourceFilter 或 IResourceFilter 接口。
在Action执行的前后执行。与Resource过滤器不一样,它在模型绑定后执行。这种类型的过滤器实现了 IAsyncActionFilter 或 IActionFilter 接口。
异常过滤器用于管理未处理的异常,比如:用于捕获异常。这种类型的过滤器实现了 IAsyncExceptionFilter 或 IExceptionFilter 接口。
在 IActionResult 执行的前后执行,使用它能够控制Action的执行结果,比如:格式化结果等。需要注意的是,它只有在Action方法成功执行完成后才会运行。这种类型过滤器实现了 IAsyncResultFilter 或 IResultFilter 接口。
过滤器的级别总共有三种,分别是全局级别过滤器(Global scope)、控制器级别过滤器(Controller scope)和动作级别过滤器(Action scope)。
这意味着过滤器覆盖了整个MVC管道。对特定MVC路由的每次调用都将通过该过滤器。在Startup类的ConfigureServices方法中把过滤器添加到全局过滤器集合。
代码实现:
services.AddMvc(options =>
{
//全局级别过滤器:添加到全局过滤器集合
options.Filters.Add<MyAuthorizeFilterAttribute>();//此处必须过滤器名称全部书写,不能省略后缀Attribute,还有就是使用全局过滤器可以不继承特性Attribute
});
在这种情况下,过滤器被初始化为一个或多个控制器类中的属性。它将仅对已定向到目标控制器的请求采取行动。定义时需要继承抽象类Attribute,使用时需要的Controller上打上特性标签。
代码实现:
/// <summary>
/// 授权过滤器测试
/// </summary>
public class AuthFilterTestController : ControllerBase
{
/// <summary>
/// 授权过滤器测试
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("Test")]
public ActionResult AuthTest()
{
return Ok(new { IsSucesss =true, Msg="授权过滤器测试成功" });
}
}
筛选器在一个或多个操作方法中初始化为属性。它将仅对已定向到目标方法的请求执行操作。定义时需要继承抽象类Attribute,使用时需要的Action上打上特性标签
代码实现:
/// <summary>
/// 授权过滤器测试
/// </summary>
//[MyAuthorizeFilterAttribute]//控制器级别过滤器
[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略
[Route("api/Auth")]
public class AuthFilterTestController : ControllerBase
{
/// <summary>
/// 授权过滤器测试
/// </summary>
/// <returns></returns>
//[MyAuthorizeFilterAttribute]//动作级别过滤器
[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略
[HttpPost]
[Route("Test")]
public ActionResult AuthTest()
{
return Ok(new { IsSucesss =true, Msg="授权过滤器测试成功" });
}
}
安装NuGet包JWT,用于生成token和进行token验证。
/// <summary>
/// JWT帮助类
/// </summary>
public class JwtHelper
{
//私钥appsettings.json中配置
private static string secret = Helper.ConfigHelper.GetSectionValue("TokenSecret");
/// <summary>
/// 生成JwtToken
/// </summary>
/// <param name="payload">不敏感的用户数据</param>
/// <returns></returns>
public static string SetJwtEncode(Dictionary<string, object> payload)
{
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, secret);
return token;
}
/// <summary>
/// 根据jwtToken获取实体
/// </summary>
/// <param name="token">jwtToken</param>
/// <returns></returns>
public static LoginUserInfo GetJwtDecode(string token)
{
LoginUserInfo loginUserInfo = new LoginUserInfo();
try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
var algorithm = new HMACSHA256Algorithm();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
loginUserInfo = decoder.DecodeToObject<LoginUserInfo>(token, secret, verify: true);//token为之前生成的字符串
return loginUserInfo;
}
catch (System.Exception ex)
{
return loginUserInfo;
}
}
/// <summary>
/// 登录人员信息
/// </summary>
public class LoginUserInfo
{
/// <summary>
/// 用户名
/// </summary>
public string username { get; set; }
/// <summary>
/// 密码
/// </summary>
public string pwd { get; set; }
/// <summary>
/// Token 过期时间
/// </summary>
public double exp { get; set; }
}
/// <summary>
///获取返回Token信息
/// </summary>
public class TokenResult
{
/// <summary>
/// 是否成功
/// </summary>
public bool success { get; set; }
/// <summary>
/// Token字符串
/// </summary>
public string token { get; set; }
/// <summary>
/// 提示信息
/// </summary>
public string message { get; set; }
}
}
读取加密key值,用于进行生成token时加密和验证token时校验。
/// <summary>
/// 配置文件帮助类
/// </summary>
public static class ConfigHelper
{
private static IConfiguration _configuration;
static ConfigHelper()
{
//在当前目录或者根目录中寻找appsettings.json文件
var fileName = "appsettings.json";
var directory = AppContext.BaseDirectory;
directory = directory.Replace("\\", "/");
var filePath = $"{directory}/{fileName}";
if (!File.Exists(filePath))
{
var length = directory.IndexOf("/bin");
filePath = $"{directory.Substring(0, length)}/{fileName}";
}
var builder = new ConfigurationBuilder()
.AddJsonFile(filePath, false, true);
_configuration = builder.Build();
}
public static string GetSectionValue(string key)
{
return _configuration.GetSection(key).Value;
}
/// <summary>
/// 解析数据库字符串
/// </summary>
/// <param name="parameter"></param>
/// <param name="providerName"></param>
/// <returns></returns>
public static string GetParameterValue(string parameter, string connectionString)
{
var vars = connectionString.Split(';');
for (var i = 0; i < vars.Length; i++)
{
var pair = vars[i].Split('=');
if (pair[0] == parameter) { return pair[1]; }
}
return "";
}
}
配置文件中,根节点添加节点TokenSecret。
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"TokenSecret": "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"//添加加密key值
}
继承类Attribute, 接口IAuthorizationFilter ,然后实现接口OnAuthorization方法,书写自定义业务逻辑。
/// <summary>
/// 授权过滤器
/// </summary>
public class MyAuthorizeFilterAttribute : Attribute, IAuthorizationFilter
{
#region IAuthorizationFilter 授权过滤器
/// <summary>
/// 授权过滤器实现方法
/// </summary>
/// <param name="context"></param>
public void OnAuthorization(AuthorizationFilterContext context)
{
var authHeader = context.HttpContext.Request.Headers["Authorization"];
if (string.IsNullOrWhiteSpace(authHeader))
{
//var json = JsonConvert.SerializeObject(new OperationResult(OperationResultType.Error, "保存失败"));
//此接口必须携带token访问!
context.Result = new ContentResult()
{
Content = "此接口必须携带token访问,请登录携带Token访问",
ContentType = "text/html"//application/json
};
}
else //字段值不为空
{
authHeader = authHeader.ToString().Replace("Bearer", "").Trim();//去掉Bearer字串
if (authHeader == "")
{
context.Result = new ContentResult()
{
Content = "token验证失败:您没有权限调用此接口,请登录重新获取Token",
ContentType = "text/html"
};
}
else
{
LoginUserInfo LoginInfo = JwtHelper.GetJwtDecode(authHeader);
string UserName = LoginInfo.username;
string PassWord = LoginInfo.pwd;
double ExpireTimeStamp = LoginInfo.exp;
if (isTokenExpire(ExpireTimeStamp)) //这里应该验证有效期
{
//token验证失败:您没有权限调用此接口,请登录获取Token
context.Result = new ContentResult()
{
Content = "token验证失败:您没有权限调用此接口,请登录重新获取Token",
ContentType = "text/html"
};
}
}
}
}
#endregion
/// <summary>
/// 时间戳字符串
/// </summary>
/// <param name="timestamp"></param>
/// <returns></returns>
private bool isTokenExpire(double timestamp)
{
try
{
System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));//当地时区
var expireTime = startTime.AddSeconds(timestamp);
if (expireTime > DateTime.Now)
{
return false;//未过期
}
else
{
return true;//已过期
}
}
catch (Exception ex)
{
return true;
}
}
}
/// <summary>
/// JWT
/// </summary>
[Route("api/Jwt")]
public class JwtController : ControllerBase
{
/// <summary>
/// 创建JwtToken
/// </summary>
/// <param name="username"></param>
/// <param name="pwd"></param>
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
[Route("getToken")]
public ActionResult CreateToken(string username, string pwd)
{
TokenResult result = new TokenResult();
result.token = "";
result.success = false;
if (username != "" && pwd != "")
{
IDateTimeProvider provider = new UtcDateTimeProvider();
var now = provider.GetNow();
var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var secondsSinceEpoch = Math.Round((now - unixEpoch).TotalSeconds);
var payload = new Dictionary<string, object>
{
{ "username",username },
{ "pwd", pwd },
{ "exp", secondsSinceEpoch + 60*30} //失效时间秒 此处设置30分钟
};
result.token = SetJwtEncode(payload);
result.success = true;
result.message = "生成token成功";
}
else
{
result.message = "生成token失败";
}
return Ok(result);
}
}
/// <summary>
/// 授权过滤器测试
/// </summary>
//[MyAuthorizeFilterAttribute]//控制器级别过滤器
//[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略
[Route("api/Auth")]
public class AuthFilterTestController : ControllerBase
{
/// <summary>
/// 授权过滤器测试
/// </summary>
/// <returns></returns>
//[MyAuthorizeFilterAttribute]//动作级别过滤器
[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略
[HttpPost]
[Route("Test")]
public ActionResult AuthTest()
{
return Ok(new { IsSucesss =true, Msg="授权过滤器测试成功" });
}
}
根据上面测试接口的特性标签位置,此处进行验证使用动作级别过滤器进行验证,只对这一个接口进行授权校验。
不携带Token访问:
获取Token:
携带Token访问