在ASP.NET Core + SPA中安全交换JWT的方法

A.

参赛作品


JWT身份验证(JSON Web令牌)是服务器和客户端之间相当统一,一致的身份验证和身份验证机制。 JWT的优势在于,它使我们可以更少地管理状态并且可以很好地扩展。 毫不奇怪,授权和身份验证及其帮助在现代Web应用程序中越来越多地使用。

使用JWT开发应用程序时,经常会出现问题:建议将令牌存储在哪里以及如何存储? 如果要开发Web应用程序,则有两个最常见的选择:

  • HTML5 Web存储(localStorage或sessionStorage)
  • 饼干

比较这些方法,我们可以说它们都将值存储在客户端的浏览器中,都非常易于使用,并且代表键值对的常规存储。 区别在于存储环境。

Web存储(localStorage / sessionStorage)可以通过同一域中的JavaScript进行访问。 这意味着您的应用程序中的所有JavaScript代码都可以访问Web存储,这会造成跨站点脚本(XSS)攻击的漏洞。 作为存储引擎,Web存储无法提供在存储和共享期间保护数据的方法。 我们只能将其用于更新(F5)或关闭选项卡时要保留的辅助数据:状态和页码,过滤器等。

令牌也可以通过浏览器cookie进行传输。 与httpOnly标志一起使用的Cookie不受XSS的影响。 httpOnly是仅在服务器上用于访问读取,写入和删除cookie的标志。 它们将无法通过客户端上的JavaScript进行访问,因此客户端将不了解令牌,并且授权将在服务器端完全处理。

我们还可以设置一个安全标志,以确保仅通过HTTPS传输cookie。 鉴于这些优点,我选择使用cookie。

本文介绍了一种在ASP.NET Core Web Api中结合SPA使用httpOnly安全cookie + JSON Web令牌来实现身份验证和身份验证的方法。 考虑服务器和客户端起源不同的选项。

设置本地开发环境


为了通过HTTPS正确配置和调试客户端-服务器关系,我强烈建议您立即配置本地开发环境,以便客户端和服务器都具有HTTPS连接。

如果您不立即执行此操作并尝试在没有HTTPS连接的情况下建立关系,那么将来会出现很多细节,否则,HTTPS生产中的安全cookie和其他安全策略将无法正常工作。

我将展示一个在OS Windows 10,服务器-ASP.NET Core,SPA-React上配置HTTPS的示例。

您可以在创建项目时使用“ 为HTTPS配置”标志在ASP.NET Core中配置HTTPS ;如果在创建时未这样做,则可以在“属性”中启用相应的选项。

要配置SPA,您需要将脚本修改为“ start” ,将其设置为“ set HTTPS = true” 。 我的设置如下:

'start': 'set HTTPS=true&&rimraf ./build&&react-scripts start' 

我建议您在create-react-app.dev/docs/using-https-in-development中为其他环境中的开发环境设置HTTPS。

配置ASP.NET Core服务器


JWT设置


在这种情况下,JWT来自文档或任何文章的最常见实现都带有附加设置options.RequireHttpsMetadata = true; 因为我们的开发环境使用HTTPS:

配置服务

 services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.RequireHttpsMetadata = true; options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters { //  .  }; }); 

配置CORS策略


重要提示 :CORS-policy必须包含AllowCredentials() 。 这是从XMLHttpRequest.withCredentials接收请求并将cookie发送回客户端所必需的。 关于此的更多内容将在以后编写。 根据项目需要配置其他选项。

如果服务器和客户端位于同一来源,则不需要下面的整个配置。

配置服务

 services.AddCors(); 

设定

 app.UseCors(x => x .WithOrigins("https://localhost:3000") //    SPA  .AllowCredentials() .AllowAnyMethod() .AllowAnyHeader()); 

设定Cookie政策


强制将cookie策略设置为httpOnly并且安全。

如果可能,设置MinimumSameSitePolicy = SameSiteMode.Strict; -这提高了不依赖跨域请求处理的应用程序类型的cookie安全性。

设定

 app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict, HttpOnly = HttpOnlyPolicy.Always, Secure = CookieSecurePolicy.Always }); 

安全令牌交换的想法


这部分是一个概念。 我们将做两件事:

  1. 使用httpOnly和secure标志将令牌放入HTTP请求。
  2. 从HTTP请求接收并验证客户端应用程序令牌。

为此,我们需要:

  • 登录时将令牌写在httpOnly cookie中,并在登录时从其中删除令牌。
  • 如果cookie中有令牌,请在每个后续请求的HTTP标头中替换该令牌。
  • 如果cookie中没有令牌,则标题将为空,并且该请求将不会被授权。

中间件


主要思想是实现自定义中间件,以将令牌插入到传入的HTTP请求中。 在获得用户授权后,我们将cookie保存在某个键下,例如: “ .AspNetCore.Application.Id” 。 我建议设置一个与授权或令牌无关的名称-在这种情况下,带有令牌的cookie看起来像AspNetCore应用程序的某种不起眼的系统常量。 因此,攻击者更有可能看到许多系统变量,并且在不了解使用哪种授权机制的情况下走得更远。 当然,如果他不阅读本文并且没有特别注意这样的常数。

接下来,我们需要将此令牌插入所有后续传入的HTTP请求中。 为此,我们将编写几行中间件代码。 这不过是HTTP管道。



