Axe*_*ate 2 c# cookies aws-lambda aws-api-gateway asp.net-core-2.0
我有一个带有 Kestrel 的 ASP.NET Core 2 应用程序。该应用程序部署到 AWS Lambda/API Gateway。一切都按预期进行,除了一个导致一切不同的小细节。
对我的应用程序的某些请求需要发出多个与安全相关的Set-Cookie标头。由于 API Gateway 和 Lambda 之间传递数据的方式,重复的标头名称会连接在一起,这会导致标Set-Cookie头无效并且浏览器拒绝接受它。
克服此限制的建议解决方案是使用仅因大小写而异的多个标头名称:Set-Cookie, Set-cookie, set-cookie...
我知道这是一个 hacky 解决方案,但如果它有效,那么在 AWS 修复这个限制的同时,它应该已经足够好了。
但是,当使用 时HttpContext.Response.Headers.Add(name, value),已知的标头名称将被规范化并成为常规的重复标头。
是否有可能绕过这种常态化机制或以其他方式实现最终目标?
当我开始研究这个问题时,我认为这很容易。经过半天的研究(太酷了,我正在度假),我终于可以分享结果了。
HttpContext.Response.Headers类型为IHeaderDictionary. 默认情况下,在 Kestrel 上的 ASP.NET Core 应用程序中,使用FrameResponseHeaders实现。主要逻辑位于FrameHeaders基类中。该标头字典针对设置/获取常用的标准 http 标头进行了高度优化。下面是处理设置 cookie(方法)的代码片段:AddValueFast
if ("Set-Cookie".Equals(key, StringComparison.OrdinalIgnoreCase))
{
if ((_bits & 67108864L) == 0)
{
_bits |= 67108864L;
_headers._SetCookie = value;
return true;
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
就用于键比较而言StringComparison.OrdinalIgnoreCase,您不能设置另一个仅大小写不同的 cookie 标头。这是有道理的,因为HTTP 标头不区分大小写。但让我们尝试克服它。
这里显而易见的解决方案是将 的实现替换IHeaderDictionary为区分大小写的实现。ASP.NET Core 为此包含很多接缝和扩展点,从包含可设置属性的IHttpResponseFeatureHeaders开始,到替换HttpContext.
不幸的是,在 Kestrel 上运行时,所有这些替代品都不起作用。如果您检查负责编写 HTTP 响应标头的FrameFrameResponseHeaders类的源代码,您将看到它自行创建 的实例,并且不尊重通过IHttpResponseFeature或设定的任何其他实例HttpContext.Response.Headers:
protected FrameResponseHeaders FrameResponseHeaders { get; } = new FrameResponseHeaders();
Run Code Online (Sandbox Code Playgroud)
所以我们应该回到FrameResponseHeaders它的基FrameHeaders类并尝试调整它们的行为。
FrameResponseHeaders类使用已知标头的快速设置(见AddValueFast上文),但将所有其他未知标头存储在MaybeUnknown字段中:
protected Dictionary<string, StringValues> MaybeUnknown;
Run Code Online (Sandbox Code Playgroud)
其初始化为:
MaybeUnknown = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
Run Code Online (Sandbox Code Playgroud)
我们可以尝试绕过快速标头设置并将它们直接添加到MaybeUnknown字典中。然而,我们应该将使用比较器创建的字典替换StringComparer.OrdinalIgnoreCase为区分大小写的默认实现。
MaybeUnknown是一个受保护的字段,我们无法让 Kestrel 使用我们的自定义实现来保存类。这就是为什么我们被迫通过反射来设置这个字段。
我已将所有这些脏代码放入扩展类中FrameHeaders:
public static class FrameHeadersExtensions
{
public static void MakeCaseInsensitive(this FrameHeaders target)
{
var fieldInfo = GetDictionaryField(target.GetType());
fieldInfo.SetValue(target, new Dictionary<string, StringValues>());
}
public static void AddCaseInsensitiveHeader(this FrameHeaders target, string key, string value)
{
var fieldInfo = GetDictionaryField(target.GetType());
var values = (Dictionary<string, StringValues>)fieldInfo.GetValue(target);
values.Add(key, value);
}
private static FieldInfo GetDictionaryField(Type headersType)
{
var fieldInfo = headersType.GetField("MaybeUnknown", BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldInfo == null)
{
throw new InvalidOperationException("Failed to get field info");
}
return fieldInfo;
}
}
Run Code Online (Sandbox Code Playgroud)
MakeCaseInsensitive替换MaybeUnknown为区分大小写的字典。
AddCaseInsensitiveHeader绕过快速标头设置,将标头直接添加到MaybeUnknown字典中。
剩下的部分只是在控制器中的适当位置调用这些方法:
[Route("api/[controller]")]
public class TestController : Controller
{
[NonAction]
public override void OnActionExecuting(ActionExecutingContext context)
{
var responseHeaders = (FrameResponseHeaders)HttpContext.Response.Headers;
responseHeaders.MakeCaseInsensitive();
}
// GET api/values
[HttpGet]
public string Get()
{
var responseHeaders = (FrameResponseHeaders)HttpContext.Response.Headers;
responseHeaders.AddCaseInsensitiveHeader("Set-Cookie", "Cookies1");
responseHeaders.AddCaseInsensitiveHeader("SET-COOKIE", "Cookies2");
return "Hello";
}
}
Run Code Online (Sandbox Code Playgroud)
这是结果标头集:
所描述的解决方案是一个非常肮脏的黑客。它仅适用于 Kestrel,并且随着未来版本的发布,情况可能会发生变化。如果 Kestrel 完全支持 ASP.NET 接缝,一切都会变得更加容易和干净。但如果您目前没有其他选择,我希望这会对您有所帮助。
| 归档时间: |
|
| 查看次数: |
2453 次 |
| 最近记录: |