为 RESTful 添加安全性 API

Adding security to RESTful API

我想实现两个需要相互通信的网站。 (由于其中一个站点为每个客户单独部署,并且分布在许多服务器上,共享数据库或私下通信不是一种选择。)所以我一直在研究 RESTful API s.

不幸的是,我 运行 了解很多我不熟悉的信息。一个问题是安全性。我们不需要任何花哨的东西——我们不是银行或任何东西。我认为我们可以使用 HTTPS 和基本的用户名和密码。

问题:

  1. 如何将用户名和密码传递给 API?它们会在 URL 中作为裸参数传递吗?

  2. .NET 是否提供任何机制来授权此类用户名和密码,或者我是否只是在每次请求时手动查看密码是否在我们的数据库中? (为了安全起见,我会散列。)

如果您控制或对连接的双方施加重大影响,客户端 ssl 证书是实现此目的的一种非常强大的方式。在这种情况下,它对我很有吸引力,因为它只需要分发一个可信的 CA 证书,这可以在创建客户端证书之前完成。它比任何用户名和密码都安全得多(因为密码不需要通过网络传输)。

我能想到的任何其他带有身份验证的解决方案,您将必须有某种数据源来验证凭据。但是x509为你解决了这个问题。我们已经在应用程序之间的工作中完成了它,除了管理证书之外,它确实非常非常好地工作。它基本上是可用的最安全的东西。

一般来说,我对 .net 了解不多,但是(不是 lmgtfy)https://support.microsoft.com/en-us/kb/315588 似乎是您正在寻找的逐步格式。

只是一个想法,这实际上取决于您 "username/password" 的意思。如果这意味着 "authorization"/访问某些 API 调用并且您希望确保客户端是 "authorized" 来调用您的 API(只有应用程序 A、B 可以对 API 进行 api 调用 - 似乎 根据您上面的评论,这就是您正在寻找的内容):

如上评论,授权header,使用JWT。有一个great/easyJWT library in Nuget

  • 这很像 "shared secret" 用来签署 "payload"(JWT

  • "sender" 将构建 JWT 并对其签名(并添加到 header 或您想要的任何协议 - 它可以是 body如果比 headers)

  • 更喜欢它
  • "receiver" 将验证 JWT 已发送

    • 这包括 handling/mitigating "replays" - JWT 规范有一个 "expire" 字段 (exp),您也可以让库验证它(或不,由你决定)

项目站点在 Github 上,带有示例。

Hth.

How would I pass the username and password to the API? Would they just be passed as bare arguments in the URL?

它可以在 URL 中,也可以在 header 中。如果您使用的是 HTTPS,它将全部加密,因此不会裸露。请参阅 this 了解更多详情。

Does .NET provide any mechanism for authorizing such username and passwords, or do I just manually see if the password is in our database on each and every request? (I would hash for security.)

不,您不需要在每次请求时都检查数据库。您可以检查一次,创建一个过期的令牌,客户端可以继续向您发送令牌。这样您就不必每次都检查数据库。

请参阅 see 此答案以获取一些有用的信息。

我认为使用 base64 编码的基本身份验证就足够了。如果没有,您可以随时更改它。以下是将其应用于后端代码的不同方法:

要将身份验证过滤器应用于控制器,请使用过滤器属性修饰控制器 class。以下代码在控制器 class 上设置 [IdentityBasicAuthentication] 过滤器,它为控制器的所有操作启用基本身份验证。

[IdentityBasicAuthentication] // Enable Basic authentication for this controller.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }
    public IHttpActionResult Post() { . . . }
}

要将过滤器应用于一个动作,请用过滤器装饰动作。以下代码在控制器的 Post 方法上设置 [IdentityBasicAuthentication] 过滤器。

[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }

    [IdentityBasicAuthentication] // Enable Basic authentication for this action.
    public IHttpActionResult Post() { . . . }
}

要将过滤器应用于所有 Web API 控制器,请将其添加到 GlobalConfiguration.Filters。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new IdentityBasicAuthenticationAttribute());

        // Other configuration code not shown...
    }
}

最后是一个实现示例,您可以根据需要更改它:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using BasicAuthentication.Results;

namespace BasicAuthentication.Filters
{
    public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
    {
        public string Realm { get; set; }

        public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
        {
            HttpRequestMessage request = context.Request;
            AuthenticationHeaderValue authorization = request.Headers.Authorization;

            if (authorization == null)
            {
                // No authentication was attempted (for this authentication method).
                // Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
                return;
            }

            if (authorization.Scheme != "Basic")
            {
                // No authentication was attempted (for this authentication method).
                // Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
                return;
            }

            if (String.IsNullOrEmpty(authorization.Parameter))
            {
                // Authentication was attempted but failed. Set ErrorResult to indicate an error.
                context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
                return;
            }

            Tuple<string, string> userNameAndPasword = ExtractUserNameAndPassword(authorization.Parameter);

            if (userNameAndPasword == null)
            {
                // Authentication was attempted but failed. Set ErrorResult to indicate an error.
                context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
                return;
            }

            string userName = userNameAndPasword.Item1;
            string password = userNameAndPasword.Item2;

            IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);

            if (principal == null)
            {
                // Authentication was attempted but failed. Set ErrorResult to indicate an error.
                context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
            }
            else
            {
                // Authentication was attempted and succeeded. Set Principal to the authenticated user.
                context.Principal = principal;
            }
        }

        protected abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,
            CancellationToken cancellationToken);

        private static Tuple<string, string> ExtractUserNameAndPassword(string authorizationParameter)
        {
            byte[] credentialBytes;

            try
            {
                credentialBytes = Convert.FromBase64String(authorizationParameter);
            }
            catch (FormatException)
            {
                return null;
            }

            // The currently approved HTTP 1.1 specification says characters here are ISO-8859-1.
            // However, the current draft updated specification for HTTP 1.1 indicates this encoding is infrequently
            // used in practice and defines behavior only for ASCII.
            Encoding encoding = Encoding.ASCII;
            // Make a writable copy of the encoding to enable setting a decoder fallback.
            encoding = (Encoding)encoding.Clone();
            // Fail on invalid bytes rather than silently replacing and continuing.
            encoding.DecoderFallback = DecoderFallback.ExceptionFallback;
            string decodedCredentials;

            try
            {
                decodedCredentials = encoding.GetString(credentialBytes);
            }
            catch (DecoderFallbackException)
            {
                return null;
            }

            if (String.IsNullOrEmpty(decodedCredentials))
            {
                return null;
            }

            int colonIndex = decodedCredentials.IndexOf(':');

            if (colonIndex == -1)
            {
                return null;
            }

            string userName = decodedCredentials.Substring(0, colonIndex);
            string password = decodedCredentials.Substring(colonIndex + 1);
            return new Tuple<string, string>(userName, password);
        }

        public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
        {
            Challenge(context);
            return Task.FromResult(0);
        }

        private void Challenge(HttpAuthenticationChallengeContext context)
        {
            string parameter;

            if (String.IsNullOrEmpty(Realm))
            {
                parameter = null;
            }
            else
            {
                // A correct implementation should verify that Realm does not contain a quote character unless properly
                // escaped (precededed by a backslash that is not itself escaped).
                parameter = "realm=\"" + Realm + "\"";
            }

            context.ChallengeWith("Basic", parameter);
        }

        public virtual bool AllowMultiple
        {
            get { return false; }
        }
    }
}

如果您还想阅读更多内容,那么这里有一个很好的 article,里面有详细的介绍。我从这篇文章中复制了上面的代码。它有很多很棒的信息。