9

设想:

我在 Windows Azure 上分别托管了两个 ASP.NET Web 应用程序,并且都与同一个 Azure Active Directory 租户相关联:

  1. 具有 AngularJS SPA 前端和adal.js库的 MVC 应用程序,用于在客户端处理 Azure AD 身份验证。

  2. 带有 Microsoft OWIN 中间件的 Web API,用于处理服务器上的 Azure AD 身份验证。

问题:

当 Angular 引导客户端应用程序时,页面在通过 oauth 重定向到正确的身份授权后正确加载,并且 adal.js 库为每个应用程序正确检索和存储不同的令牌(通过检查资源/会话存储选项卡Chrome 开发工具)。但是,当客户端应用程序尝试访问或更新 API 中的任何数据时,CORS 预检请求会响应 302 重定向到身份验证机构,这会导致控制台中出现以下错误:

XMLHttpRequest 无法加载https://webapi.azurewebsites.net/api/items。请求被重定向到' https://login.windows.net/ {authority-guid}/oauth2/authorize?response_type=id_token&redirect_uri=....etc..etc..',这对于跨域请求是不允许的需要预检。

示例标头(匿名):

要求
选项 /api/items HTTP/1.1
主机:webapi.azurewebsites.net
连接:保持活动
访问控制请求方法:GET
Access-Control-Request-Headers:接受、授权
来源:https://mvcapp.azurewebsites.net
用户代理:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36
接受: */*
推荐人:https://mvcapp.azurewebsites.net/

回复
找到 HTTP/1.1 302
内容长度:504
位置:https://login.windows.net/{authority-guid}/oauth2/authorize?response_type=id_token&redirect_uri=https%3A%2F%2F....etc..etc.%2F&client_id={api-guid} &scope=openid+profile+email&response_mode=form_post&state=...等...
服务器:Microsoft-IIS/8.0
X-Powered-By: ASP.NET
设置 Cookie:ARRAffinity=4f51...snip....redact....db6d;Path=/;Domain=webapi.azurewebsites.net

我做过/尝试过的事

  1. 确保 Azure AD 租户允许 OAuth2 隐式流,如此和其他地方所述。
  2. 确保 API公开访问权限,并且 MVC/SPA使用这些公开的权限注册访问。
  3. 在 API 的 web.config 中显式添加了一个 OPTIONS 动词处理程序(见下文)。
  4. 在 API 服务器上使用了各种启用 CORS 的组合,OWIN 本身以及 EnableCorsAttribute(见下文)。

问题

有没有办法让与 Azure AD 租户关联的 Web API 不会在 CORS 预检请求上重定向?我是否缺少 adal.js 库和/或 OWIN 启动代码中的一些初始化设置(见下文)?Azure 门户中是否有允许 OPTIONS 请求通过 OWIN 管道的设置?

相关代码:

adal.js 初始化

angular.module("myApp", ["ngRoute", "AdalAngular"])

.config(["$routeProvider", "$locationProvider", "$httpProvider", "adalAuthenticationServiceProvider",
    function ($routeProvider, $locationProvider, $httpProvider, adalProvider) {

        $routeProvider.when("/", { // other routes omitted for brevity
            templateUrl: "/content/views/home.html",
            requireADLogin: true // restrict to validated users in the Azure AD tenant
        });

        // CORS support (I've tried with and without this line)
        $httpProvider.defaults.withCredentials = true;

        adalProvider.init({
            tenant: "contoso.onmicrosoft.com",
            clientId: "11111111-aaaa-2222-bbbb-3333cccc4444", // Azure id of the web app
            endpoints: {
                // URL and Azure id of the web api
                "https://webapi.azurewebsites.net/": "99999999-zzzz-8888-yyyy-7777xxxx6666"
            }
        }, $httpProvider);
    }
]);

OWIN 中间件初始化

public void ConfigureAuth(IAppBuilder app)
{
    // I've tried with and without the below line and also by passing
    // in a more restrictive and explicit custom CorsOptions object
    app.UseCors(CorsOptions.AllowAll);

    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
        new WindowsAzureActiveDirectoryBearerAuthenticationOptions
        {
            TokenValidationParameters = new TokenValidationParameters
            {
                // Azure id of the Web API, also tried the client app id
                ValidAudience = "99999999-zzzz-8888-yyyy-7777xxxx6666"
            },
            Tenant = "contoso.onmicrosoft.com"
        }
    );

    // I've tried with and without this
    app.UseWebApi(GlobalConfiguration.Configuration);
}

WebApiConfig 初始化

public static void Register(HttpConfiguration config)
{
    // I've tried with and without this and also using both this
    // and the OWIN CORS setup above. Decorating the ApiControllers
    // or specific Action methods with a similar EnableCors attribute
    // also doesn't work.
    var cors = new EnableCorsAttribute("https://mvcapp.azurewebsites.net", "*", "*")
    {
        cors.SupportsCredentials = true // tried with and without
    };
    config.EnableCors(cors);

    // Route registration and other initialization code removed
}

API OPTIONS 动词处理程序注册

<system.webServer>
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="OPTIONSVerbHandler" />
    <remove name="TRACEVerbHandler" />
    <add name="OPTIONSHandler" path="*" verb="OPTIONS" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

相关资源

有时,我尝试了以下(以及更多)论坛和博客文章以及 github 示例代码中几乎所有可以想象的组合。

  1. ADAL JavaScript 和 AngularJS – 深入了解
  2. 使用 Azure Active Directory、Owin 中间件和 ADAL 保护 ASP.NET Web API 2
  3. 使用 ASP.NET Web API 2、Owin 和 Identity 的基于令牌的身份验证
  4. AzureADSamples/SinglePageApp-DotNet (github)
  5. AngularJSCORS (github)
  6. 如何在 WebAPI 2 中进行 CORS 身份验证?
  7. WebApi 上的 AngularJS 和 OWIN 身份验证
4

2 回答 2

4

我有类似的问题来找出合适的包。只需 Owin cors 即可设置。请先检查 owin.cors 的软件包。

<package id="Microsoft.Owin" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin.Cors" version="2.1.0" targetFramework="net45" />

处理程序的 WebConfig 选项:

<system.webServer>
<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

您在 owin 配置中使用指定的 cors 选项做得很好。

 public void ConfigureAuth(IAppBuilder app)
    {
        app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Audience = ConfigurationManager.AppSettings["ida:Audience"],
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"]

            });
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    }

控制器不需要 CORS 相关的属性。

   [Authorize]
public class ContactsController : ApiController
{

    // GET api/<controller>
    public IEnumerable<string> Get()
    {
        return new string[] { "person1", "person2" };
    }

    // GET api/<controller>/5
    public string Get(int id)
    {
        return "person" + id;
    }

WebAPIConfig 不需要 CORS 相关条目。

工作示例在这里:https ://github.com/omercs/corsapisample

您可以使用以下代码在您的应用中进行测试:

app.factory('contactService', ['$http', function ($http) {
var serviceFactory = {};

var _getItems = function () {
    $http.defaults.useXDomain = true;
    delete $http.defaults.headers.common['X-Requested-With'];
    return $http.get('http://yourhostedpage/api/contacts');
};

serviceFactory.getItems = _getItems;

return serviceFactory;

}]);

预检响应示例:

Remote Address:127.0.0.1:8888
Request URL:http://localhost:39725/api/contacts
Request Method:OPTIONS
Status Code:200 OK
Request Headersview source
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, authorization
Access-Control-Request-Method:GET
Host:localhost:39725
Origin:http://localhost:49726
Proxy-Connection:keep-alive
Referer:http://localhost:49726/myspa.html
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36
Response Headersview source
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:authorization
Access-Control-Allow-Origin:http://localhost:49726
Content-Length:0
Date:Fri, 23 Jan 2015 01:10:54 GMT
Server:Microsoft-IIS/8.0
X-Powered-By:ASP.NET
于 2015-01-22T23:01:02.460 回答
3

解决了(有点)

这似乎是由部署问题引起的,特别是我最初将应用程序发布到 Azure 的方式。这两个应用程序最初都是使用 Windows 身份验证编写的,并部署到标准(即非 Azure)Web 服务器。使用这些技术,应用程序按预期工作。

根据新的业务需求,我一直致力于将它们迁移到 Azure。我遵循的过程是:

  1. 按照最初编写的方式,从 Visual Studio 2013 发布向导直接将这两个应用程序部署/发布到 Azure。当然,正如预期的那样,这失败了,因为他们无法从 Azure 中与本地 Active Directory 进行通信。
  2. 按照问题末尾所有链接的详细信息,逐步更新代码以删除 Windows 身份验证并替换为 Azure AD 身份验证。
  3. 使用 Azure AD 门户手动将应用与 Azure AD 租户关联以进行身份​​验证。

在某个时候,我注意到 VS 发布向导的设置选项卡上的启用组织身份验证选项,并开始使用它将应用程序与租户相关联。因为我已经手动关联了它们,所以 Visual Studio 最终为每个创建了另一个 Azure AD 应用程序,结果是两个。

最后,这就是这样做的:

  1. 使用门户删除了两个应用程序的所有 Azure 记录,包括 Azure 网站本身以及 Azure AD 站点/关联。
  2. 将两个应用程序中的所有代码更改恢复到其原始状态。
  3. 在应用程序中重新实现了 Azure AD 身份验证(OWIN、adal.js 等)。
  4. 是否重新发布了两个应用程序,让 VS 向导处理所有 Azure 关联。
  5. 使用新创建的客户端 ID 更新了 Web.config 文件和 adal.js 初始化。
  6. 再次发布。

现在 OPTIONS 预检请求不再是 302 重定向。

相反,它们现在是 405 Method Not Allowed,与此线程非常相似。进步,某种意义上。

即使它仍然不能端到端工作,我会留下这个答案(而不是删除问题),以防它可能帮助其他人经历 Azure 302 重定向 CORS 预检。

于 2015-01-27T20:26:01.340 回答