保护ASP.NET Web API

开发完Web API后,根据您的需求,在将其公开给客户之前,您可能需要保护API服务的某些或全部部分,以便只有经过验证的用户才能访问您的API服务。 可以使用身份验证和授权机制来实现ASP.NET中的这种安全保护。

认证方式

身份验证是确定某人或某物是否实际上是声称的那个人或身份的过程。 通过使用身份验证机制,我们确保Web API服务收到的每个请求都是从具有适当凭据的客户端发送的。

使用消息处理程序进行身份验证

消息处理程序是接收HTTP请求并返回HTTP响应的类。 消息处理程序是从抽象类HttpMessageHandler派生的类。 它们对于在HTTP消息级别(而不是控制器操作)运行的横切关注点很有用。 例如,消息处理程序可能会:

  • 读取或修改请求标头
  • 向响应添加响应头
  • 在请求到达控制器之前对其进行验证

在Web API中,通常,一系列消息处理程序链接在一起,形成称为委托处理程序的模式。

通过消息处理程序的HTTP请求流

这些处理程序的设置顺序很重要,因为它们将顺序执行。

最重要的处理程序位于最顶端,保护进来的所有内容。如果检查通过,它将把请求向下传递到下一个委托处理程序,依此类推。

如果一切顺利,它将到达API控制器并执行所需的操作。 但是,如果处理程序中的任何检查失败,则该请求将被拒绝,并将响应发送到客户端。

掌握了如此多的理论,现在让我们为处理程序编写代码。 我们将在本文中创建两个消息处理程序:

  1. APIKeyHandler负责拦截HTTP请求并确保其标头包含API密钥的处理程序
  2. AuthHandler负责验证用户凭据和角色的处理程序

API密钥认证

在您的Web API项目中,创建一个名为MessageHandlers的文件夹。 并添加一个类APIKeyHandler.cs

public class APIKeyHandler : DelegatingHandler
    {
        //set a default API key 
        private const string yourApiKey = "X-some-key";

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            bool isValidAPIKey = false;
            IEnumerable<string> lsHeaders;
            //Validate that the api key exists

            var checkApiKeyExists = request.Headers.TryGetValues("API_KEY", out lsHeaders);

            if (checkApiKeyExists)
            {
                if (lsHeaders.FirstOrDefault().Equals(yourApiKey))
                {
                    isValidAPIKey = true;
                }                
            }
                
            //If the key is not valid, return an http status code.
            if (!isValidAPIKey)
                return request.CreateResponse(HttpStatusCode.Forbidden, "Bad API Key");

            //Allow the request to process further down the pipeline
            var response = await base.SendAsync(request, cancellationToken);

            //Return the response back up the chain
            return response;
        }
    }

APIKeyHandler.cs继承自DelegatingHandler ,后者又继承自HttpMessageHandler 。 这使我们可以覆盖检查HTTP请求的功能,并控制是要允许此请求顺着管道流向下一个处理程序和控制器,还是要通过发送自定义响应来中止请求。

