CORS 预检请求在 Azure 托管 Web 中以 302 重定向响应 API
CORS preflight request responds with 302 redirect in Azure hosted Web API
场景:
我有两个 ASP.NET Web 应用程序分别托管在 Windows Azure 上,并且都关联到同一个 Azure Active Directory 租户:
具有 AngularJS SPA 前端和 adal.js 库的 MVC 应用程序,用于在客户端处理 Azure AD 身份验证。
Web API 带有 Microsoft OWIN 中间件,用于处理服务器上的 Azure AD 身份验证。
问题:
当 angular 引导客户端应用程序时,页面在通过 oauth 重定向到正确的身份授权后正确加载,并且 adal.js 库为每个应用程序正确检索和存储不同的令牌 (通过检查 Chrome 开发工具中的 Resources/Session-Storage 选项卡进行验证)。 但是当客户端应用程序尝试访问或更新 API 中的任何数据时, CORS 预检请求以 302 重定向响应身份授权,这导致控制台中出现以下错误:
XMLHttpRequest cannot load https://webapi.azurewebsites.net/api/items.
The request was redirected to
'https://login.windows.net/{authority-guid}/oauth2/authorize?response_type=id_token&redirect_uri=....etc..etc..',
which is disallowed for cross-origin requests that require preflight.
示例headers(匿名):
Request
OPTIONS /api/items HTTP/1.1
Host: webapi.azurewebsites.net
Connection: keep-alive
Access-Control-Request-Method: GET
Access-Control-Request-Headers: accept, authorization
Origin: https://mvcapp.azurewebsites.net
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36
Accept: */*
Referer: https://mvcapp.azurewebsites.net/
Response
HTTP/1.1 302 Found
Content-Length: 504
Location: 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=...etc...
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=4f51...snip....redact....db6d;Path=/;Domain=webapi.azurewebsites.net
我有什么done/tried
- 确保 Azure AD 租户允许 OAuth2 隐式流程,如 here 和其他地方所述。
- 确保 API exposes access permissions and that the MVC/SPA registers for access 使用那些暴露的权限。
- 在 API 的 web.config 中明确添加了一个 OPTIONS 动词处理程序(见下文)。
- 在 API 服务器上使用了启用 CORS 的各种组合,OWIN 本身以及 EnableCorsAttribute(见下文)。
问题
有什么方法可以让与 Azure AD 租户关联的 Web API 不在 CORS 预检请求上重定向?
我是否缺少 adal.js 库 and/or 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 选项动词处理程序注册
<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 示例代码中几乎所有可以想象的组合。
- ADAL JavaScript and AngularJS – Deep Dive
- Secure ASP.NET Web API 2 using Azure Active Directory, Owin Middleware, and ADAL
- Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
- AzureADSamples/SinglePageApp-DotNet (github)
- AngularJSCORS (github)
- How to make CORS Authentication in WebAPI 2?
- AngularJS and OWIN Authentication on WebApi
我有类似的问题来找出合适的包。只有 Owin cors 足以 setup.Please 首先检查 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 配置中使用 specfiying 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
已解决(有点)
这似乎是由部署问题引起的,特别是我最初将应用程序发布到 Azure 的方式。这两个应用程序最初都是使用 Windows 身份验证编写的,并部署到标准(即非 Azure)Web 服务器。使用这些技术,应用程序按预期运行。
根据新的业务需求,我一直在努力将它们迁移到 Azure。我遵循的过程是:
- Deploy/Publish 这两个应用程序,如最初编写的那样,来自 Visual Studio 2013 直接发布向导到 Azure。当然,正如预期的那样,这失败了,因为他们无法从 Azure 内部与本地 Active Directory 通信。
- 逐步更新代码以删除 Windows 身份验证并按照问题末尾所有链接中的详细信息替换为 Azure AD 身份验证。
- 手动将应用程序与 Azure AD 租户相关联,以便使用 Azure AD 门户进行身份验证。
在某个时候,我注意到 VS 发布向导的“设置”选项卡上的“启用组织身份验证”选项,并开始使用它来将应用程序与租户相关联。因为我已经手动关联了它们,所以 Visual Studio 最终为每个创建了另一个 Azure AD 应用程序,结果是两个。
最后是这样的:
- 删除了使用门户的两个应用程序的所有 Azure 记录,包括 Azure 网站本身和 Azure AD sites/associations。
- 将两个应用程序中的所有代码更改还原为原始状态。
- 在应用程序中重新实现了 Azure AD 身份验证(OWIN、adal.js 等)。
- 是否重新发布了两个应用程序,让 VS 向导处理所有 Azure 关联。
- 使用新创建的客户端 ID 更新了 Web.config 文件和 adal.js 初始化。
- 再次发布。
现在 OPTIONS 预检请求不再是 302 重定向。
相反,它们现在是 405 Method Not Allowed,与 非常相似。某种程度上的进步。
尽管它仍然不能端到端地工作,但我会留下这个答案(而不是删除问题),以防它可能帮助其他人体验 Azure 302 重定向的 CORS 预检。
场景:
我有两个 ASP.NET Web 应用程序分别托管在 Windows Azure 上,并且都关联到同一个 Azure Active Directory 租户:
具有 AngularJS SPA 前端和 adal.js 库的 MVC 应用程序,用于在客户端处理 Azure AD 身份验证。
Web API 带有 Microsoft OWIN 中间件,用于处理服务器上的 Azure AD 身份验证。
问题:
当 angular 引导客户端应用程序时,页面在通过 oauth 重定向到正确的身份授权后正确加载,并且 adal.js 库为每个应用程序正确检索和存储不同的令牌 (通过检查 Chrome 开发工具中的 Resources/Session-Storage 选项卡进行验证)。 但是当客户端应用程序尝试访问或更新 API 中的任何数据时, CORS 预检请求以 302 重定向响应身份授权,这导致控制台中出现以下错误:
XMLHttpRequest cannot load https://webapi.azurewebsites.net/api/items. The request was redirected to 'https://login.windows.net/{authority-guid}/oauth2/authorize?response_type=id_token&redirect_uri=....etc..etc..', which is disallowed for cross-origin requests that require preflight.
示例headers(匿名):
Request OPTIONS /api/items HTTP/1.1 Host: webapi.azurewebsites.net Connection: keep-alive Access-Control-Request-Method: GET Access-Control-Request-Headers: accept, authorization Origin: https://mvcapp.azurewebsites.net User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36 Accept: */* Referer: https://mvcapp.azurewebsites.net/ Response HTTP/1.1 302 Found Content-Length: 504 Location: 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=...etc... Server: Microsoft-IIS/8.0 X-Powered-By: ASP.NET Set-Cookie: ARRAffinity=4f51...snip....redact....db6d;Path=/;Domain=webapi.azurewebsites.net
我有什么done/tried
- 确保 Azure AD 租户允许 OAuth2 隐式流程,如 here 和其他地方所述。
- 确保 API exposes access permissions and that the MVC/SPA registers for access 使用那些暴露的权限。
- 在 API 的 web.config 中明确添加了一个 OPTIONS 动词处理程序(见下文)。
- 在 API 服务器上使用了启用 CORS 的各种组合,OWIN 本身以及 EnableCorsAttribute(见下文)。
问题
有什么方法可以让与 Azure AD 租户关联的 Web API 不在 CORS 预检请求上重定向? 我是否缺少 adal.js 库 and/or 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 选项动词处理程序注册
<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 示例代码中几乎所有可以想象的组合。
- ADAL JavaScript and AngularJS – Deep Dive
- Secure ASP.NET Web API 2 using Azure Active Directory, Owin Middleware, and ADAL
- Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
- AzureADSamples/SinglePageApp-DotNet (github)
- AngularJSCORS (github)
- How to make CORS Authentication in WebAPI 2?
- AngularJS and OWIN Authentication on WebApi
我有类似的问题来找出合适的包。只有 Owin cors 足以 setup.Please 首先检查 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 配置中使用 specfiying 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
已解决(有点)
这似乎是由部署问题引起的,特别是我最初将应用程序发布到 Azure 的方式。这两个应用程序最初都是使用 Windows 身份验证编写的,并部署到标准(即非 Azure)Web 服务器。使用这些技术,应用程序按预期运行。
根据新的业务需求,我一直在努力将它们迁移到 Azure。我遵循的过程是:
- Deploy/Publish 这两个应用程序,如最初编写的那样,来自 Visual Studio 2013 直接发布向导到 Azure。当然,正如预期的那样,这失败了,因为他们无法从 Azure 内部与本地 Active Directory 通信。
- 逐步更新代码以删除 Windows 身份验证并按照问题末尾所有链接中的详细信息替换为 Azure AD 身份验证。
- 手动将应用程序与 Azure AD 租户相关联,以便使用 Azure AD 门户进行身份验证。
在某个时候,我注意到 VS 发布向导的“设置”选项卡上的“启用组织身份验证”选项,并开始使用它来将应用程序与租户相关联。因为我已经手动关联了它们,所以 Visual Studio 最终为每个创建了另一个 Azure AD 应用程序,结果是两个。
最后是这样的:
- 删除了使用门户的两个应用程序的所有 Azure 记录,包括 Azure 网站本身和 Azure AD sites/associations。
- 将两个应用程序中的所有代码更改还原为原始状态。
- 在应用程序中重新实现了 Azure AD 身份验证(OWIN、adal.js 等)。
- 是否重新发布了两个应用程序,让 VS 向导处理所有 Azure 关联。
- 使用新创建的客户端 ID 更新了 Web.config 文件和 adal.js 初始化。
- 再次发布。
现在 OPTIONS 预检请求不再是 302 重定向。
相反,它们现在是 405 Method Not Allowed,与
尽管它仍然不能端到端地工作,但我会留下这个答案(而不是删除问题),以防它可能帮助其他人体验 Azure 302 重定向的 CORS 预检。