设定

 app.Use(async (context, next) => { var token = context.Request.Cookies[".AspNetCore.Application.Id"]; if (!string.IsNullOrEmpty(token)) context.Request.Headers.Add("Authorization", "Bearer " + token); await next(); }); app.UseAuthentication(); 

我们可以将此逻辑带到单独的中间件服务中,以免阻塞Startup.cs,这个想法不会改变。

为了将值写入Cookie,我们只需将以下行添加到授权逻辑中:

 if (result.Succeeded) HttpContext.Response.Cookies.Append(".AspNetCore.Application.Id", token, new CookieOptions { MaxAge = TimeSpan.FromMinutes(60) }); 

使用我们的cookie-policies,这些cookie将自动以httpOnly的方式安全发送。 无需在Cookie选项中重新定义其政策。

在CookieOptions中,可以将MaxAge设置为指定生存期。 在颁发令牌时与JWT Lifetime一起指定是很有用的,这样cookie会在一段时间后消失。 CookieOptions的其他属性根据项目的要求进行配置。

为了提高安全性,建议将以下标头添加到中间件:

 context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); context.Response.Headers.Add("X-Xss-Protection", "1"); context.Response.Headers.Add("X-Frame-Options", "DENY"); 

  • X-Content-Type-Options标头用于防止MIME嗅探漏洞。 当网站允许用户下载内容但用户伪装某种类型的文件时,可能会发生此漏洞。 这可以使攻击者能够执行跨站点脚本脚本或破坏网站。
  • 所有现代浏览器均具有内置的XSS过滤功能,可在页面完全显示给我们之前尝试捕获XSS漏洞。 默认情况下,它们在浏览器中处于启用状态,但是用户可能会更棘手并禁用它们。 使用X-XSS-Protection标头,我们实际上可以告诉浏览器忽略用户的操作并应用内置过滤器。
  • X-Frame-Options告诉浏览器,如果您的网站位于HTML框架内,则不显示任何内容。 当尝试保护自己免遭点击劫持尝试时,这非常重要。

我所描述的远非所有的头条新闻。 有许多方法可以实现更高的Web应用程序安全性。 我建议您重点关注来自securityheaders.com资源的安全清单。

SPA客户端设置


当客户端和服务器位于不同的来源时,客户端上还需要其他配置。 您必须使用XMLHttpRequest.withCredentials包装每个请求。

我将方法包装如下:

 import axios from "axios"; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL }); api.interceptors.request.use(request => requestInterceptor(request)) const requestInterceptor = (request) => { request.withCredentials = true; return request; } export default api; 

我们可以用任何方式包装请求配置,主要是withCredentials = true存在

XMLHttpRequest.withCredentials属性确定是否应使用cookie,授权标头或TLS证书之类的凭据生成跨域请求。

此标志还用于确定是否将忽略响应中发送的cookie。 如果在创建此请求之前未将withCredentials标志设置为true,则来自另一个域的XMLHttpRequest无法在其自己的域上设置cookie。

换句话说,如果您未指定此属性,那么我们的Cookie不会被浏览器保存,即 我们将无法将Cookie发送回服务器,并且服务器将无法通过JWT找到所需的Cookie,也不会在我们的HTTP管道中对Bearer Token进行签名。

这都是为了什么?


上面,我描述了一种抗XSS的令牌交换方式。 让我们来看一下已实现功能的结果。

如果您进入Developer Tools,我们会看到令人垂涎的标志httpOnlysecure



让我们做一个挤压测试,尝试从客户端取出cookie:



我们正在观察'',即 无法从文档空间访问cookie,这使得无法使用脚本读取它们。

我们可以尝试通过其他工具或扩展程序来获取这些cookie,但是我尝试过的所有工具都已从文档空间调用了本机实现。

示范项目


启动说明位于README.MD中


UPD:防范CSRF


配置ASP.NET Core服务器


中间件服务

XsrfProtectionMiddleware.cs
 public class XsrfProtectionMiddleware { private readonly IAntiforgery _antiforgery; private readonly RequestDelegate _next; public XsrfProtectionMiddleware(RequestDelegate next, IAntiforgery antiforgery) { _next = next; _antiforgery = antiforgery; } public async Task InvokeAsync(HttpContext context) { context.Response.Cookies.Append( ".AspNetCore.Xsrf", _antiforgery.GetAndStoreTokens(context).RequestToken, new CookieOptions {HttpOnly = false, Secure = true, MaxAge = TimeSpan.FromMinutes(60)}); await _next(context); } } 



MiddlewareExtensions.cs
 public static class MiddlewareExtensions { public static IApplicationBuilder UseXsrfProtection(this IApplicationBuilder builder, IAntiforgery antiforgery) => builder.UseMiddleware<XsrfProtectionMiddleware>(antiforgery); } 



配置服务

 services.AddAntiforgery(options => { options.HeaderName = "x-xsrf-token"; }); services.AddMvc(); 


设定

 app.UseAuthentication(); app.UseXsrfProtection(antiforgery); 

SPA设置


api.axios.js
 import axios from "axios"; import cookie from 'react-cookies'; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL }); api.interceptors.request.use(request => requestInterceptor(request)) const requestInterceptor = (request) => { request.headers['x-xsrf-token'] = cookie.load('.AspNetCore.Xsrf') return request; } export default api; 


使用方法


为了保护我们的API方法,您必须为控制器添加[AutoValidateAntiforgeryToken]属性或为该方法添加[ValidateAntiForgeryToken]

Source: https://habr.com/ru/post/zh-CN468401/


All Articles