☰
HTTPS加密与JWT鉴权:从TLS握手到算法防混淆的完整安全链路
2026/10/10 3:22:46 网站建设 项目流程

最近帮朋友查了一个线上事故,现象很典型:站点早就全站HTTPS,登录也上了JWT,结果接口还是被人批量刷,用户数据被拉走一大半。排查到最后,问题不在密码学上,而在一个大家都默认“没问题”的细节里——服务端校验JWT时用的算法是HS256,但密钥拿的是公钥文件的内容。这类问题文档里通常不会写,只有踩过坑的人才会真正记住。

今天这篇就围绕“HTTPS加密”和“JWT鉴权机制”展开,把传输安全和身份鉴权这两条链路彻底捋一遍。适合刚接触后端的同学建立整体安全认知,也适合已经上过生产、但没时间深究原理的朋友,对照着自己的项目把隐患筛一遍。

1. HTTPS到底加密了什么——先把根本问题说清楚

1.1 没上HTTPS的时候,你的数据在裸奔

HTTP协议从设计之初就没考虑加密,所有请求和响应都以明文在网络上传输。这意味着一个登录请求里的用户名、密码,一个订单接口里的收货地址、手机号,一个支付回调里的订单金额,经过每一台路由器、交换机、运营商网关时都是可读的。

打个比方,用HTTP传数据就像寄明信片:邮局分拣员、沿途经手的人,只要拿到这张明信片就能看到上面的全部内容。而HTTPS相当于把明信片装进带锁的信封里,只有收件人手里有钥匙。

加锁这件事在技术上的实现方式,就是在TCP之上叠加了一层TLS协议。TLS负责做三件事:加密传输内容防止窃听、校验数据完整性防止篡改、验证服务端身份防止冒充。这三件事分别对应机密性、完整性、身份认证,是安全通信的三大支柱。

你不需要把TLS源码读一遍,但至少要清楚一个核心结论:HTTPS保护的是客户端和服务端之间的传输通道,管不了存储、业务逻辑和鉴权层的问题。很多人以为上了HTTPS就安全了,这是最大的误区。

1.2 TLS握手:四次报文里藏着哪些关键动作

TLS握手是整个HTTPS加密的起点。握手期间,客户端和服务端要协商加密套件、交换密钥材料、完成身份认证。用一个最常见的TLS 1.2握手流程来看:

  1. ClientHello:客户端告诉服务端自己支持的TLS版本、加密套件列表,以及一个客户端随机数。
  2. ServerHello:服务端从列表里选出一套加密套件,返回服务端随机数,同时下发自己的数字证书(证书里包含公钥)。
  3. 证书校验与密钥交换:客户端验证证书的合法性(域名匹配、有效期、签发链、吊销状态),确认没问题后生成一个预主密钥(pre-master secret),用服务端公钥加密后发给服务端。
  4. 会话密钥生成与加密通信:服务端用私钥解密拿到预主密钥,双方用“客户端随机数 + 服务端随机数 + 预主密钥”一起推导出会话密钥。之后所有业务数据都用这个会话密钥做对称加密。

这里有一个新手的典型困惑:非对称加密性能远低于对称加密,为什么HTTPS不用非对称加密直接传所有数据?原因很简单——性能。RSA或ECC做一次加密运算要消耗不少CPU,视频、图片、JSON数据全走非对称加密,服务器根本扛不住。所以TLS的智慧在于“做强交换,再用强交换换来的密钥做大流量加密”:非对称加密只保护密钥交换这一步,后续数据用AES或ChaCha20对称加密。

1.3 证书信任链:浏览器为什么给你报“不安全”

证书是HTTPS信任体系的基石。它的作用是向客户端证明“我确实是这个域名对应的服务器”。这个证明不是服务器自己说了算,而是由一个受信任的第三方——CA(证书颁发机构)来背书。

证书的信任链是树状结构:根CA证书(浏览器内置信任)→ 中间CA证书 → 服务器证书。浏览器在验证服务器下发的证书时,会沿着证书链逐级向上找,只要能找到一颗已经内置在系统信任库里的根证书,就认为这条链是可信的。

实际部署中最常遇到的坑是证书链不完整。很多人只把服务端证书(leaf certificate)部署上去了,中间CA证书没有一起配置。结果就是:浏览器拿到后无法把链拼完整,明明证书有效也会报“证书不受信任”。用openssl命令可以一目了然:

openssl s_client -connect yourdomain.com:443 -showcerts

如果返回里只有一张证书,而缺少中间证书,就需要把中间证书合并进服务端的证书文件里。大部分云厂商的证书控制台都会提供“完整链证书”的下载选项,直接选那个就好。

