如何在无视图 WebAPI ASP.NET Core 应用程序中使用 [Authorize] 和防伪?

Posted

技术标签:

【中文标题】如何在无视图 WebAPI ASP.NET Core 应用程序中使用 [Authorize] 和防伪?【英文标题】:How to use [Authorize] and antiforgery in a View-less WebAPI ASP.NET Core app? 【发布时间】:2018-08-11 00:34:33 【问题描述】:

当我无法保证客户端将使用什么平台时,我无法在严格的(即Viewless)ASP.NET Core WebAPI 项目中使用[Authorize] 注释。也就是说,应用需要是真正的 API,不需要特定平台即可访问。

注意:当我说“严格的 WebAPI”时,我的项目实际上是作为由...生成的 MVC 项目开始的。

dotnet new mvc --auth Individual

...我立即从中删除了所有视图等,并更改了路由首选项以匹配 WebAPI 约定。


我正在尝试什么

当我通过 AJAX 访问标准登录功能(在下面的粘贴中精简为基本要素)时,我得到一个 JSON 有效负载并返回一个 cookie。

[HttpPost("apiTest")]
[AllowAnonymous]
public async Task<IActionResult> ApiLoginTest([FromBody] LoginViewModel model, string returnUrl = null)

    object ret = new  Error = "Generic Error" ;

    if (ModelState.IsValid)
    
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
            ret = new  Success = true ;
        else
            ret = new  Error = "Invalid login attempt" ;
    

    return new ObjectResult(ret);

如果成功,则返回类似于以下内容的 cookie:

.AspNetCore.Identity.Application=CfDJ8Ge9E-[many characters removed]; path=/; domain=localhost; HttpOnly; Expires=Fri, 16 Mar 2018 16:27:47 GMT;

问题

在看似成功的登录之后,我尝试访问两个执行完全相同操作的 API 端点,一个注释为 AllowAnonymous,一个注释为 Authorized

private IActionResult _getStatus()

    object ret = new  Error = "Generic Error" ;

    var isSignedIn = _signInManager.IsSignedIn(User);
    var userName = _userManager.GetUserName(User);

    return new ObjectResult(
        new 
            SignedIn = isSignedIn,
            Name = userName
        
    );


[HttpGet("authorizedTest")]
[Authorize]
public IActionResult GetCurrentLoginInfo2()

    return _getStatus();


[HttpGet("anonymousTest")]
[AllowAnonymous]
public IActionResult GetCurrentLoginInfo()

    return _getStatus();

anonymousTest 端点在登录前后都可以访问,尽管它告诉我即使在登录后我也没有登录(SignedInfalse)。 authorizedTest 端点永远无法访问。

我的猜测是单个 cookie 不足以通过 [Authorized] 标记。我相信我还需要一个防伪值,要么来自@html.AntiForgeryToken() 生成的表单中的隐藏值,要么来自Views 默认发送的第二个cookie。那个cookie看起来像这样......

.AspNetCore.Antiforgery.0g4CU0eoNew=CfDJ8Ge9E-[many characters removed]; path=/; domain=localhost; HttpOnly; Expires=Tue, 19 Jan 2038 03:14:07 GMT;

失败的解决方案

我已经看到很多关于如何使用纯 AJAX 的答案,基本上都是“get the anti-forgery from the hidden form”或“read it from the headers”,但我没有View;没有隐藏的形式。我也不想混sending down a partial view for the clients to scrape。

我见过的最佳答案是this one talking about using ios。情况似乎类似:

因为我们不向客户端提供 HTML,所以我们不能使用标准的 @Html.AntiForgeryToken(),因此我们必须使用 AntiForgery.GetTokens 来获取令牌并将其分发给我们的客户。

但即使我的 Intellisense “看到”AntiForgery.GetTokens,它也不会编译,即使在抓取了似乎是正确的 nuget 包之后:

dotnet add package microsoft-web-helpers --version 2.1.20710.2

那么,我的问题是:如何在 Razor 之外使用 Antiforgery,但要使用 ASP.NET Core 创建身份受限的 WebAPI 样式项目?


为什么我认为这是一个防伪问题

有一段时间,我想不通为什么我的代码不能在严格的 WebAPI 项目中运行,但在移植到标准标识 ASP.NET Core 项目(从 dotnet new mvc --auth Individual 创建的)AccountController 时却能运行.

钥匙? options.LoginPath。当我在 WebAPI 领域时,我转发到 一个 API 端点 以登录:

options.LoginPath = "/api/accounts/requestLogin";

在 stock 项目中,默认为 View在加载时提供防伪 cookie

options.LoginPath = "/Account/Login"; // As soon as Login.cshtml loads, BAM. .AspNetCore.Antiforgery cookie.

奇怪的是,有时我可以在 Postman 中删除第二个 AspNetCore.Antiforgery cookie 并仍然访问 [Authorize]-annotated 方法,所以我不能 100% 确定我没有在这里吠叫错误的树,但是这个是我迄今为止最好的领先...

