【问题标题】:Making Integrated security and CORS work with Asp.Net, Angular 6 and IIS使集成的安全性和 CORS 与 Asp.Net、Angular 6 和 IIS 一起工作
【发布时间】:2019-03-30 10:58:48
【问题描述】:

我有一个带有 Angular 前端和 Asp.Net Web 服务的应用程序。我正在使用 Angular 中的 HttpClient 实体进行 Web 服务调用。我已经配置了 CORS,并且我的 GET 使用以下基本结构在整个应用程序中完美运行

let url = environment.base_url + 'api/SubDoc/GetDocument?docID=' + docID;

this.httpClient.get(url,
  {
    withCredentials: true
  })
  .subscribe(
    response => {
      console.log(response);
      newData = response;
      this.currentDocument = newData;
    }
  );

我已经证明 CORS 可以正常工作,因为只要我在调用 EnableCors 时正确编辑 Origin 值,我就可以从我的本地主机会话或运行在我们的 QA 和生产服务器上的应用程序会话访问相同的服务端点

        config.EnableCors(new EnableCorsAttribute(origins: "http://localhost:2453,http://localhost:2452,http://***,http://10.100.101.46", headers: headers, methods: methods) { SupportsCredentials = true });

标头和方法定义为以下字符串:

        headers = "Origin, Content-Type, X-Auth-Token, content-type,application/x-www-form-urlencoded";
        methods = "GET,PUT,POST,OPTIONS,DELETE";

但是,我无法使用以下 HttpClient 结构向同一服务发出 PUT 请求

let url = this.base_url + 'api/ContactGroup/Put';
// Try the new HttpClient object
this.httpClient.put(url, body,
  {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
    withCredentials: true
  })
  .subscribe(
    response=>{
      let temp:number;
      temp = parseInt(response.toString(),10);
      if(!isNaN(temp)){
        this.groupID = temp;          
      }
    }
  );

当我从同一服务器连接时(即没有适用的 CORS),此 PUT 请求运行完美,但当我尝试从任何其他服务器连接时失败并出现预检错误。

未能加载 http://***/api/ContactGroup/Put:对预检请求的响应未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”标头。因此,Origin 'http://localhost:2452' 不允许访问。

如上所述,我确实定义了 Access-Control-Allow-Origin 标头,并且我相信我已将 PUT 和 OPTIONS 作为支持的方法包含在内。

我认为问题与未发送凭据的 PUT 请求有关。 GET 请求在请求标头中有一个凭证项,但 PUT 没有,即使我使用的是 withCredentials 选项。

PUT 请求的标头如下所示:

Request URL: http://reitgc01/REITServicesQA/api/ContactGroup/Put
Request Method: OPTIONS
Status Code: 401 Unauthorized
Remote Address: 10.100.101.46:80
Referrer Policy: no-referrer-when-downgrade
Content-Type: text/html
Provisional headers are shown
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: PUT
DNT: 1
Origin: http://localhost:2452
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36

我现在已经在这个问题上花费了数天时间,如果能提供任何帮助,我们将不胜感激。

【问题讨论】:

  • 您需要为http://reitgc01/REITServicesQA/api/ContactGroup/Put 端点配置 ASP.Net 服务器后端,以使 OPTIONS 请求不需要身份验证凭据。显示的请求标头表明浏览器在尝试来自前端代码的 PUT 请求之前正在发送 CORS 预检 OPTIONS 请求。并且预检失败并返回 401 响应,因为服务器需要身份验证,但浏览器在该 OPTIONS 请求中不包含身份验证凭据 - 因为 CORS 规范要求浏览器在没有凭据的情况下发出请求。
  • 感谢您的反馈,这是解决方案的一个很好的开始。我确实有澄清的问题。我们正在使用 IIS,并且我们禁用了匿名身份验证,因为这是一个使用集成身份验证的 Intranet 站点。我已经阅读了一些关于 Web.config 中自定义配置的帖子,以便仅在 OPTIONS 上启用匿名或在 begin_request 方法中编写自定义处理程序。您是否有使用这两种方法的经验,并且您是否曾经为集成安全站点做过这项工作?

标签: asp.net cors


【解决方案1】:

这就是我解决这个确切示例的方法。我正在使用启用了 WebApi 和 Windows 身份验证的 Angular 5。 CORS的东西详细here

TL;DR;本文的重点是您需要匿名(因为 CORS 标头不携带身份验证信息)和 Windows 身份验证(因此您可以保护您的应用程序)。

所以在我的 webapi 项目中,我有一个 CorsConfig 类来设置我的 CORS

public static class CorsConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute(ConfigurationManager.AppSettings["CorsClientUrl"], "*", "*")
        {
            SupportsCredentials = true
        };

        config.EnableCors(cors);
    }
}

用你的 HttpConfiguration 注册它

CorsConfig.Register(config);

在您的网络配置中

<authorization>
  <allow verbs="OPTIONS" users="*" />
  <allow users="<your user list>" />
  <deny users="*" />
</authorization>

这将使您的 OPTIONS 检查通过所有用户。对于所有其他动词,我拒绝所有人,只允许特定的用户列表。您可以将其更改为使用角色。

在 Angular 方面,我创建了一个拦截器,它将把凭据放在所有 http 请求上。

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';


@Injectable()
export class WinAuthInterceptor implements HttpInterceptor {
  constructor() { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    req = req.clone({
      withCredentials: true
    });

    return next.handle(req);
  }
}

在您的提供商列表中的 app.module.ts 中注册

  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: WinAuthInterceptor, multi: true }
  ]

