HTTP严格传输安全
在本节课中,我们将学习如何使用 HTTP 安全传输强制头(HTTP Strict Transport Security header)来确保通信安全,从而提升用户与网站之间的端到端通信安全性。
HTTP严格传输安全(HTTP Strict Transport Security,简称HSTS)是一种协议标准,通过SSL/TLS在HTTP上强制实现与服务器的安全连接。HSTS由服务器配置并通过HTTP头 Strict-Transport-Security 发送给任何HTTP客户端。该头信息指定了浏览器在此期间应仅通过HTTPS安全连接进行通信的时间段。
提示
当在不安全的HTTP连接上传输Strict-Transport-Security头时,浏览器会忽略它,因为连接本身不安全。
设置该头后,浏览器会查询一个预加载服务(如Google的),以判断该网站是否已选择启用HSTS。
风险
在使用安全的HTTPS连接时,仍可能面临恶意用户发起的中间人攻击(Man-In-The-Middle, MITM)。此类攻击会将后续对服务器的请求降级为HTTP连接。一旦建立HTTP连接,攻击者即可查看并读取所有经过的数据流。
有趣事实:
HSTS的原始草案于2011年由PayPal的Jeff Hodges、卡内基梅隆大学的Collin Jackson以及谷歌的Adam Barth发布。
即使网站使用HTTPS,仍可能发出不安全的HTTP请求。普通用户可能不会察觉异常,但仍可能暴露于中间人攻击中。
在下图(图1-1)所示的流程中,我们可以看到一个典型场景:服务器向浏览器返回登录页面的HTML文件,其中包含一些通过HTTP访问的资源(如http://cdn.server.com/images/submit.png),例如提交按钮的图片。
如果攻击者能够实施中间人攻击并“监听网络流量”,他们就能读取所有未加密的通信内容,包括携带敏感数据的HTTP请求。更严重的情况是,攻击者可以监视实际数据发送的位置,并访问为POST或PUT端点设置的HTTP资源。
解决方案
当Web服务器希望使用安全的HTTPS连接保护其客户端时,需要发送带有指定值的Strict-Transport-Security头,该值表示客户端应持续使用HTTPS连接发送请求的时间长度(单位为秒)。该时间到期后,客户端不再强制使用HTTPS。
例如,要指示浏览器在未来一小时内将所有发往服务器的请求升级为HTTPS,可使用如下配置:
1Strict-Transport-Security: max-age=3600
Helmet实现
要使用Helmet的HSTS功能,需通过npm安装相关包,并将其作为Node.js项目的依赖项添加:
1npm install helmet --save
接下来,配置hsts中间件,指示客户端在未来一个月内仅向服务器主机名发送HTTPS请求:
1const helmet = require("helmet"); 2 3// 设置HTTPS请求的有效期为1个月,单位为毫秒 4const reqDuration = 2629746000; 5 6app.use( 7 helmet.hsts({ 8 maxAge: reqDuration, 9 }) 10);
在上述代码中,我们指示Express应用使用hsts中间件,并对所有请求响应中包含Strict-Transport-Security头。
注意:如果浏览器接收到多个HSTS头指令,它只会遵循并执行第一个接收到的策略。
通常,Web服务器会拥有子域名,用于获取资源或调用REST API。我们也希望对这些子域名同样启用HTTPS保护。为此,可在hsts选项对象中加入以下可选参数:
1includeSubDomains: true;
提示
若需让浏览器禁用Strict-Transport-Security策略,服务器可将该头的
max-age设为0。这将使浏览器立即失效该策略,从而允许通过不安全的HTTP连接访问。
练习
以下练习展示了如何使用HTTP严格传输安全(HSTS)作为浏览器安全控制机制,确保仅从网站主域名加载启用HTTPS的资源。
项目整体文件结构如下:
server.js中的Express服务器views/home.handlebars中的Handlebars模板,作为示例网站public目录中包含一张我们将使用的图片
源码
您也可以从官方GitHub仓库 这里 获取源码,但平台运行的代码略有修改。
1const http = require("http"); 2const https = require("https"); 3var fs = require("fs"); 4const express = require("express"); 5const helmet = require("helmet"); 6const morgan = require("morgan"); 7const expressHandlebars = require("express-handlebars"); 8const appRoutes = require("./routes/app.routes.js"); 9 10const HTTP_PORT_NUMBER = 80; 11 12const httpApp = express(); 13httpApp.engine("handlebars", expressHandlebars()); 14httpApp.set("view engine", "handlebars"); 15 16httpApp.use( 17 helmet.hsts({ 18 maxAge: 86400, 19 }) 20); 21 22var options = { 23 key: fs.readFileSync("localhost-key.pem"), 24 cert: fs.readFileSync("localhost.pem"), 25}; 26 27httpApp.use(morgan("dev")); 28httpApp.use("/", appRoutes); 29 30httpApp.use(express.static("public")); 31 32const httpServer = http.createServer(httpApp); 33httpServer.listen(HTTP_PORT_NUMBER, function () { 34 console.log(`服务器已启动,端口:${httpServer.address().port}`); 35}); 36 37https.createServer(options, httpApp).listen(443);
运行项目
点击下方的RUN按钮运行示例应用,将打开一个Google Chrome窗口。
- 可通过
http://localhost:80查看HTTP版本 - 可通过
https://localhost:443查看HTTPS版本
No configuration found for ID: 7a3e826625b8d54404a816a8ac6972b99be931c6675d8b74c8faafbdcf3e2b59
练习1
当Express应用启动后,请访问:https://localhost:443
接着打开开发者工具调试应用的网络请求。可通过按下 CTRL+SHIFT+I 或 F12 实现。打开开发者工具后刷新页面,点击“网络”标签页。你会看到网页中包含的所有资源列表。点击“localhost”条目,在弹出窗口中切换到“Headers”标签页。你能看到HSTS头吗?
你是否检查了浏览器开发者工具中的网络标签?发现了什么?
你应该注意到以下几点:
- 对页面
https://localhost:443的主请求返回了Strict-Transport-Security安全头。 - 加载图片的请求(在
home.handlebars文件第138行可见)http://localhost/harley-davidson-zGzXsJUBQfs-unsplash.jpg被浏览器自动重定向至其HTTPS版本——这是因为HSTS会将所有请求升级为对应的HTTPS版本以确保安全加载。 - 来自
http://http.rip/favicon.ico的图标被阻止加载。
在Chrome中调试HSTS设置
在实验HSTS头时,你可能会在“localhost”页面上设置它。这可能适得其反,因为HTTP页面会被强制重定向到HTTPS。
检查Chrome的HSTS设置
若出现此类问题,可检查Chrome中HSTS的配置,并手动添加或移除域名。在Chrome地址栏输入 chrome://net-internals/#hsts 并根据需要进行更新。
界面应如图1-2所示:

清除缓存
有时,Chrome的HSTS内部配置中没有本地主机条目,但仍会发生强制HTTPS重定向。
为确保清除缓存,请按以下步骤操作:
- 访问本地主机域名
http://localhost - 按下
CTRL+SHIFT+I或F12打开开发者工具 - 找到地址栏上的刷新图标,右键点击,在弹出菜单中选择“清空缓存并强制重新加载”
现在,通过HTTP访问本地主机网站(如 http://localhost:80)应能正常工作,不会再发生HTTPS重定向。