在 IdentityServer3 中的 CORS 之前处理 pre-flight OPTIONS 请求发送
Handle pre-flight OPTIONS request send before CORS in IdentityServer3
我们目前正在与一个团队合作开发两个不同的 RestAPI。一个是我们的主系统,另一个只是一个注册了 IdentityServer 的空主机。我们已经在主应用程序中成功配置了 CORS
和 pre-flight OPTIONS
处理,但现在我们正在 IdentityServer 中与 OPTIONS
作斗争(CORS
已启用并正在运行) .不幸的是,我们在令牌请求中需要一些特定的 headers 并且浏览器正在发送 pre-flight OPTIONS
请求。每次我们这样做,我们都会得到:
The requested resource does not support HTTP method 'OPTIONS'.
在我们的 RestAPI 应用程序管道中,我们只需创建 DelegatingHandler
:
public class OptionsHttpMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Options)
{
var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
var controllerRequested = request.GetRouteData().Values["controller"] as string;
var supportedMethods = apiExplorer.ApiDescriptions
.Where(d =>
{
var controller = d.ActionDescriptor.ControllerDescriptor.ControllerName;
return string.Equals(
controller, controllerRequested, StringComparison.OrdinalIgnoreCase);
})
.Select(d => d.HttpMethod.Method)
.Distinct();
if (!supportedMethods.Any())
{
return Task.Factory.StartNew(
() => request.CreateResponse(HttpStatusCode.NotFound));
}
return Task.Factory.StartNew(() =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
return response;
});
}
return base.SendAsync(request, cancellationToken);
}
}
并在Global.asax
中注册:
GlobalConfiguration.Configuration.MessageHandlers.Add(new OptionsHttpMessageHandler());
我不知道如何在 IdentityServer 管道中注册它。这是 Owin Startup class:
的简化实现
[assembly: OwinStartup(typeof(MyNamespace.IdentityServer.Host.Startup))]
namespace MyNamespace.IdentityServer.Host
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var identityServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryClients(Clients.Get());
identityServerServiceFactory.UserService = new Registration<IUserService>(resolver => UserServiceFactory.Create());
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "MyNamespace.IdentityServer",
SigningCertificate = this.LoadCertificate(),
Factory = identityServerServiceFactory
});
});
}
}
}
目前已尝试:
在 web.config
的 <handlers>
部分设置一些 ISS 处理程序
- IIS hijacks CORS Preflight OPTIONS request
- 还有很多,都是关于这种方法的
我不记得我在哪里找到这个解决方案,但有人建议执行以下操作:
- 在 IdentityServer
之前注册主机 API
- 创建一个控制器,路由设置为我们想要劫持 OPTIONS 请求的地址(在我的例子中
/identity/connect/token
)
- 添加
[HttpOptions]
标记的操作,其中 returns 200, OK
…但不幸的是,正如我所料,我的新控制器劫持了所有到 IdentityServer 的流量,而不仅仅是 OPTIONS
请求。
更新 - 忘记提及第三个选项
- 我想出了一个有效的解决方案(我的意思是几乎是因为我还没有完成)。这很简单,但是 我宁愿避免这种方法,因为它增加了额外的工作,并且稍后会增加代码维护。这个想法是简单地将
IdentityServer's
令牌端点包装在我们自己的端点中,以便 AJAX 只调用我们的 RestAPI。而且,正如我之前所说,我们的主机已经设置为 CORS
和 pre-flight OPTIONS
我已经设法克服了这个问题。 我希望这只是临时解决方案,因为它既不优雅也不安全(我认为它可能为某种攻击开辟了道路,但我是安全方面的初学者)。
我还在等待更好的解决方案,我愿意更改已接受的答案。
解决方案是由Chtiwi Malek
在另一个SO question中提出的,我也在我的问题中提供了(我的错,我没有检查所有答案)。
我们所要做的就是在每个请求的开头处理OPTIONS
protected void Application_BeginRequest()
{
var res = HttpContext.Current.Response;
var req = HttpContext.Current.Request;
res.AppendHeader("Access-Control-Allow-Origin", "*");
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
if (req.HttpMethod == "OPTIONS")
{
res.StatusCode = 200;
res.End();
}
}
(我从 Chtiwi Malek's
的回答中复制了 AppendHeader
s,但这些是在我的 web.config
中处理的)
我们还必须在 web.config
中为我们的应用程序启用 OPTIONS
请求
<handlers>
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
我们目前正在与一个团队合作开发两个不同的 RestAPI。一个是我们的主系统,另一个只是一个注册了 IdentityServer 的空主机。我们已经在主应用程序中成功配置了 CORS
和 pre-flight OPTIONS
处理,但现在我们正在 IdentityServer 中与 OPTIONS
作斗争(CORS
已启用并正在运行) .不幸的是,我们在令牌请求中需要一些特定的 headers 并且浏览器正在发送 pre-flight OPTIONS
请求。每次我们这样做,我们都会得到:
The requested resource does not support HTTP method 'OPTIONS'.
在我们的 RestAPI 应用程序管道中,我们只需创建 DelegatingHandler
:
public class OptionsHttpMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Options)
{
var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
var controllerRequested = request.GetRouteData().Values["controller"] as string;
var supportedMethods = apiExplorer.ApiDescriptions
.Where(d =>
{
var controller = d.ActionDescriptor.ControllerDescriptor.ControllerName;
return string.Equals(
controller, controllerRequested, StringComparison.OrdinalIgnoreCase);
})
.Select(d => d.HttpMethod.Method)
.Distinct();
if (!supportedMethods.Any())
{
return Task.Factory.StartNew(
() => request.CreateResponse(HttpStatusCode.NotFound));
}
return Task.Factory.StartNew(() =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
return response;
});
}
return base.SendAsync(request, cancellationToken);
}
}
并在Global.asax
中注册:
GlobalConfiguration.Configuration.MessageHandlers.Add(new OptionsHttpMessageHandler());
我不知道如何在 IdentityServer 管道中注册它。这是 Owin Startup class:
的简化实现[assembly: OwinStartup(typeof(MyNamespace.IdentityServer.Host.Startup))]
namespace MyNamespace.IdentityServer.Host
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var identityServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryClients(Clients.Get());
identityServerServiceFactory.UserService = new Registration<IUserService>(resolver => UserServiceFactory.Create());
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "MyNamespace.IdentityServer",
SigningCertificate = this.LoadCertificate(),
Factory = identityServerServiceFactory
});
});
}
}
}
目前已尝试:
在 web.config
的<handlers>
部分设置一些 ISS 处理程序- IIS hijacks CORS Preflight OPTIONS request
- 还有很多,都是关于这种方法的
我不记得我在哪里找到这个解决方案,但有人建议执行以下操作:
- 在 IdentityServer 之前注册主机 API
- 创建一个控制器,路由设置为我们想要劫持 OPTIONS 请求的地址(在我的例子中
/identity/connect/token
) - 添加
[HttpOptions]
标记的操作,其中 returns200, OK
…但不幸的是,正如我所料,我的新控制器劫持了所有到 IdentityServer 的流量,而不仅仅是
OPTIONS
请求。
更新 - 忘记提及第三个选项
- 我想出了一个有效的解决方案(我的意思是几乎是因为我还没有完成)。这很简单,但是 我宁愿避免这种方法,因为它增加了额外的工作,并且稍后会增加代码维护。这个想法是简单地将
IdentityServer's
令牌端点包装在我们自己的端点中,以便 AJAX 只调用我们的 RestAPI。而且,正如我之前所说,我们的主机已经设置为CORS
和 pre-flightOPTIONS
我已经设法克服了这个问题。 我希望这只是临时解决方案,因为它既不优雅也不安全(我认为它可能为某种攻击开辟了道路,但我是安全方面的初学者)。
我还在等待更好的解决方案,我愿意更改已接受的答案。
解决方案是由Chtiwi Malek
在另一个SO question中提出的,我也在我的问题中提供了(我的错,我没有检查所有答案)。
我们所要做的就是在每个请求的开头处理OPTIONS
protected void Application_BeginRequest()
{
var res = HttpContext.Current.Response;
var req = HttpContext.Current.Request;
res.AppendHeader("Access-Control-Allow-Origin", "*");
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
if (req.HttpMethod == "OPTIONS")
{
res.StatusCode = 200;
res.End();
}
}
(我从 Chtiwi Malek's
的回答中复制了 AppendHeader
s,但这些是在我的 web.config
中处理的)
我们还必须在 web.config
OPTIONS
请求
<handlers>
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>