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")
设定Cookie政策
强制将cookie策略设置为httpOnly并且安全。
如果可能,设置
MinimumSameSitePolicy = SameSiteMode.Strict;
-这提高了不依赖跨域请求处理的应用程序类型的cookie安全性。
设定 app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict, HttpOnly = HttpOnlyPolicy.Always, Secure = CookieSecurePolicy.Always });
安全令牌交换的想法
这部分是一个概念。 我们将做两件事:
- 使用httpOnly和secure标志将令牌放入HTTP请求。
- 从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,我们会看到
令人垂涎的标志
httpOnly和
secure :

让我们做一个挤压测试,尝试从客户端取出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]
。