WebAPI-基于Forms的用户认证授权解决方案
使用.net framework内置的forms认证库进行的实现,需要添加以下的引用
using System.Net.Http; using System.Net.Http.Headers; using System.Security.Principal; using System.Web; using System.Web.Http; using System.Web.Http.Filters; using System.Web.Security;
实现思路: System.Web.Http.Filters命名空间中的AuthorizationFilterAttribute类,其中的OnAuthorization方法会在Action执行前运行以检查授权。
这是一个属性类(Attribute), 所以我们定义自己的属性类,继承该类,并重写虚方法OnAuthorization以实现自己的认证授权逻辑
实例中使用了Session, WebAPI默认不启用Session,所以首先需要配置启用Session,也可以不使用Session。
Global.asax添加代码:
public override void Init() { PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest; base.Init(); } void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e) { HttpContext.Current.SetSessionStateBehavior( SessionStateBehavior.Required); }
定义自己的属性类并继承AuthorizationFilterAttribute、重写OnAuthorization方法
public class FormsAuthenticationFilterAttribute : AuthorizationFilterAttribute { private const string UnauthorizedMessage = "请求未授权,拒绝访问。"; private const string OutOfTime = "登录超时,请重新登录"; public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0) { base.OnAuthorization(actionContext); return; } if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated) { base.OnAuthorization(actionContext); return; } var cookies = actionContext.Request.Headers.GetCookies(); if (cookies == null || cookies.Count < 1) { actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage, Encoding.UTF8) }; return; } FormsAuthenticationTicket ticket = GetTicket(cookies); if (ticket == null) { actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage, Encoding.UTF8) }; return; } //session超时则重新登录 if (HttpContext.Current.Session["TICKET"] == null|| HttpContext.Current.Session["USER_AUTHORITIES"]==null) { actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(OutOfTime, Encoding.UTF8) }; FormsAuthentication.SignOut(); return; }//将cookievalue写入session var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName]; if (cookie != null) { //Cookie未过期时,读取cookie,重新写一次Session HttpContext.Current.Session["TICKET"] = cookie.Value; HttpContext.Current.Session["USER_AUTHORITIES"]=AuthorizedUser.Current.UserInfo; } var principal = new GenericPrincipal(new FormsIdentity(ticket), null); HttpContext.Current.User = principal; Thread.CurrentPrincipal = principal; base.OnAuthorization(actionContext); } private FormsAuthenticationTicket GetTicket(Collection<CookieHeaderValue> cookies) { FormsAuthenticationTicket ticket = null; foreach (var item in cookies) { var cookie = item.Cookies.SingleOrDefault(c => c.Name == FormsAuthentication.FormsCookieName); if (cookie != null) { ticket = FormsAuthentication.Decrypt(cookie.Value); break; } } return ticket; } }
这里将在每个action运行前运行,主要做票据判断和超时判断相关的操作。同时也将Ticket和当前的用户对象存到了session对象中,不使用session的话可以可以去除。
然后开始用户相关的操作:
AccountModel类
新建一个类,用来做判断用户名和密码、获取用户、注销等 和用户有关操作的类:
public class AccountModel { /// <summary> /// 读取数据库用户表数据,判断用户密码是否匹配 /// </summary> /// <param name="name"></param> /// <param name="password"></param> /// <returns></returns> internal bool ValidateUserLogin(string name, string password) { //这里写 判断用户名和密码是否正确的逻辑 return true; } /// <summary> /// 获取用户数据 /// </summary> /// <param name="userName"></param> /// <returns></returns> internal void GetUserAuthorities(string userName) { //这里写读取用户信息的逻辑: UserInfo user = new UserInfo() { Name = "张三", RightCode = "123123" }; HttpContext.Current.Session["USER_AUTHORITIES"] = user; HttpContext.Current.Session["TICKET"] = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName].Value; } /// <summary> /// 用户注销执行的操作 /// </summary> internal void Logout() { FormsAuthentication.SignOut(); } }
考虑到部分操作也需要获取到当前登录用户的详细信息,所以新建一个"已授权用户信息"类。
封装一些get;set;属性, 用以获取当前用户的票据、权限、基本信息等;
AuthorizedUser类
public class AuthorizedUser { public string UserTicket { get { if (HttpContext.Current.Session["TICKET"] != null) return HttpContext.Current.Session["TICKET"].ToString(); else return string.Empty; } } public UserInfo UserInfo { get { return HttpContext.Current.Session["USER_AUTHORITIES"] as UserInfo; } } private AuthorizedUser() { } public static AuthorizedUser Current { get { return new AuthorizedUser(); } } }
public class UserInfo { public string Name { get; set; } public string RightCode { get; set; } }
完成上面的实现之后,就可以在自己的Controller中头上标上这个属性类,就可以实现相关认证操作了
但是考虑到每个Controller中都需要这样写,而且当前用户信息也是通用了,所以新建一个控制器基类,在基类中使用这个属性,并加上一些通用的方法,然后其他控制器继承自这个基类就可以了:
自定义的控制器基类
放一些通用的操作之类
[FormAuthenticationFilter] public class ApiControlerBase : ApiController { public UserInfo UserInfo { get { return AuthorizedUser.Current.UserInfo; } } internal void Logout() { FormsAuthentication.SignOut(); } }
然后其他的控制器就继承它就可以,不需要加[Attribute]:
登录/注销的代码
[AllowAnonymous] [AcceptVerbs("Get")] [Route("Api/Values/Login")] public HttpResponseMessage Login(string uname, string pwd) { AccountModel am = new AccountModel(); if (am.ValidateUserLogin(uname, pwd)) { //创建票据 FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, uname, DateTime.Now, DateTime.Now.AddMinutes(30), false, string.Empty); //加密票据 string authTicket = FormsAuthentication.Encrypt(ticket); //存储为cookie HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, authTicket); cookie.Path = FormsAuthentication.FormsCookiePath; HttpContext.Current.Response.AppendCookie(cookie); //读取用户权限数据 am.GetUserAuthorities(uname); return Request.CreateResponse(HttpStatusCode.OK, "登录成功"); } else { HttpContext.Current.Response.AppendCookie(new HttpCookie(FormsAuthentication.FormsCookieName) { Expires = DateTime.Now.AddDays(-10) }); return Request.CreateErrorResponse(HttpStatusCode.NotFound, "无效的用户名或密码"); } } [AllowAnonymous] [Route("Api/Values/Logout")] public string Logout() { var accountModel = new AccountModel(); accountModel.Logout(); RedirectToRoute("Login", "Account"); return ""; }
登录时保存票据、相关session、cookie 。
* 加上 [AllowAnonymous]属性时,将不会进行权限认证,参照在FormAuthenticationFilterAttribute中定义的操作:
所以在登录、登出等操作的action上加上这个属性。
测试
未登录,调用第一个方法:
未登录,调用第二个方法:
登录后调用第二个方法:
登录超时后调用第二个方法: