0

我有一个 ASP.NET Web API 项目,在初始用户登录时,用户名和密码通过 SSL 在 http 标头中发送并由服务器验证。

服务器使用 UserId、randmon 64 字符串 (UserToken)、到期日期和客户端 IP 地址创建数据库记录。

然后将 UserToken 发送回客户端,然后存储在 cookie 中。

所有后续请求都在 http 标头中发送 UserToken,并由服务器使用调用 IP 地址进行验证。

这样,用户名和密码只发送一次,所有使用 UserToken 的调用都会被记录下来。

我创建了两个自定义 DelegatingHandler——LoginAuthenticationHandler 和 TokenAuthenticationHandler——它们处理 http 标头并发送适当的 200 或 400 http 响应。

///////////////

似乎我唯一的问题是我希望 LoginAuthenticationHandler 也将 UserToken 返回给客户端,因此它可以存储 cookie。

抱歉冗长:-\

另外-我是Web API的新手-所以也许这不是最好的地方-但是如果可以以这种方式将UserToken传递回LoginController,那将非常方便。

感谢您的任何输入:-)

一些相关的SO帖子:

WebApi 中响应的 DelegatingHandler

是否可以将数据从 DelegatingHandler 传递到 ASP.NET Web API 中的控制器?

///////////////

  public class LoginAuthenticationHandler : DelegatingHandler
      {
          public const string BasicScheme = "Basic";
          public const string ChallengeAuthenticationHeaderName = "WWW-Authenticate";
          public const char AuthorizationHeaderSeparator = ':';

          protected override Task<HttpResponseMessage> SendAsync(
              HttpRequestMessage request,
              CancellationToken cancellationToken)
          {
              // Get Authorization Http Header
              var authHeader = request.Headers.Authorization;
              if (authHeader == null)
              {
                  // Unauthorized
                  return CreateUnauthorizedResponse();
              }
              // Check if Basic Authentication
              if (authHeader.Scheme != BasicScheme)
              {
                  // Unauthorized
                  return CreateUnauthorizedResponse();
              }
              // Decode UserName + Password from Http Header
              var encodedCredentials = authHeader.Parameter;
              var credentialBytes = Convert.FromBase64String(encodedCredentials);
              var credentials = Encoding.ASCII.GetString(credentialBytes);
              var credentialParts = credentials.Split(AuthorizationHeaderSeparator);
              if (credentialParts.Length != 2)
              {
                  // Unauthorized
                  return CreateUnauthorizedResponse();
              }
              var username = credentialParts[0].Trim();
              var password = credentialParts[1].Trim();
              // Authenticate Username + Password and Return UserToken
              var userId = new Users().GetUserIdFromUserNamePassword(username, password);
              if (userId == 0)
              {
                  // Unauthorized
                  return CreateUnauthorizedResponse();
              }
              // User is Authorized - Create New UserToken
              var ipAddress = HttpContext.Current.Request.UserHostAddress;
              var userToken = new Users().CreateUserToken(ipAddress, userId);

              return base.SendAsync(request, cancellationToken).ContinueWith(task =>
                  {
                      var response = task.Result;
             //======================================================
             // Return UserToken to Login Controller to be Stored as Cookie on the Client
                      // response.Content = userToken ??
                      // maybe set header for userToken ??
                      // HttpRequestMessage Properties ??
                      return response;
             //======================================================
                  });
          }

          private static Task<HttpResponseMessage> CreateUnauthorizedResponse()
          {
              // Send Back Http Unauthorized if Authentication Fails
              var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
              response.Headers.Add(ChallengeAuthenticationHeaderName, BasicScheme);
              var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
              taskCompletionSource.SetResult(response);
              return taskCompletionSource.Task;
          }
      }
  }
4

2 回答 2

0

也许您可以使用控制器登录而不使用 DelegatingHandler:您可以将令牌返回给客户端以添加到未来 API 调用的标头中,或者使用 Request.Headers.Add 函数将其添加到控制器的标头中。

那么您将不需要两个自定义 DelegatingHandler,TokenAuthenticationHandler 就足够了。但是您可能希望指定除了初始登录之外的所有请求都通过 TokenAuthenticationHandler 进行汇集。

为此,您需要自定义 WebAPI 路由。在默认的 Web API 项目中,目前这是在 WebApiConfig.cs 中的 WebApiConfig.Register 方法中完成的(从 Global.asax.cs 调用)。首先,让您的所有 API 调用都通过您的 TokenAuthenticationHandler 路由;然后明确添加登录路由,使其不会通过您的 TokenAuthenticationHandler:

//this message handler chain is used to put TokenAuthenticationHandleron all API requests and not Login
DelegatingHandler[] handlers = new DelegatingHandler[] {
    new TokenAuthenticationHandler()
};
var routeHandlers = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(config), handlers);
config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{action}",
    defaults: null,
    constraints: null,
    handler: routeHandlers
 );

//login route
config.Routes.MapHttpRoute(
    name: "Login",
    routeTemplate: "login/{action}",
    defaults: new { Controller = "Login" }
);

现在,您可以使用 request.Headers.TryGetValues 在 TokenAuthenticationHandler 中验证令牌以获取它:

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
            CancellationToken cancellationToken)
{
    //token validation
    IEnumerable<string> foundValues = null;
    if (request.Headers.TryGetValues("AuthenticationToken", out foundValues))
    {
        if (foundValues.Count() == 1)
        {
            string token = foundValues.Single();

            AuthenticationDAO dao = new AuthenticationDAO();
            if (dao.AuthenticateUser(token))
            {
                //add values to request.Properties for use in Web API controllers
                request.Properties.Add(new KeyValuePair<string, object>("SomeValue", 4));

                //Engage!
                return base.SendAsync(request, cancellationToken);
            }
        }
    }

    //fail if token not present or not valid
    var tcs = new TaskCompletionSource<HttpResponseMessage>();
    tcs.SetResult(new HttpResponseMessage(HttpStatusCode.Forbidden)
    {
        Content = new StringContent("Missing or invalid authorization token.")
    });
    return tcs.Task;
}

根据您将值从 DelegatingHandler 传递到控制器的原始问题,使用 request.Properties.Add 函数很容易,如上所示。

一些额外的考虑:

  • 我不确定在标头中发送登录凭据是否比仅作为请求中的内容更安全,因为它是通过 SSL 进行的。

  • 您应该考虑实施 AntiForgeryToken。 这篇文章是一个很好的开始,这篇 SO 帖子指出了如何使用 DelegatingHandler 也只在 Web 请求上检查它(允许从本机应用程序访问你的 api)。

  • 您可以轻松添加适用于强制 HTTPS的所有请求的 DelegatingHandler。

希望有帮助。我所概述的是我正在做的方式,所以如果它是错误的,我希望一些评论。

于 2013-07-10T16:29:08.140 回答
0

通常,HTTP 服务是无状态的,不适用登录的概念。LoginController 用于 MVC 控制器而不是 Web API。你试图做的不是一个好的做法,即使它在技术上是可以实现的。

如果你真的想做你想做的事,不要想着将会话数据(你称之为用户令牌)发送到 LoginController。您可以将 cookie 写入消息处理程序本身的响应中。看到这个。在这种情况下,您只能将加密数据存储到 cookie 中。您可以使用表单身份验证并使用 FA 票创建 cookie,而不是创建自己的 cookie。看到这个

顺便说一句,欺骗客户端 IP 地址是可能且容易的。

于 2013-06-23T11:37:50.560 回答