使集成的安全性和 CORS 与 Asp.Net、Angular 6 和 IIS 一起工作

Posted

技术标签:

【中文标题】使集成的安全性和 CORS 与 Asp.Net、Angular 6 和 IIS 一起工作【英文标题】:Making Integrated security and CORS work with Asp.Net, Angular 6 and 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 方法中编写自定义处理程序。您是否有使用这两种方法的经验,并且您是否曾经为集成安全站点做过这项工作? 【参考方案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 启用匿名登录。我确实找到了几篇 *** 帖子,声称您可以在 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 代码已在上面更新。

以上是关于使集成的安全性和 CORS 与 Asp.Net、Angular 6 和 IIS 一起工作的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET Web 窗体中的特定 URL 上启用 CORS

JWT+ASP.NET Core集成方案

是否有特定的 asp.net 核心配置让 CORS 与多个 API 一起工作

如何在 ASP.NET MVC 中将条带支付与自定义表单集成? [关闭]

ASP.NET-入门

ASP.NET Core 为特定控制器禁用 CORS