使集成的安全性和 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
是否有特定的 asp.net 核心配置让 CORS 与多个 API 一起工作