【问题讨论】:

【参考方案1】:

首先,请求是幂等的:每个请求都是独一无二的,并且必须包含服务请求所需的所有信息,包括授权。传统网站使用基于 cookie 的身份验证,并且 Web 浏览器会在每次请求时将所有 cookie 发送回服务器,而无需用户干预。与请求一起发送的 cookie 用于对请求进行授权,从而使用户看起来已通过身份验证,而无需不断传递用户名和密码。

API 是一种不同的野兽。由于发出请求的通常不是实际的 Web 浏览器,因此添加授权是手动操作。这通常是通过Authorization 请求标头处理的,其中包括诸如不记名令牌之类的东西。如果你的请求没有通过类似的东西,那么就像用户从未通过身份验证一样,因为实际上他们没有。服务器不会唯一标识特定请求的来源,也不知道或关心特定客户端之前是否发出过请求。 HTTP 协议在设计上是去中心化的。

其次,防伪令牌是完全不同的东西。它们旨在防止 XSRF 攻击,其中恶意网站可能会尝试从您的站点托管表单的副本以捕获信息,然后发布到您的站点,以便用户不知道发生了任何事情。例如,假设我在http://faceboook.com 建立了一个站点(注意额外的o)。大多数用户不会注意到链接中的这种细微差别,甚至可能会不小心自己在地址栏中键入它。然后,我的恶意网站会创建与真实 Facebook 主页完全相同的副本,并提供登录位置。用户将他们的 Facebook 登录凭据输入到我的表单中(我登录后使用它来窃取他们的帐户),但我随后将这些凭据发布到与真实 Facebook 站点使用的相同处理程序。最终结果是用户最终在 Facebook 上登录,然后继续发布猫视频等业务。但是,现在我有了他们的登录凭据。

防伪令牌可以防止这种情况。该令牌已加密,因此我无法在我的恶意站点上创建一个能够在 Facebook 端成功验证的令牌。当验证失败时,请求被拒绝。基于防伪令牌要解决的问题,它们大多与 API 不兼容,因为 API 通常打算被第三方使用。任何受防伪令牌保护的 API 端点都不能被该应用程序或域之外的任何东西使用。无论如何,它们与请求的身份验证/授权无关。

【讨论】:

我得到了幂等性——这就是为什么AntiForgery.GetTokens 是一个吸引人的解决方案。发送一个唯一令牌作为登录有效负载的一部分,客户端使用该值(OAuth 样式)来验证从那里开始的操作。我相信您的意思是您不能强制 ASP.NET Identity 用于真正的 API 项目。那正确吗?我关心防伪的唯一原因是 b/c,在过多的邮递员之后,似乎身份迫使我这样做。什么是身份的 MS 替代品及其在 WebAPI 应用程序中的角色?自旋登录和身份验证令牌?我是在滚动自己的 API 身份验证密钥生成器吗? 一点也不。 Identity 可以轻松处理任何身份验证场景。关键是您需要为基于令牌的身份验证之类的东西实际设置它,然后实际将这些令牌传回。防伪令牌不能替代它。特别是,它们是一次性使用的,因此您必须在每个请求中将一个新的传递回客户端,并且他们必须在下一个请求中发送该新的。这给客户端带来了相当大的压力,实际上并不比一次又一次地验证每个请求更好。 您也可以考虑 IdentityServer4,它可以插入 Identity 并分散您的身份验证,还可以处理 OAuth 等复杂的工作流程 如果您必须使用 3rd 方库来完成 API 身份验证,我会有点感到惊讶——似乎正确的不记名令牌身份验证对于Microsoft for WebAPI as Identity 适用于 MVC。我最终基本上编写了自定义注释,以确保某些控制器操作检查请求标头中的合法身份验证令牌,并在选择身份登录期间给出这些不记名令牌。但这本质上只是将 MS Identity 纳入不记名令牌的基本身份验证中。我会查看 IdentityServer,但希望我缺少 MSDN 记录的解决方案。 ASP.NET Core 支持开箱即用的令牌身份验证,但与所有事物一样,它仍然不知道您选择如何实现它。换句话说,您仍然需要处理获取令牌的过程。这就是 OpenIddict、IdentityServer 等的用武之地

以上是关于如何在无视图 WebAPI ASP.NET Core 应用程序中使用 [Authorize] 和防伪?的主要内容,如果未能解决你的问题,请参考以下文章

asp.net 如何变成 MVC 或 WebApi?

我可以让 jQuery 读取 ASP.NET Core 2.1 的标签助手吗?似乎完全无视他们

如何在我的 ASP.NET 核心 WebApi 项目中全局启用 CORS

如何在 Asp.Net Core 3.0 WebAPI 中启用 CORS

如何在 asp.net 核心 webapi 控制器中读取请求正文?

如何在 ASP.NET WebAPI 中返回文件 (FileContentResult)