另外,自签名证书只适合本地开发联调。它的问题不是加密强度不够,而是无法被客户端信任——没有CA背书,客户端无法确认你没有被中间人冒充。开发环境下可以通过配置信任来绕过,但生产环境必须用正规CA签发的证书。

1.4 全站HTTPS和HSTS:很容易被忽略的强制跳转

很多项目只给登录页、支付页配了HTTPS,其他页面还是HTTP。这种“部分加密”的状态比全裸更危险:用户在HTTP页面上点击一个链接跳转到HTTPS登录页,这中间跳转的那一下是明文的,攻击者可以在这时做劫持,把用户引导到伪造的登录页。

正确的做法是全站HTTPS,并配置HTTP到HTTPS的301跳转。更进一步,加上HSTS响应头,告诉浏览器:未来一段时间内,这个域名只能通过HTTPS访问,不要先发起HTTP请求。

Strict-Transport-Security: max-age=31536000; includeSubDomains

设置HSTS之后要谨慎,max-age别一开始就设一年。如果某个子域名还没配置好HTTPS证书,HSTS强制跳转会直接把那个子域名打挂。稳妥的做法是从小范围试点,确认全部子域名都能正常走HTTPS后再逐步加大max-age。

还有一个高频问题是混合内容:HTTPS页面里通过http://加载了图片、JS、CSS,浏览器会默认拦截这类不安全请求。排查时打开开发者工具,在Console里会看到明确的“Mixed Content”提示,把对应资源的协议改成HTTPS即可。

2. JWT不是一把锁,而是一张通行证

2.1 拆开JWT看结构:Header、Payload、Signature

JWT(JSON Web Token)是一种轻量级、自包含的令牌格式。说它“自包含”,是因为令牌本身携带了用户身份信息和元数据,服务端拿到后不需要查数据库就能知道“这个请求来自谁、有效期到什么时候”。

一个JWT看起来是一长串由点号分隔的三段字符串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEwMDg4LCJleHAiOjE3MDAwMDAwMDB9.mzJvWZYX_iYbC0I8VY0n8xQzJzNTGhDUg

这三段分别是Header、Payload、Signature。Header是JSON,声明令牌类型和签名算法;Payload是声明部分,放过期时间、签发时间、用户ID等自定义字段;Signature是对前两段内容的数字签名。

这里必须反复强调:JWT的三段都只是Base64URL编码,不是加密,任何人都可以解码看到内容。千万别把密码、手机号、身份证号这类敏感信息塞进Payload。签名的目的是防篡改,不是保密。能用jwt.io直接解码验证,代价就是裸奔。

默认情况下,Payload里应该尽量少放信息,只放能唯一定位用户的ID和必要的声明(如角色、权限)。需要用户详细信息时,让服务端拿着用户ID去查缓存或数据库,而不是把整份用户资料塞进Token。

2.2 签名算法:HS256和RS256怎么选

JWT的签名算法直接决定了令牌被篡改后能否被识别,也决定了密钥管理方式。目前主流是两种:HS256(对称)和RS256(非对称)。

HS256用同一个密钥做签名和验签,逻辑简单、性能高,但风险也集中在同一个点上——密钥一旦泄露,攻击者可以自己签发任意身份的Token。适合单服务、密钥只掌握在一方手里的场景。

RS256用私钥签名、公钥验证。私钥由认证服务持有,其他服务只需要持有公钥就能验签。这样即使某个业务服务的公钥泄露了,攻击者也造不出合法Token。微服务架构、多服务共享认证结果时,RS256是更合理的方案。

选型时有一个容易踩的经典大坑——算法混淆攻击。攻击者把Token头部的alg字段从RS256改成HS256,然后尝试用服务端已知的公钥内容作为HMAC密钥来签名。如果服务端验签代码没有固定算法,直接读Header里的alg字段来决定验签逻辑,就会用公钥当HS256的密钥去校验,结果攻击者的伪造Token验签通过。

防御方式很简单:服务端在验签前固定算法白名单,只接受RS256;或者对所有入站Token先校验alg字段,不是预期算法直接拒绝。

除了选算法,还要注意加密库的使用。Java生态常用jjwt,Python常用PyJWT,Node.js常用jsonwebtoken。用库就尽量别手写Base64拼接和签名逻辑,手写最容易漏掉防混淆校验、填充问题这些边角。

2.3 Token的生命周期:为什么一定要做续签和过期

JWT有一个先天的管理难点:它无状态,签发了就没法主动吊销。业务需求里常见的“改密码后所有端下线”“封号后立即失效”,JWT天然做不到。所以设计时必须依赖短有效期来收敛风险。

常见做法是双Token方案:access token(短效)+ refresh token(长效)。

  • access token有效期短,通常15分钟到2小时,用来访问业务接口。
  • refresh token有效期长,通常7到30天,只用来换新的access token,不走业务接口。

