MVC到同一应用程序的Web API身份验证

时间:2013-05-04 17:22:04

标签: asp.net-mvc asp.net-mvc-4 asp.net-web-api forms-authentication

我正在构建一个具有MVC端的网站,并将从我们自己的WebAPI后端获取其数据,但最有可能托管在不同的服务器上(甚至在Azure上)。我们将使用Forms身份验证。

由于我们希望用户只需要登录一次(到MVC网站),使用他们在MVC表单身份验证登录中输入的相同信息透明地向WebAPI后端验证用户的建议方法是什么?

由于此身份验证基于Cookie,因此最好的方法是在MVC应用程序的Login操作上调用WebApi身份验证操作/方法,获取webapi的auth cookie并在每次调用WebAPI后端时使用它?

非常感谢任何指导

1 个答案:

答案 0 :(得分:4)

GR7,我不能说我曾尝试过你在做什么。

让我指出一些困扰我的想法,以及我认为你可以让它发挥作用。

您在一台Web服务器上运行ASP.NET MVC应用程序,在另一台服务器上运行ASP.NET WebAPI应用程序。您想要使用另一个cookie。 MVC应用程序中的cookie如何对WebAPI应用程序有效?即使两个系统上用户的用户名和密码相同,两个不同应用程序生成的cookie也不会相同吗?为了清楚起见,我不是百分之百确定它,这只是一种怀疑。

这是我怀疑的基础 - 假设您在Azure云上运行ASP.NET MVC应用程序,并且它负载均衡(意味着您实际上有多个实例,每个实例在不同的物理机器上运行)。用户连接到您的网站,并在该实例上进行身份验证。然后他导航到该网站上的另一个页面,负载均衡器最终将他发送到另一个实例上的该页面。我认为在这种情况下他将被要求重新认证,因为他的cookie无效,即使它是完全相同的MVC应用程序。这种情况的解决方案是在所有机器上设置相同的机器密钥。

这在MSDN上讨论: http://msdn.microsoft.com/en-us/library/eb0zx8fc(v=vs.100).aspx

一篇微软知识库文章: http://support.microsoft.com/kb/910443

还有一些StackOverflow文章在讨论这个问题: Does Forms Authentication work with Web Load Balancers?.NET Forms Authentication in Azure - What changes are required for multiple VMs?

所以我猜你应该能够在两台Web服务器上将机器密钥设置为相同,然后将cookie从MVC应用程序传递到WebAPI应用程序,以便不让用户进行两次身份验证。如果我错了,希望有人会纠正我。

另一种解决方案是坚持使用两个cookie,一个用于MVC应用程序,另一个用于Web API。您将需要找出存储webapi cookie的位置 - 因为ASP.NET以无状态方式工作,每次用户单击不同的MVC页面时,这基本上是一个全新的事务。所以也许你希望用户浏览器存储两个cookie。第一次进行身份验证时,您使用相同的用户名和密码在MVC应用程序以及WebAPI应用程序上对他进行身份验证,然后将两个cookie发送回给他(当然,MVC cookie将自动返回给他)。因此,每当他导航到不同的页面时,您都会将两个cookie发送到您的MVC应用程序,并且必须使用其中一个并使用它调用WebAPI应用程序。您可能需要确保两个cookie不具有相同的名称(默认情况下,两者都是ASPXAUTH)。您可以使用

在web.config中更改MVC cookie的名称
<authentication mode="Forms">
  <forms name="MyAuthCookie" loginUrl="LoginPage.aspx" />
</authentication>

这应该允许您在用户的浏览器上存储2个cookie,并帮助您区分它们。我假设您的MVC和WebAPI都在同一个域中,否则浏览器将不接受WebAPI cookie(或者至少在后续请求中不会将其传递回给您)。

如果这个答案有帮助,请投票,我几乎没有代表:)

======================================

编辑 - 在回复下面的问题时添加此内容 - 您想知道如何实际获取WebAPI为您的MVC应用程序提供的cookie,并将其返回给用户的浏览器。我将首先从您的mvc应用程序发送带有您的凭据的http发送请求到您的webapi,这样您就可以清楚地了解所有内容。

