今天使用filter来做身份验证。
filter本质也是attribute,需要继承自Attribute类,并指定作用范围。但是在mvc中,针对controller和action的filter,一般不直接继承自Attribute,而是继承自FilterAttribute或者其继承类。
mvc4中实现了验证的特性,AuthorizeAttribute,实现了默认的身份验证。但是这个身份验证,是使用默认的数据连接,不能满足我们的实际要求。我们需要针对自己的系统,创建满足系统健全条件的身份验证。
查看此特性的定义如下:
// 摘要:
// 表示一个特性,该特性用于限制调用方对操作方法的访问。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
// 摘要:
// 初始化 System.Web.Mvc.AuthorizeAttribute 类的新实例。
public AuthorizeAttribute();
// 摘要:
// 获取或设置用户角色。
//
// 返回结果:
// 用户角色。
public string Roles { get; set; }
//
// 摘要:
// 获取此特性的唯一标识符。
//
// 返回结果:
// 此特性的唯一标识符。
public override object TypeId { get; }
//
// 摘要:
// 获取或设置授权用户。
//
// 返回结果:
// 授权用户。
public string Users { get; set; }
// 摘要:
// 重写时,提供一个入口点用于进行自定义授权检查。
//
// 参数:
// httpContext:
// HTTP 上下文,它封装有关单个 HTTP 请求的所有 HTTP 特定的信息。
//
// 返回结果:
// 如果用户已经过授权,则为 true;否则为 false。
//
// 异常:
// System.ArgumentNullException:
// httpContext 参数为 null。
protected virtual bool AuthorizeCore(HttpContextBase httpContext);
//
// 摘要:
// 处理未能授权的 HTTP 请求。
//
// 参数:
// filterContext:
// 封装有关使用 System.Web.Mvc.AuthorizeAttribute 的信息。filterContext 对象包括控制器、HTTP 上下文、请求上下文、操作结果和路由数据。
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext);
//
// 摘要:
// 在过程请求授权时调用。
//
// 参数:
// filterContext:
// 筛选器上下文,它封装有关使用 System.Web.Mvc.AuthorizeAttribute 的信息。
//
// 异常:
// System.ArgumentNullException:
// filterContext 参数为 null。
public virtual void OnAuthorization(AuthorizationContext filterContext);
//
// 摘要:
// 在缓存模块请求授权时调用。
//
// 参数:
// httpContext:
// HTTP 上下文,它封装有关单个 HTTP 请求的所有 HTTP 特定的信息。
//
// 返回结果:
// 对验证状态的引用。
//
// 异常:
// System.ArgumentNullException:
// httpContext 参数为 null。
protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext);
}
他继承自FilterAttribute并实现了IAuthorizationFilter接口。IAuthorizationFilter接口很简单,只有一个OnAuthorization方法,就是身份验证的主题函数。实现IAuthorizationFilter必须实现该方法。
AuthorizeAttribute特性实现了OnAuthorization方法,并将其声明为虚函数,理论上说,只需重写OnAuthorization方法,即可实现验证。但是如果那样,何必从AuthorizeAttribute继承,直接写一个特性继承自FilterAttribute,和IAuthorizationFilter接口就好了,又轻量。
AuthorizeAttribute提供了3个属性和4个方法,4个方法全都是虚函数。我们重点关注AuthorizeCore和HandleUnauthorizedRequest。重载AuthorizeCore方法,实现判断用户是否合法,并返回bool,并重载HandleUnauthorizedRequest实现AuthorizeCore返回false时的处理。
具体代码:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class CheckAccessAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return Access.CheckCookiesVal(httpContext.Request.Cookies);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpStatusCodeResult(403);
}
}
若用[CheckAccess]修饰了controller,下面的action可以使用[AllowAnonymous]来忽略验证。
其中,Access.CheckCookiesVal()是自己定义的用于验证的方法,具体定义如下:
/// <summary>
/// 登陆和身份验证具体实现
/// </summary>
public class Access
{
/// <summary>
/// 默认密钥
/// </summary>
private const string KEY = "!@#$Q";
/// <summary>
/// 获取验证码
/// </summary>
/// <param name="userID">用户id</param>
/// <param name="key">密钥</param>
/// <returns>验证码</returns>
private static string GenCookieValStr(string userID, string key)
{
string source = key + "!!?" + userID + "USB";
string valStr = Encrypt(source);
return valStr;
}
/// <summary>
/// 验证cookie
/// </summary>
/// <param name="cookies">传入cookie</param>
/// <param name="key">密钥</param>
/// <returns>是否通过验证</returns>
public static bool CheckCookiesVal(HttpCookieCollection cookies, string key = KEY)
{
try
{
return (cookies["val"].Value == GenCookieValStr(cookies["admin_id"].Value, key));
}
catch
{
return false;
}
}
/// <summary>
/// 设置cookie(登陆调用)
/// </summary>
/// <param name="cookies">传入cookie</param>
/// <param name="userID">用户id</param>
/// <param name="key">密钥</param>
public static void SetCookies(HttpCookieCollection cookies, string userID, string key = KEY)
{
HttpCookie cookie = new HttpCookie("admin_id", userID);
cookie.Expires = DateTime.Now.AddDays(1);
cookies.Add(cookie);
cookie = new HttpCookie("val", GenCookieValStr(userID, key));
cookie.Expires = DateTime.Now.AddDays(1);
cookies.Add(cookie);
}
/// <summary>
/// MD5编码
/// </summary>
/// <param name="strPwd">待编码字串</param>
/// <returns>编码结果</returns>
static public string Encrypt(string strPwd)
{
MD5 md5 = new MD5CryptoServiceProvider();
byte[] data = System.Text.Encoding.UTF8.GetBytes(strPwd);//将字符编码为一个字节序列
byte[] md5data = md5.ComputeHash(data);//计算data字节数组的哈希值
md5.Clear();
//输出为字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < md5data.Length; i++)
{
sb.Append(md5data[i].ToString("x").PadLeft(2, '0'));
}
return sb.ToString();
}
}