客户端把access token放在请求头里,服务端校验通过就放行;access token过期后,客户端拿着refresh token请求一个专门的重发接口,换取新的access token;refresh token也过期,就要求用户重新登录。

两个时长怎么定?要结合业务重要度和客户端习惯。内部管理系统可以把access token设成2小时、refresh token设成7天;面对C端用户的App,access token设30分钟、refresh token设30天比较常见。原则是:风险越高的操作,有效期要越短。

续签接口本身也是攻击面。实现时至少要加这几道保护:refresh token只能使用一次(用后即弃或轮换)、携带者和账号绑定、服务端记录一个版本号,密码修改时把版本号+1,旧refresh token全部失效。

3. HTTPS和JWT怎么配合——一条完整的安全链路

3.1 两者各管一段,谁也替代不了谁

接触安全没多久的开发者很容易产生一个误解:HTTPS和JWT似乎是重复建设。实际上它们解决的是完全不同层次的问题。

HTTPS解决的是传输层信任:保证数据在网络上传输时不被窃听、不被篡改、不被冒充服务器。JWT解决的是应用层身份信任:证明“这个请求确实是某个用户发出的、且在有效期内”。没有HTTPS,JWT在网络上传输时会被抓包截获,攻击者拿到Token后可以重放攻击;没有JWT,HTTPS只是加密了匿名流量,服务端根本不知道请求者是谁、该不该放行。

类比一下:HTTPS是门锁和门禁,确保只有合法访客进入大楼;JWT是工牌,进入大楼后,警察、管理员、普通员工能去的地方完全不一样。两者配合才是完整的安全闭环。

3.2 一个登录到接口调用的完整流程

以最常见的SPA项目为例,走一遍完整流程:

  1. 用户提交用户名、密码和图形验证码。验证码这一步值得单列:它能有效防撞库和暴力破解,建议在登录接口上强制开启,同时配合失败次数限制和锁定策略。
  2. 服务端校验用户凭据,通过后生成access token和refresh token,返回给前端。
  3. 前端把access token存到内存或localStorage,把refresh token存到localStorage或httpOnly Cookie。
  4. 后续每个请求,前端在Authorization: Bearer <token>头里带上access token。
  5. 服务端网关或拦截器统一做三步处理:验签、校验exp、把用户ID解出来放进请求上下文。
  6. 业务接口从上下文里取用户ID,做细粒度的权限判断,然后执行业务逻辑。

这里就引出一个经典的存储争论:Token放localStorage还是httpOnly Cookie?localStorage的痛点是XSS——只要页面里有一个脚本注入点,攻击者就能把Token读走。httpOnly Cookie的痛点是CSRF——浏览器会在请求里自动带Cookie。没有银弹,取决于你的风险偏好:强交互型内网系统,可能选localStorage加严格CSP更顺手;面向C端的系统,很多团队倾向httpOnly Cookie加CSRF防护双管齐下。

3.3 常用的接口鉴权方案横向对比

把Session、JWT、OAuth2.0放在一起对比,能更清楚JWT的边界在哪里:

方案状态存放扩展性典型场景主要劣势
Session + Cookie服务端内存/Redis多实例需共享Session存储单体应用、传统Web需要共享存储,横向扩展有额外成本
JWT客户端持有,服务端无状态天然适合多实例、微服务前后端分离、API网关难主动吊销,载荷不能太大
OAuth2.0授权码+令牌,有状态适合第三方授权、开放平台第三方登录、开放API协议复杂度高,需要理解四种授权模式

JWT的优势在无状态和跨服务共享。一次签发的Token,多个微服务都可以验签通过,不需要集中式会话存储,这对容器化部署、弹性伸缩特别友好。劣势就是前面说的:无法主动吊销、无法踢人。Session模式虽然要管Redis,但踢人、封号、查看在线用户都是顺手的事。

所以选型不要跟风,先想清楚自己有没有“立即失效”的强需求。有这种需求还执着于无状态JWT,后面会非常拧巴。

3.4 最容易被打穿的几个薄弱点

再安全的链路,只要有一个口子漏了,等于白搭。按我的排查经验,最常见的是下面四个:

  • XSS窃取Token:前端把Token放在localStorage,同时页面上有未过滤的输入点,攻击者用一段恶意脚本直接把Token发到自己的服务器。防御靠CSP、输入输出过滤、HttpOnly Cookie三管齐下。
  • CORS配置过宽:后端把Access-Control-Allow-Origin设成*,等于告诉所有网站:你们可以跨域调用我的接口。严格的做法是白名单机制,只允许特定源,且不反射用户传入的Origin。
  • 日志打印Token:网关层、业务层为了排查问题,把鉴权头打进了日志系统。日志系统一旦被读,批量Token直接泄露。建议日志里对Token做脱敏,只保留前缀和后几位。
  • 代理/CDN层未鉴权:有些单体应用在Nginx层做了静态资源代理,但鉴权只在应用代码层做。如果某个路由直接映射到内部接口,发到CDN上的请求根本不会走到应用层鉴权。要确保所有动态请求都经过鉴权节点,不能在网关层裸放行。