让我们使用JSON将登录信息作为HTTP请求的一部分从MVC应用程序发送到Web API服务器。在MVC应用程序的Models文件夹中创建一个模型,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SitterWebsite.Models
{
    public class MyJsonLoginModel
    {
        public string UserName;
        public string Password;
        public bool RememberMe;
    }
}

然后在ActionController.cs的Login()方法中,你可以添加这样的东西来发出请求

string loginapibaseaddress = "http://mywebapiurl.com/";
string loginapiaddress = "api/AccountAPI/SignMeIn";

MyJsonLoginModel mydatamodel = new MyJsonLoginModel()
{
        UserName = "gary",
        Password = "password",
        RememberMe = false,
};

// Create the JSON formatter.
MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();

// Use the JSON formatter to create the content of the request body.
HttpContent content = new ObjectContent<MyJsonLoginModel>(mydatamodel, jsonFormatter);

// I am going to return the cookie received from the Web API controller to the browser
// I will obtain it from the HTTP POST request to the WebAPI in the form of a Cookie object
// I will then need to convert it to an HttpCookie object
Cookie cookietosendback = new Cookie();   // cookie of type System.Net.Cookie
HttpCookie httpcookietosendback = new HttpCookie("will_name_later");   // cookie of type System.Web.HttpCookie


// Create a new cookie container. 
// We will attach this cookie container to the HTTP request
// The Web API auth cookie will automatically be put into this container
CookieContainer cookie_container = new CookieContainer();

HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookie_container;
HttpClient loginclient = new HttpClient(handler);

// Set the base address of the client
loginclient.BaseAddress = new Uri(loginapibaseaddress);

// Set the web api address of the client
Uri loginapiaddressuri = new Uri(loginapibaseaddress+loginapiaddress);

// Send an HTTP POST request
HttpResponseMessage response = loginclient.PostAsync(loginapiaddressuri, content).Result;


Cookie mycookie;

if (response.IsSuccessStatusCode) 
{

    // Now let's access the cookies from the cookie container since it will be automatically populated with any
    // cookies returned by our http request (ie. any cookies in the http response). 

    IEnumerable<Cookie> responseCookies = cookie_container.GetCookies(loginapiaddressuri).Cast<Cookie>();

    foreach (Cookie cookie in responseCookies)
    {
    if cookie.Name.Equals('.ASPXAUTH')
        cookietosendback = cookie
    }

    // We want to return the cookie to the users browser
    // However the HttpContext.Response.Cookies.Add() method needs an HttpCookie object, not a Cookie object
    // So we need to convert the Cookie to an HttpCookie
    httpcookietosendback.Name = "GaryCookie"; // changing name since both MVC and WebAPI name their cookie .ASPXAUTH
    httpcookietosendback.Value = cookietosendback.Value;
    httpcookietosendback.Path = cookietosendback.Path;
    httpcookietosendback.Expires = cookietosendback.Expires;
    httpcookietosendback.Domain = cookietosendback.Domain;  
    // Note - if the domain of your WebAPI is different from the MVC app, you might want to change it in
    // above statement, otherwise the browser will either not accept a cookie from another domain, or it will
    // definitely not pass it to the mvc app in your next request

    this.ControllerContext.HttpContext.Response.Cookies.Add(httpcookietosendback);

}
else
{
    // Http post to webapi failed
}

所以现在当你进入登录页面并输入你的凭证并点击提交时,你不仅会得到你通常得到的MVC cookie,还会得到WebAPI cookie。根据我上面的代码,它将被命名为“GaryCookie”。每当您转到网站上的另一个页面时,您的浏览器都会请求该页面并将两个cookie发送到您的mvc应用程序。如果您希望调用其他WebAPI方法,您现在需要执行与我刚刚完成的操作相反的操作,即使用修改后的WebAPI cookie“GaryCoookie”并将其重命名为原来的内容。然后在WebAPI方法上发出GET或POST请求时,使用标头发送它。

如果您的webapi和mvc应用程序不在同一个域中,您还应该将cookie的域设置为与MVC的域匹配。否则,如果您请求其他页面,您的浏览器将不会将cookie发送到MVC应用程序。

顺便说一句,我刚才测试了所有这些,所以它可以工作。