【讨论】:

    【解决方案2】:

    感谢 sideshowbarker 为我指明了正确的方向。我终于弄清楚了我一直在努力解决的根本问题并实施了解决方案。

    基本问题是我们的站点是一个内部站点,它对连接使用集成安全性,这会导致与 IIS 中的 CORS 和 CORS 支持不兼容。前端是一个带有 ASP.NET Web 服务的 Angular 6 接口。为了使用集成安全性,您必须禁用匿名身份验证。此外,为了使身份验证正常工作,您必须将 {withCredentials:true} 与您的请求一起传递。这适用于 GET 请求,因为 CORS 规范不需要这些请求的飞行前 OPTIONS,并且 GET 请求标头将具有正确解析所需的凭据令牌。

    问题与 PUT 请求有关。 CORS 需要 PUT 的预检 OPTIONS 请求,但即使您设置了 withCredentials:true 标志,OPTIONS 请求也不包含凭据。这意味着您必须启用匿名身份验证,这将破坏您的集成安全性。有点问题 22. 我无法找到一种方法来仅对 OPTIONS 启用匿名登录。我确实找到了几篇 StackOverflow 帖子,声称您可以在 IIS 中执行此操作,但我无法使其正常工作。我还发现 MS 的帖子表明 IIS 不支持此功能。这意味着您必须为 OPTIONS 请求编写自定义处理程序并生成正确的响应以满足浏览器的要求。关于这个主题有几个帖子,但我发现最好的是 Stu 在这个线程上的回应 401 response for CORS request in IIS with Windows Auth enabled

    通过一个小的改进,我能够让它为我工作。我需要在我的系统配置中支持多个域,如果我在源字段中放置多个 url,我仍然会收到错误消息。我使用 Request.Headers 的 Origin 字段来获取当前来源,并将该值发送回我构造的响应标头中,如下面的代码所示:

    编辑:玩了一段时间后,我意识到原来的解决方案存在缺陷。我的原始代码没有明确检查来源以确保它被允许。正如我之前所说,这对于 GET 操作是可以的,因为 GET 仍然会失败(并且 OPTIONS 调用对于 GET 无论如何都是可选的)但是对于 PUT,客户端将传递 OPTIONS 调用并发出 PUT 请求并在响应中得到错误。但是,服务器仍然会执行 PUT 请求,因为 CORS 是在客户端强制执行的。这将导致不应该发生的数据插入或更新。解决方案是根据 web.config 中维护的 CORS 源列表显式检查检测到的源,如果请求源无效,则返回 403 状态和空白源值。更新后的代码如下

        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            // Declare local variables to store the CORS settings loaded from the web.config
            String origins;
            String headers;
            String methods;
    
            // load the CORS settings from the webConfig file
            origins = ConfigurationManager.AppSettings["corsOrigin"];
            headers = ConfigurationManager.AppSettings["corsHeader"];
            methods = ConfigurationManager.AppSettings["corsMethod"];
    
    
            if (Request.HttpMethod == "OPTIONS")
            {
                // Get the Origin from the Request header. This should be present for any cross domain requests
                IEnumerable<string> headerValues = Request.Headers.GetValues("Origin");
                var id = headerValues.FirstOrDefault();
    
                String httpOrigin = null;
                // Assign the origin value to the httpOrigin string.
                foreach (var origin in headerValues)
                {
                    httpOrigin = origin;
                }
    
                // Construct the Access Control headers using the derived origin value and the desired content.
                if (httpOrigin == null) httpOrigin = "*";
    
                // Check to see if the origin is included in the CORS origin list
                if (origins.IndexOf(httpOrigin) < 0)
                {
                    // This has failed the CORS check so send a blank origin list and a 403 status (forbidden)
                    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "");
                    HttpContext.Current.Response.StatusCode = 403;
                }
                else
                {
                    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", httpOrigin);
                    HttpContext.Current.Response.StatusCode = 200;
                }
    
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With,content-type, Content-Type, Accept, X-Token");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials", "true");
    
    
                var httpApplication = sender as HttpApplication;
                httpApplication.CompleteRequest();
            }
    
        }
    

    此代码已添加到 global.asax.cs 文件中的 Application_BeginRequest 方法中。此外,该方法的签名已修改为包含发送者对象,如下所示: protected void Application_BeginRequest(对象发送者,EventArgs e) { 现在这将生成对满足 CORS 要求的浏览器 OPTIONS 请求的响应,并且后续的 GET 仍将得到验证,因此我认为不会对安全产生任何负面影响。

    最后一步是构建您的 GET 和 PUT 以包含凭据信息。示例请求如下所示: 让 url = environment.base_url + 'api/SubDoc/GetDocument?docID=' + docID;

    this.httpClient.get(url,
      {
        withCredentials: true
      })
      .subscribe(
        response => {
          console.log(response);
          newData = response;
          this.currentDocument = newData;
        }
      );
    

    body = { "GroupID": groupID, "ContactID": contact.ContactID, "IsActive": isActive }
    
    let url = this.base_url + 'api/ContactGroup/Put';
    this.httpClient.put(url, body,
      {
        headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
        withCredentials: true
      })
      .subscribe(
        response=>{
          let temp:number;
          temp = parseInt(response.toString(),10);
          if(!isNaN(temp)){
            this.groupID = temp;          
          }
        }
      );
    

    希望这可以让其他人摆脱我在尝试让 CORS 与 Angular 6、ASP.NET 和集成安全性一起工作时所经历的痛苦和沮丧的日子

    【讨论】:

    • 更新了解决方案以在生成对 OPTIONS 请求的 HTTP 响应之前正确检查原始值。这修复了原始解决方案中的安全漏洞。 begin_request 代码已在上面更新。
    猜你喜欢
    • 2013-02-11
    • 1970-01-01
    • 2019-11-18
    • 2016-09-29
    • 2012-07-11
    • 1970-01-01
    • 2013-10-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多