4. 常见问题排查与避坑技巧实录

4.1 HTTPS证书类问题速查表

症状可能原因排查方式
浏览器提示证书已过期证书超过有效期,未及时续期查看证书有效期,提前30天做自动续期
浏览器提示证书不受信任证书链不完整,缺中间证书用openssl s_client -showcerts检查返回的证书数量
证书适用于其他域名域名与证书中的SAN列表不匹配检查证书绑定域名,确认有无泛域名证书
部分用户访问异常本地时钟偏差过大,证书验证失败检查服务器和客户端时间同步,建议上NTP
手机端访问报错但PC正常部分根证书未更新或信任库差异检查中间证书是否齐全,选用受信度高的CA

证书过期是最常见的线上事故原因,没有之一。强烈建议做两张保单:一是自动续期脚本或云厂商的托管证书;二是有效期监控告警,比如在过期前30天、7天分别报警。

4.2 JWT类问题调试指南

JWT调试最常用的工具是jwt.io。但要提醒一句:别把生产环境的Token贴进去。jwt.io是网页版解码工具,Token一旦贴进去就相当于把身份信息暴露给了第三方。敏感环境下建议用本地脚本解码,或者自己写一个小工具页面。

比较典型的JWT问题包括:

  • 签名验证总是失败:优先检查密钥是否一致。HS256场景同一份密钥要在签发端和验签端保持一致,尤其注意换行符、Base64解码后再传还是原文传这类“看起来一样实际不一样”的细节。
  • Token过了一会就失效:检查服务端时间是否正确。JWT里的iat和exp都是Unix时间戳,如果服务器时钟漂移了几分钟,恰好卡在边界上的Token容易误判。设置较短的时钟偏差容忍窗口,例如±30秒。
  • 续签接口偶发400/401:检查refresh token是否被重复使用。如果上一次请求超时,客户端重试把同一个refresh token发了两次,服务端如果已经把它标记为已使用,第二次就会被拒。这里建议在续签接口上加幂等键。

另外,安全测试场景里经常要用jmeter录制HTTPS脚本。默认情况下jmeter录不到HTTPS流量,需要在jmeter里配置代理并导入服务端的根证书(如果是自签证书)。操作路径是:选项 → SSL管理器 → 导入证书,然后在系统或浏览器配置JMeter的代理端口。这一步几乎是录制HTTPS压测脚本的必经之路。

4.3 JWT鉴权绕过漏洞自查清单

把已知的JWT漏洞做一个自查清单,每一条都对应着实际踩过的坑:

  • 算法混淆:验签代码是否固定算法白名单?Header里的alg是否被信任?这是最容易被利用的点。
  • 密钥太弱:HS256的密钥是否足够长、是否随机?弱密钥可以被离线爆破。
  • 密钥硬编码:私钥是否提交到了Git仓库?是否有定期轮换机制?
  • exp/nbf不校验:过期时间是不是只是“签了但没验”?带一个过期Token测一下,如果还能访问接口,就是漏校验。
  • Signature缺失:有些实现对于三段式中缺签名的Token会直接放过,测试时故意去掉签名部分试一下。
  • 敏感信息泄露:Payload里是不是塞了不该塞的数据?Base64解码能看到的都不是秘密。

每一条都值得在代码评审时重新过一遍。尤其是前两条,能利用的难度低、危害大。

5. 我的几点实战经验总结

项目做多了之后,我对安全有一个越来越强的体会:大多数事故不是被高深的攻击打穿的,而是败在基础配置和遗漏的校验上。证书过期没人盯、算法没做白名单、Token存了敏感字段、鉴权中间件漏配了一个路由——这些才是真正常见的事故源。

有一个“三张表”习惯我觉得很有效:证书和密钥的到期日历、接口鉴权覆盖情况登记表、Token密钥的轮换记录表。每次上线新服务,先对着这几张表把环境过一遍,比什么安全扫描都实在。

最后再分享一个小技巧:在网关层统一做JWT验签,把解析出来的用户ID写入请求头(如X-User-Id),下游业务服务直接从请求头取,不再各自重复验签。这样既统一了策略,又避免了每个服务各写一套验签逻辑、各犯一遍相同错误。安全链路,关键是要把规则的主动权握在自己手里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询