在此类中,我们将通过重写SendAsync方法来实现此目的。 此方法在每个HTTP请求的标头中查找API密钥( API_KEY ,并且仅在请求标头中存在有效的API密钥时,才将请求传递给控制器​​。

现在,要查看此处理程序的运行情况,我们首先需要从Global.asax文件中的Application_Start方法中将其注册到我们的应用程序中。

GlobalConfiguration.Configuration.MessageHandlers.Add(new APIKeyHandler());

尝试调用通过Web API控制器公开的任何方法,并且应该看到“错误的API密钥”作为响应。

对于本文中的演示,我使用的是我以前的文章“ 开发ASP.NET Web API ”中创建的项目和URL。

API密钥处理程序演示

让我们验证一下APIKeyHandler 通过创建带有正确标头的HTTP请求,可以正常工作。 为此,我们需要创建一个具有键值的HTTP标头:

"API_KEY" : "X-some-key"

我正在使用Mozilla浏览器插件“ HTTP工具”在此处创建HTTP请求标头。

带有HTTP工具的HTTP标头

现在,处理程序将HTTP请求一直传递到控制器。

因此,我们的API密钥检查处理程序现在已经到位。 这样可以保护我们的Web API的安全,以确保只有那些提供了有效API密钥的客户端才能访问此服务。 接下来,我们将研究如何基于用户角色实现安全性。

基本认证

顾名思义,基本身份验证是身份验证HTTP请求的最简单,最基本的形式。 客户端在每个HTTP请求上在Authorize标头中发送Base64编码的凭据,并且只有在验证了凭据之后,API才会返回预期的响应。 基本身份验证不需要服务器端会话存储或cookie的实现,因为API会验证每个请求。

一旦了解了Web API中的基本身份验证实现,将很容易挂接其他形式的身份验证。 只有身份验证过程会有所不同,并且Web API挂钩在完成时将是相同的。

为了验证用户凭据,我们创建一个IPrincipal对象,该对象代表当前的安全上下文。

在其中添加一个名为Security的新文件夹和一个新类TestAPIPrincipal.cs

public class TestAPIPrincipal : IPrincipal
    {
        //Constructor
        public TestAPIPrincipal(string userName)
        {
            UserName = userName;
            Identity = new GenericIdentity(userName);
        }

        public string UserName { get; set; }
        public IIdentity Identity { get; set; }
        public bool IsInRole(string role)
        {
            if (role.Equals("user"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }        
    }

所述IIdentity与主体关联对象有一个名为属性IsAuthenticated 。 如果用户通过了身份验证,则此属性将返回true;否则,此属性将返回true。 否则,它将返回false。

现在,让我们创建另一个名为AuthHandler.cs处理程序。

public class AuthHandler : DelegatingHandler
    {
        string _userName = "";
        
        //Method to validate credentials from Authorization
        //header value
        private bool ValidateCredentials(AuthenticationHeaderValue authenticationHeaderVal)
        {
            try
            {
                if (authenticationHeaderVal != null
                    && !String.IsNullOrEmpty(authenticationHeaderVal.Parameter))
                {
                    string[] decodedCredentials
                    = Encoding.ASCII.GetString(Convert.FromBase64String(
                    authenticationHeaderVal.Parameter))
                    .Split(new[] { ':' });

                    //now decodedCredentials[0] will contain
                    //username and decodedCredentials[1] will
                    //contain password.

                    if (decodedCredentials[0].Equals("username")
                    && decodedCredentials[1].Equals("password"))
                    {
                        _userName = "John Doe";
                        return true;//request authenticated.
                    }
                }
                return false;//request not authenticated.
            }
            catch {
                return false;
            }
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //if the credentials are validated,
            //set CurrentPrincipal and Current.User
            if (ValidateCredentials(request.Headers.Authorization))
            {
                Thread.CurrentPrincipal = new TestAPIPrincipal(_userName);
                HttpContext.Current.User = new TestAPIPrincipal(_userName);
            }
            //Execute base.SendAsync to execute default
            //actions and once it is completed,
            //capture the response object and add
            //WWW-Authenticate header if the request
            //was marked as unauthorized.

            //Allow the request to process further down the pipeline
            var response = await base.SendAsync(request, cancellationToken);

            if (response.StatusCode == HttpStatusCode.Unauthorized
                && !response.Headers.Contains("WwwAuthenticate"))
            {
                response.Headers.Add("WwwAuthenticate", "Basic");
            }

            return response;
        }
    }

此类包含私有方法ValidateCredentials ,该方法检查HTTP请求标头中的已解码用户名和密码值,以及SendAsync 拦截HTTP请求的方法。

如果客户端的凭据有效,则当前的IPrincipal 对象附加到当前线程,即Thread.CurrentPrincipal 。 我们还设置HttpContext.Current.User以使安全上下文一致。 这使我们可以从应用程序中的任何位置访问当前用户的详细信息。

验证请求后,将调用base.SendAsync将请求发送到内部处理程序。 如果响应中包含HTTP未经授权的标头,则代码将注入WwwAuthenticate标头,其值为Basic 告知客户我们的服务需要基本身份验证。

现在,我们需要在注册此处理程序Global.Asax因为我们没有为我们的类ApiKeyHandler 。 确保AuthHandler 处理程序位于第一个处理程序注册的下面,以确保顺序正确。

GlobalConfiguration.Configuration.MessageHandlers.Add(new APIKeyHandler());
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthHandler());

但是,在我们看到基本的身份验证之前,我们首先需要实现授权。

授权书

授权是在验证通过身份验证的用户是否可以执行特定操作或消耗特定资源。 Web API中的此过程在管道中的稍后阶段
身份验证以及执行控制器操作之前。

使用授权属性

ASP.NET MVC Web API提供了一个称为AuthorizeAttribute的授权过滤器 验证请求的IPrincipal ,检查其Identity.IsAuthenticated属性,如果该值为false且将不执行所请求的操作方法,则返回401 Unauthorized HTTP状态。 此过滤器可以应用于不同级别,例如控制器级别或操作级别,并且可以使用[Authorize]轻松应用 控制器或操作之上的语法。

[Authorize]
public class ClassifiedsController : ApiController

设置此属性后,将防止未经授权的用户访问控制器中的所有操作方法。

首先,我们的基本身份验证处理程序开始设置当前用户的身份IPrincipal对象。 然后,在此请求到达控制器之前, AuthorizeAttribute验证当前用户对特定控制器/操作的访问。

为了了解这一点,首先让我们创建一个没有适当凭据的HTTP请求。

拒绝未经授权的用户访问

该访问被AuthorizeAttribute拒绝。

现在,让我们这次创建具有Authorization标头键/值的另一个请求,如下所示:

授权: 基本dXNlcm5hbWU6cGFzc3dvcmQ =

此处,值dXNlcm5hbWU6cGFzc3dvcmQ= 是个   username:password Base64编码形式。

该请求将按预期获得对控制器/操作的访问权限。

为具有适当凭据的用户授予访问权限

这是确保整个控制器的公共行为安全的一个示例。

行动级别授权

我们还可以通过设置 只能在操作级别使用[Authorize]属性。 这样做将使我们能够在同一控制器中同时拥有受保护和不受保护的动作。

//[Authorize]
    public class ClassifiedsController : ApiController
    {
        public List<ClassifiedModel> Get(string id)
        {
            return ClassifiedService.GetClassifieds(id);
        }

        [Authorize]
        public List<ClassifiedModel> Get()
        {
            return ClassifiedService.GetClassifieds("");
        }

[AllowAnonymous]属性

在控制器内同时具有受保护和不受保护的动作的另一种方式是通过使用[AllowAnonymous]属性。 当我们在控制器级别设置[Authorize]属性并为控制器内部的任何操作设置[AllowAnonymous]属性时,该操作将跳过[Authorize]属性。

角色和用户检查

也可以过滤某些角色和用户的访问权限。 例如,我们可以在控制器和动作上添加类似[Authorize(Roles = "Admin")]内容。

定制授权属性

最后,我们还可以根据需要创建自己的自定义授权属性。 实现此目的的方法之一是扩展AuthorizeAttribute

假设我们想通过限制对不在IP地址范围内的用户的访问来将Web API服务限制在世界的某些地方。 为此,我们可以通过从AuthorizeAttribute派生来创建自定义授权属性 类并覆盖IsAuthorized方法。

public class RestrictIPsAttribute: System.Web.Http.AuthorizeAttribute
    {
        protected override bool IsAuthorized(HttpActionContext context)
        {
            var ip = HttpContext.Current.Request.UserHostAddress;

            //check for ip here
            if (ip.Contains(""))
            {
                return true;
            }
            return false;
        }
    }

一旦有了自定义的Authorize属性,就可以用它来装饰控制器/动作。

[RestrictIPsAttribute]
public List<ClassifiedModel> Get()
{
    return ClassifiedService.GetClassifieds("");
}

结论

在本文中,我们研究了如何在将服务公开给外界之前保护ASP.NET Web API服务的安全。 我们研究了如何对有效的API密钥和有效的用户凭据的HTTP请求进行身份验证。 掌握了这么多的知识,我相信我们已经准备好为我们的API开发任何自定义安全性。

对于那些刚刚开始使用Laravel或希望通过扩展来扩展您的知识,网站或应用程序的人,我们可以在Envato Market中进行很多研究。

希望您喜欢阅读本文并从中学到很多,并记得在下面的提要中留下任何问题或评论!

翻译自: https://code.tutsplus.com/tutorials/securing-aspnet-web-api--cms-26012

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值