• ASP.NET Web API 2 使用 AuthorizationFilter(授权过滤器)实现 Basic 认证


    Ø  前言

    Web 项目中授权认证方式有很多种,本文主要讲述基于 Basic 的认证方式。这是一种比较简单、常见的认证方式,主要是将请求的用户名和密码进行加密后返回给调用方,比较适合采用用户名、密码授权的项目中,比如:网站系统、后台管理系统、以及前后端分离的 APP 应用等。

     

    1.   首先,来看一下基于 Basic 认证的请求模式

    clip_image002[4]

     

    2.   具体实现步骤

    1)   首先,新建一个授权过滤器(RequestAuthorizeAttribute),可以继承于 System.Web.Http.AuthorizationFilterAttribute,或者 System.Web.Http.AuthorizeAttribute,因为 AuthorizeAttribute 也是 AuthorizationFilterAttribute 的派生类。

    /// <summary>

    /// 请求授权特性。

    /// </summary>

    public class RequestAuthorizeAttribute : System.Web.Http.AuthorizeAttribute

    {

        public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)

        {

            //首先检查 Action Controller 是否允许匿名访问

            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0

                || actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0)

            {

                base.OnAuthorization(actionContext);

            }

            else

            {   //不允许匿名访问

                var authorization = actionContext.Request.Headers.Authorization;

                if (authorization != null)

                {

                    if ("Basic".Equals(authorization.Scheme, StringComparison.CurrentCultureIgnoreCase)

                        && !string.IsNullOrEmpty(authorization.Parameter))

                    {

                        try

                        {

                            var ticket = System.Web.Security.FormsAuthentication.Decrypt(authorization.Parameter);

                            string[] array = ticket.UserData.Split('&');    //获取加密前的用户数据(用户名和密码)

                            //登录时:已经验证了用户名和密码,所以这里只需要验证票据是否过期

                            if (ticket.Expired)

                            {

                                //也可以使用 actionContext.ControllerContext.Request,与 actionContext.Request 是同一实例。

                                actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(

                                    System.Net.HttpStatusCode.BadRequest, "身份票据已过期");

                            }

                            else

                            {

                                base.IsAuthorized(actionContext);

                            }

                        }

                        catch (Exception ex)

                        {

                            actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(

                                System.Net.HttpStatusCode.Unauthorized, string.Format("授权鉴定异常,{0}", ex.Message));

                        }

                    }

                    else { HandleUnauthorizedRequest(actionContext); }

                }

                else { HandleUnauthorizedRequest(actionContext); }

            }

        }

    }

    1.   在授权过滤器中,首先检查当前 Action Controller 是否允许匿名访问。如果允许则跳过授权验证,否则需要验证传递的票据以及是否过期。

     

    2)   新建一个控制器(用于验证登录用户名和密码)

    /// <summary>

    /// 账户控制器。

    /// </summary>

    [RequestAuthorize]

    [RoutePrefix("api/account")]

    public class AccountController : ApiController

    {

        /// <summary>

        /// 登录。

        /// </summary>

        [Route("login"), HttpPost]

        [AllowAnonymous]

        public object Login(Newtonsoft.Json.Linq.JObject jObj)

        {

            //两种方式获取 JObject 中的值

            string userName = jObj.GetValue("UserName").ToObject<string>();

            Newtonsoft.Json.Linq.JToken token = null;

            string password = null;

            if (jObj.TryGetValue("Password", out token))

                password = token.ToObject<string>();

            else

                throw new System.ArgumentException("获取密码失败");   //异常将被异常过滤器捕获

     

            //模拟用户数据(应该是从数据库获取)

            var userData = new

            {

                UserId = 1,

                UserName = "aibaincheng",

                Password = "abc123" //可以将密码加密后保存至数据库(以提高安全性)

            };

            if (userData.UserName == userData.UserName && userData.Password != password)

                return new { Msg = "用户名或密码错误" };

            else

            {

                // Forms 身份验证加密

                FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(

                    1,

                    userName,

                    DateTime.Now,

                    DateTime.Now.AddMinutes(3), //票据3分钟后失效

                    true,

                    string.Format("{0}&{1}", userName, password),

                    FormsAuthentication.FormsCookiePath);

                string ticketStr = FormsAuthentication.Encrypt(ticket);

                return new { UserId = userData.UserId, Expire = DateTime.Now.AddMinutes(3), Ticket = ticketStr };

            }

        }

    }

    1.   在控制器上加了 RequestAuthorize 特性,并在 Login Action 上也加了 AllowAnonymous 特性,这样最终还是以 Action 的为准,表示该 Action 可以匿名访问(跳过了授权验证)。

    2.   拿到请求的用户名和密码,与数据库中的用户名密码进行匹配。如果匹配成功则生成加密的票据返回,否则返回错误信息。

     

    3)   再新建另一个控制器(用于授权测试)

    /// <summary>

    /// 客户控制器。

    /// </summary>

    [RequestAuthorize]

    [RoutePrefix("api/customer")]

    public class CustomerController : ApiController

    {

        [Route("get"), HttpGet]

        public object Get(int id)

        {

            //这里完成查询客户的操作...

            return new { Code = 200, Data = new { CustomerId = id, CustomerName = "客户A", Address = "上海市杨浦区" } };

        }

    }

    1.   同样在控制器上加了 RequestAuthorize 特性,表示控制器中的所有 Action 方法都将采用 RequestAuthorize 过滤器完成授权验证。

    2.   因为该接口需要授权访问,所以在调用该接口时,必须将 Ticket(票据)传递给服务器端,否则不能访问该接口。

     

    3.   模拟客户端调用

    1)   Account/Login 调用失败

    clip_image004[4]

    2)   Account/Login 调用成功

    clip_image006[4]

    3)   Customer/Get 调用失败(票据过期)

    clip_image008[4]

    4)   Customer/Get 调用失败(无票据)

    clip_image010[4]

    5)   Customer/Get 调用失败(错误的票据)

    clip_image012[4]

    6)   Customer/Get 调用成功

    clip_image014[4]

     

    Ø  总结

    1)   关于 Basic 认证的方式,服务端可能存在不同的处理方式,比如:有将请求用户名+密码进行 base64 编码的方式,也有将票据 MD5 加密等。

    2)   但无论使用哪种方式,都是将票据经过不同的处理后传递给调用方,调用方再请求其他需授权的接口时,在将票据带过来,完成身份认证。

    3)   最后简单分析下 Basic 认证的优缺点:

    1.   优点

    1)   简单,无论是调用方还是服务端实现起来都比较简单。

    2.   缺点

    1)   存在安全隐患,因为会比较频繁的向服务端提供用户名和密码来换取票据,容易被其他程序截取或破解。比较适合一些内部系统,或者安全要求比较低的项目。

    2)   只适合以用户名+密码的访问方式,所有的调用者首先必须有用户名、密码,这样就说明只能是在同一套系统或框架中使用。

    4)   好了 Basic 认证就先讨论到这里,如有不对之处,欢迎指正,感激不尽,嘿嘿~~

  • 相关阅读:
    HDU 2639 Bone Collector II (01背包,第k解)
    POJ 2184 Cow Exhibition 奶牛展(01背包,变形)
    hihoCoder #1165 : 益智游戏 (挑战赛11 B题)
    UVA 562 Dividing coins 分硬币(01背包,简单变形)
    POJ Charm Bracelet 挑饰品 (常规01背包)
    hiho一下 第四十四周 博弈游戏·Nim游戏(直接公式解)
    UVA 624 CD(01背包,要记录路径)
    118 Pascal's Triangle 帕斯卡三角形 杨辉三角形
    117 Populating Next Right Pointers in Each Node II 每个节点的右向指针 II
    116 Populating Next Right Pointers in Each Node 每个节点的右向指针
  • 原文地址:https://www.cnblogs.com/abeam/p/8698019.html
Copyright © 2020-2023  润新知