首页 > 解决方案 > 使集成的安全性和 CORS 与 Asp.Net、Angular 6 和 IIS 一起工作

问题描述

我有一个带有 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”标头。因此,不允许访问源“ http://localhost:2452 ”。

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

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

这是 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

我现在已经在这个问题上花了几天时间,任何帮助都将不胜感激。

标签: asp.netcors

解决方案


感谢 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 在这个线程上的回应 启用 Windows 身份验证的 IIS 中的 CORS 请求的 401 响应

通过一个小的改进,我能够让它为我工作。我需要在我的系统配置中支持多个域,如果我在源字段中放置多个 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(object sender, EventArgs e) { 这将生成对满足 CORS 要求的浏览器 OPTIONS 请求的响应,并且后续 GET 仍将也得到验证,所以我不相信有任何负面的安全影响。

最后一步是构建您的 GET 和 PUT 以包含凭据信息。示例请求如下所示: 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;
    }
  );

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 和集成安全性一起工作时所经历的痛苦和沮丧的日子


推荐阅读