• 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【八】——Web Api的安全性


    系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html

    前言

    这一篇文章我们主要来探讨一下Web Api的安全性,到目前为止所有的请求都是走的Http协议(http://),因此客户端与服务器之间的通信是没有加密的。在本篇中,我们将在“StudentController”中添加身份验证功能——通过验证用户名与密码来判断是否是合法用户。众所周知,对于机密信息的传递,我们应该使用安全的Http协议(https://)来传输

    在Web Api中强制使用Https

    我们可以在IIS级别配置整个Web Api来强制使用Https,但是在某些情况下你可能只需要对某一个action强制使用Https,而其他的方法仍使用http。

    为了实现这一点,我们将使用Web Api中的filters——filter(过滤器)的主要作用就是可以在我们执行方法之前执行一段代码。没接触过得可以通过下图简单理解下,大神跳过:

    无标题

    我们新创建的filter将用来检测是否是安全的,如果不是安全的,filter将终止请求并返回相应:请求必须是https。

    具体做法:创建一个filter继承自AuthorizationFilterAttribute,重写OnAuthorization来实现我们的需求。

    在网站根目录下创建“Filters”文件夹,新建一个类“ForceHttpsAttribute”继承自“System.Web.Http.Filters.AuthorizationFilterAttribute”,下面上代码:

    public class ForceHttpsAttribute : AuthorizationFilterAttribute
        {
            public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                var request = actionContext.Request;
     
                if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
                {
                    var html = "<p>Https is required</p>";
     
                    if (request.Method.Method == "GET")
                    {
                        actionContext.Response = request.CreateResponse(HttpStatusCode.Found);
                        actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
     
                        UriBuilder httpsNewUri = new UriBuilder(request.RequestUri);
                        httpsNewUri.Scheme = Uri.UriSchemeHttps;
                        httpsNewUri.Port = 443;
     
                        actionContext.Response.Headers.Location = httpsNewUri.Uri;
                    }
                    else
                    {
                        actionContext.Response = request.CreateResponse(HttpStatusCode.NotFound);
                        actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
                    }
     
                }
            }
        }

    在上面代码中,我们通过actionContext参数拿到request和response对象,我们判断客户端的请求:如果不是https,那么直接响应客户端应该使用https。

    在这里,我们需要区分请求是Get还是其他(Post,Delete,Put),因为对于使用了Http的Get请求来访问资源,我们将使用https创建一个连接并添加在响应Header的Location中。这样做了之后客户端就会自动使用https来发送Get请求了。

    对于非Get请求,直接返回404,并通知客户端必须使用https来请求

    如果我们打算在整个项目中使用,那么在“WebAPIConfig”类中做如下设置:

    public static void Register(HttpConfiguration config)
       {
           config.Filters.Add(new ForceHttpsAttribute());
       }

    如果我们相对具体的Controller或Action设置时,可以做如下设置:

    //对于整个Controller强制使用Https
    [Learning.Web.Filters.ForceHttps()]
        public class CoursesController : BaseApiController
        {
        //仅对这个方法强制使用Https
            [Learning.Web.Filters.ForceHttps()]
                public HttpResponseMessage Post([FromBody] CourseModel courseModel)
                {
     
            }
    }

    使用Basic Authentication验证用户

    到目前为止,我们提供的所有Api都是公开的,任何人都能访问。但在真是场景中却是不可取的,对于某些数据,只有通过认证的用户才能访问,我们这里有两个地方恰好说明这一点:

    1.当客户端发送Get请求道“http://{your_port}/api/students/{userName}“的时候.例如:通过上述URI访问userNme为“TaiseerJoudeh”的信息时,我们必须让客户端提供TaiseerJoudeh相应的用户名和密码,对于没有提供验证信息的用户我们就不让访问,因为学生信息包含一些重要的私人信息(email,birthday等)。

    2.当客户端发送Post请求到“http://{your_port}/api/courses/2/students/{userName}“的时候,这意味着给学生选课,我们可以想一下,这里如果不做验证,那么所有人都能随便给某个学生选课,那么不就乱了么。

    对于上面的场景,我们使用Basic Authentication来进行身份验证,主要思路是使用filter从请求header部分获取身份信息,校验验证类型是否为“basic”,然后校验内容,正确就放行,否则返回401 (Unauthorized)状态码。

    在上代码前,解释一下下basic authentication:

    什么是basic authentication?

    它意味着在正式处理Http请求之前对请求者身份的校验,这可以防止服务器受到DoS攻击(Denial of service attacks)。原理是:客户端在发送Http请求的时候在Header部分提供一个基于Base64编码的用户名和密码,形式为“username:password”,消息接收者(服务器)进行验证,通过后继续处理请求。

    由于用户名和密码仅适用base64编码,因此为了保证安全性,basic authentication通常是基于SSL连接(https)

    为了在我们的api中使用,创建一个类“LearningAuthorizeAttribute”继承自System.Web.Http.Filters.AuthorizationFilterAttribute

    public class LearningAuthorizeAttribute : AuthorizationFilterAttribute
        {
     
            [Inject]
            public LearningRepository TheRepository { get; set; }
     
            public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                //forms authentication Case that user is authenticated using forms authentication
    //so no need to check header for basic authentication.
                if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
                {
                    return;
                }
     
                var authHeader = actionContext.Request.Headers.Authorization;
     
                if (authHeader != null)
                {
                    if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) &&
                        !String.IsNullOrWhiteSpace(authHeader.Parameter))
                    {
                        var credArray = GetCredentials(authHeader);
                        var userName = credArray[0];
                        var password = credArray[1];
     
                        if (IsResourceOwner(userName, actionContext))
                        {
                            //You can use Websecurity or asp.net memebrship provider to login, for
                            //for he sake of keeping example simple, we used out own login functionality
                            if (TheRepository.LoginStudent(userName, password))
                            {
                                var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null);
                                Thread.CurrentPrincipal = currentPrincipal;
                                return;
                            }
                        }
                    }
                }
     
                HandleUnauthorizedRequest(actionContext);
            }
     
            private string[] GetCredentials(System.Net.Http.Headers.AuthenticationHeaderValue authHeader)
            {
     
                //Base 64 encoded string
                var rawCred = authHeader.Parameter;
                var encoding = Encoding.GetEncoding("iso-8859-1");
                var cred = encoding.GetString(Convert.FromBase64String(rawCred));
     
                var credArray = cred.Split(':');
     
                return credArray;
            }
     
            private bool IsResourceOwner(string userName, System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                var routeData = actionContext.Request.GetRouteData();
                var resourceUserName = routeData.Values["userName"] as string;
     
                if (resourceUserName == userName)
                {
                    return true;
                }
                return false;
            }
     
            private void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
     
                actionContext.Response.Headers.Add("WWW-Authenticate",
                                                   "Basic Scheme='eLearning' location='http://localhost:8323/account/login'");
     
            }
        }

    我们重写了“OnAuthorization”,实现如下功能:

    1.从请求Header中获取校验数据

    2.判断验证信息类型为“basic”并包含base64编码

    3.将base64编码转化为string,并提取用户名和密码

    4.校验提供的验证信息是否与访问的资源信息相同(学生的详细信息只能由他自己访问)

    5.去数据库校验用户名及密码

    6.如果校验通过,则设置Thread的CurrentPrincipal,使本次接下来的请求都是通过校验的。

    7.校验没通过,返回401(Unauthorized)并添加一个WWW-Authenticate响应头,根据这个请求,客户端可以添加相应的验证信息

    在代码中实现起来就很简单了,上两个Attribute就完了:

    public class StudentsController : BaseApiController
        {
            [LearningAuthorizeAttribute]
            public HttpResponseMessage Get(string userName)
                {
     
                }
        }
    public class EnrollmentsController : BaseApiController
        {
            [LearningAuthorizeAttribute]
            public HttpResponseMessage Post(int courseId, [FromUri]string userName, [FromBody]Enrollment enrollment)
                {
     
                }
        }

    测试成果

    使用测试工具发送如下请求:

    image

    由于没有提供身份验证,于是得到如下响应:

    image

    取消:

    image

    去数据库找到对应的用户名和密码输入,得到如下结果:

    image

    总结

    因为 Base Authentication 的安全性较差,但对于无 Cookie 的 Web Api 来说,应用上非常的简单和方便。

    Base Authentication 最大的缺点是凭据会被浏览器缓存——直到你关闭浏览器为止。如果你已经对某个URI获得了授权,浏览器就会在授权头发送相应的凭据,这使其更容易受到跨站点请求伪造(CSRF)攻击

    Base Authentication 通常需要使用HTTPS方式进行加密处理。

    源码地址:https://github.com/fzrain/WebApi.eLearning

     
    作者:FZRAIN
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    viewport的故事(一)
    Laravel项目部署上线(阿里云 Ubuntu 16.04)
    Javascript数组方法总结
    html中编写js的方式
    js验证表单并提交
    html+css+js实现复选框全选与反选
    Cookie记住账号密码
    加密口令
    ASP.NET 在GridView中自动添加序号列
    ASP.NET使用递归遍历TreeView树
  • 原文地址:https://www.cnblogs.com/fzrain/p/3552423.html
Copyright © 2020-2023  润新知