使用基本身份验证向Katana Hosted Web API添加cookie

时间:2013-09-16 13:53:07

标签: c# asp.net-web-api owin katana

我已经为Katana实现了一个基本身份验证中间件(下面的代码)。

(我的客户端托管在跨域,然后是实际的API)。

  

如果满足以下条件,浏览器可以跳过预检请求   是的:

     

请求方法是GET,HEAD或POST,而应用程序则不是   设置除Accept,Accept-Language之外的任何请求标头,   内容 - 语言,内容类型或最后事件ID,以及内容类型   标头(如果设置)是以下之一:   application / x-www-form-urlencoded multipart / form-data text / plain

在javascript中,我在服务器接受请求的所有请求上设置了认证头(使用jquery,beforeSend)。这意味着上面将发送所有请求的选项请求。我不想那样。

function make_base_auth(user, password) {
    var tok = user + ':' + password;
    var hash = Base64.encode(tok);
    return "Basic " + hash;
}

我该怎么做才能解决这个问题?我的想法是在用户身份验证后将用户信息存储在cookie中。

我还在katana项目中看到了Microsoft.Owin.Security.Cookies - 这可能是我想要的而不是我自己的基本身份验证吗?

BasicAuthenticationMiddleware.cs

using Microsoft.Owin;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security.Infrastructure;
using Owin;

namespace Composite.WindowsAzure.Management.Owin
{
    public class BasicAuthenticationMiddleware : AuthenticationMiddleware<BasicAuthenticationOptions>
    {
        private readonly ILogger _logger;

        public BasicAuthenticationMiddleware(
           OwinMiddleware next,
           IAppBuilder app,
           BasicAuthenticationOptions options)
            : base(next, options)
        {
            _logger = app.CreateLogger<BasicAuthenticationMiddleware>();
        }

        protected override AuthenticationHandler<BasicAuthenticationOptions> CreateHandler()
        {
            return new BasicAuthenticationHandler(_logger);
        }
    }
}

BasicAuthenticationHandler.cs

using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using System;
using System.Text;
using System.Threading.Tasks;

namespace Composite.WindowsAzure.Management.Owin
{
    public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
    {
        private readonly ILogger _logger;

        public BasicAuthenticationHandler(ILogger logger)
        {
            _logger = logger;
        }
        protected override Task ApplyResponseChallengeAsync()
        {
            _logger.WriteVerbose("ApplyResponseChallenge");
            if (Response.StatusCode != 401)
            {
                return Task.FromResult<object>(null);
            }

            AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);

            if (challenge != null)
            {
                Response.Headers.Set("WWW-Authenticate", "Basic");
            }

            return Task.FromResult<object>(null);
        }
        protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
        {
            _logger.WriteVerbose("AuthenticateCore");

            AuthenticationProperties properties = null;

            var header = Request.Headers["Authorization"];

            if (!String.IsNullOrWhiteSpace(header))
            {
                var authHeader = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(header);

                if ("Basic".Equals(authHeader.Scheme, StringComparison.OrdinalIgnoreCase))
                {
                    string parameter = Encoding.UTF8.GetString(Convert.FromBase64String(authHeader.Parameter));
                    var parts = parameter.Split(':');
                    if (parts.Length != 2)
                        return null;

                    var identity = await Options.Provider.AuthenticateAsync(userName: parts[0], password: parts[1], cancellationToken: Request.CallCancelled);
                    return new AuthenticationTicket(identity, properties);
                }
            }

            return null;
        }
    }
}

Options.Provider.AuthenticateAsync验证了用户名/密码,并在验证后返回身份。

规格

我要解决的是:我有一个使用N Azure云服务部署的Owin托管WebAPI。它们中的每一个都链接到一个存储帐户,该帐户包含用户名/散列密码列表。

从我的客户端我将这N个服务中的任何一个添加到客户端,然后可以通过他们的webapis与他们进行通信。他们被认证锁定。第一步是使用上面提供的列表通过基本身份验证方案验证用户。之后,我希望很容易添加其他身份验证方案,如Owin,UseWindowsAzureAuthentication ect或UseFacebookAuthentication。 (我确实有一个挑战,因为webapi没有网络前端,除了添加服务的跨域网站)。

如果你擅长Katana并想与我一起工作,请随时给我发邮件至pks@s-innovations.net。我也会在最后提供答案。

更新

根据回答我做了以下事情:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = "Application",
    AuthenticationMode = AuthenticationMode.Active,
    LoginPath = "/Login",
    LogoutPath = "/Logout",
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = context =>
        {
            //    context.RejectIdentity();
            return Task.FromResult<object>(null);
        },
        OnResponseSignIn = context =>
        {

        }
    }
});

app.SetDefaultSignInAsAuthenticationType("Application");

我认为它必须在AuthenticationMode = Active中,否则Authorize属性不起作用?

我的webapi控制器到底需要做什么来交换cookie?

public async Task<HttpResponseMessage> Get()
{
    var context = Request.GetOwinContext();
    //Validate Username and password
    context.Authentication.SignIn(new AuthenticationProperties()
    {
        IsPersistent = true
    },
    new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, "MyUserName") }, "Application"));

    return Request.CreateResponse(HttpStatusCode.OK);
}

上面还好吗?

当前解决方案

我已将BasicAuthenticationMiddleware添加为活动版,将上述CookieMiddleware添加为被动版。

然后在AuthenticateCoreAsync中,检查我是否可以使用Cookie登录,

 var authContext = await Context.Authentication.AuthenticateAsync("Application");
            if (authContext != null) 
                return new AuthenticationTicket(authContext.Identity, authContext.Properties);

所以我现在可以从webapi控制器交换用户名/传递给cookie,我也可以直接使用Basic Sc​​heme进行不使用cookie的设置。

1 个答案:

答案 0 :(得分:2)

如果web api和javascript文件来自不同的来源,并且您必须向请求添加授权标头或Cookie标头,则无法阻止浏览器发送预检请求。否则,它将导致CSRF攻击任何受保护的Web api。

您可以使用OWIN Cors packageWeb API Cors package启用CORS方案,该方案可以为您处理预检请求。

OWIN cookie中间件负责设置auth cookie并验证它。这似乎是你想要的。

BTW,基本的auth挑战可能会导致浏览器弹出浏览器身份验证对话框,这在大多数Web应用程序中是不可取的。不确定这是不是你想要的。相反,使用表单发送用户名和密码并与cookie交换是常见的Web应用程序。

如果您的计算机上安装了VS 2013 RC或VWD 2013 RC,则可以创建启用了单独身份验证的MVC项目。该模板使用cookie中间件并形成后登录。虽然它是MVC控制器,但您只需将代码转换为Web API。

[更新] 关于预检请求,根据规范,即使使用cookie头也会发送。您可以考虑添加Max Age标头以使其在浏览器上缓存。 JSONP是另一种不需要预检的选项。

[Update2]为了通过owin中间件设置cookie,请使用以下示例代码。

var identity = new ClaimsIdentity(CookieAuthenticationDefaults.ApplicationAuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, "Test"));
AuthenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(identity, new AuthenticationProperties()
{
    IsPersistent = true
});