在 IdentityServer3 中处理 CORS 之前发送的飞行前 OPTIONS 请求

Posted

技术标签:

【中文标题】在 IdentityServer3 中处理 CORS 之前发送的飞行前 OPTIONS 请求【英文标题】:Handle pre-flight OPTIONS request send before CORS in IdentityServer3 【发布时间】:2018-04-13 13:11:55 【问题描述】:

我们目前正在与一个团队合作开发两种不同的 RestAPI。一个是我们的主系统,另一个只是一个空主机,其中注册了 IdentityServer。我们已经在主应用程序中成功配置了CORS 和飞行前OPTIONS 处理,但现在我们在IdentityServer 中处理OPTIONSCORS 已启用并正常工作)。不幸的是,我们在令牌请求中需要一些特定的标头,并且浏览器正在发送飞行前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 类的简化实现:

[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 的 &lt;handlers&gt; 部分设置一些 ISS 处理程序

    How to support HTTP OPTIONS verb in ASP.NET MVC/WebAPI application IIS hijacks CORS Preflight OPTIONS request 还有更多,都是关于这种方法的

    我不记得我在哪里找到了这个解决方案,但有人建议执行以下操作:

    在 IdentityServer 之前注册主机 API 创建一个控制器,将路由设置为我们想要劫持 OPTIONS 请求的地址(在我的情况下为 /identity/connect/token) 添加[HttpOptions] 标记操作,返回200, OK

    ...但不幸的是,正如我所料,我的新控制器劫持了所有到 IdentityServer 的流量,而不仅仅是 OPTIONS 请求。

更新 - 忘了提到第三个选项

    我想出了一个可行的解决方案(我的意思几乎是因为我还没有完成)。这很简单,但是我宁愿避免使用这种方法,因为它会增加额外的工作,并且稍后会增加要维护的代码。这个想法是简单地将IdentityServer's 令牌端点包装在我们自己的端点中,以便 AJAX 只调用我们的 RestAPI。而且,正如我之前所说,我们的主机已经为CORS 和飞行前OPTIONS 设置好了

【问题讨论】:

不相关的旁注:在您的OptionsHttpMessageHandler 中使用Task.FromResult(或使用异步标记方法)而不是Task.Factory.StartNewStartNew 在这里完全没有必要(并且浪费时间和资源,即使一点点)。 老实说,它只是从外部网站复制粘贴的,我们只是验证了它有效。不过谢谢你的建议,我会解决的。 【参考方案1】:

我已经设法克服了这个问题。 我希望这只是临时解决方案,因为它既不优雅也不安全(我认为它可能会为某种攻击开辟道路,但我是安全方面的初学者)。

我仍在等待更好的解决方案,我愿意更改已接受的答案。

该解决方案是由 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 的答案中复制了AppendHeaders,但这些在我的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>

【讨论】:

以上是关于在 IdentityServer3 中处理 CORS 之前发送的飞行前 OPTIONS 请求的主要内容,如果未能解决你的问题,请参考以下文章

跳过 IdentityServer3 登录屏幕

一步一步学习IdentityServer3 (13) 令牌

IdentityServer3 和通过 OpenIDConnect 进行外部登录

IdentityServer3 将注销重定向到自定义 URL

如何覆盖由 IdentityServer3.accesstokenvalidation 引起的服务耦合

IdentityServer3 绕过同意屏幕