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上加上这个属性。

测试

 

 

未登录,调用第一个方法:

 

 

未登录,调用第二个方法:

 

 

登录后调用第二个方法:

 

 

 

 登录超时后调用第二个方法:

 

posted @ 2020-12-24 18:59  陈鹏昱Chen  阅读(270)  评论(0编辑  收藏  举报