HTTP 和 HTTPS 就差一个 S,凭啥一个被浏览器骂"不安全"?
不知道朋友们有没有过这种经历:自己辛辛苦苦本地起了个服务,浏览器地址栏敲http://localhost:8080是岁月静好;可一旦部署到服务器上,用域名http://一访问,浏览器立马给你甩脸子——要么是个大大的"不安全",要么直接警告"你与此网站之间建立的连接并非私密连接"。
咱当时就懵了:HTTP 和 HTTPS 不就差一个字母 S 吗?咋差距跟"我跟马云都姓马"一样离谱捏?
更离谱的是,去面试的时候面试官一句"说说 HTTPS 的加密过程",能把一堆人问得哑口无言。咱明明每天都在用 HTTPS,却说不清楚它到底把 HTTP 怎么了 (꒦ິ⌓꒦ີ)
所以这篇就来把这个事掰开揉碎了讲清楚——它不背八股,咱就聊明白 HTTP 有啥毛病、HTTPS 是怎么一个一个把毛病治好的。看完之后,面试再被问到,你也能跟面试官唠上两句。
一、HTTP 到底是个啥
先用大白话说:HTTP(超文本传输协议)就是浏览器和服务器之间"聊天"用的一套规矩。
你打开一个网页,本质上是这么个流程:
- 浏览器(客户端)跟服务器说:“我要
/index.html这个文件” - 服务器把文件内容回给你
- 浏览器拿到 HTML,再按里面的内容去要 CSS、JS、图片……
就这么一来一回,靠的就是 HTTP。它规定了"请求长什么样"、“响应长什么样”、"用哪个方法(GET/POST 啥的)"这些格式问题。
那你有没有想过一个问题
浏览器和服务器之间的这些"聊天记录",是怎么传到对方那儿的?
答案是通过一大堆网络设备:你的路由器 → 运营商 → 各种骨干网节点 → 目标服务器所在的机房……中间要经过多少个节点,谁也说不准。
关键来了:HTTP 传输的东西是"明文"的。
啥叫明文?就是原原本本、不加任何遮挡地传。这就好比你去寄快递,但是没装箱、没封口,里面的东西一路裸奔到收件人手里,中途任何一个经手的人都能直接看到、甚至顺手改一改。
平时传个"今天天气不错"那当然无所谓,可你要是传的是账号密码、身份证号、银行卡号、支付验证码呢?想想就后背发凉 (。•́︿•̀。)
二、HTTP 的三大原罪
咱把 HTTP 的问题总结成三条,江湖人称"三大原罪":
罪一:窃听(别人能看见)
因为明文传输,数据经过的任何一台中间设备(比如公共 WiFi 的路由器)都能把你的聊天内容看个精光。
你在星巴克连个免费 WiFi,随手填个登录密码,旁边坐着的大哥可能就把你密码抄走了。
罪二:篡改(别人能改)
更狠的是,中间人不仅能看到,还能改了再转给对方。
比如你请求一个网页,中间人偷偷在返回的 HTML 里塞了一段广告代码、或者把页面里的"转账给张三"改成"转账给李四",你这边完全不知情。
罪三:冒充(别人能装)
还有一招叫冒充:中间人直接假装自己就是目标服务器,跟你建立连接。你以为你在跟银行聊,其实全程是在跟骗子聊。
,只有服务器手里握着唯一能开锁的钥匙(私钥)。任何人想给服务器发东西,就用这把公开的挂锁锁上,中途谁都没法开,只有服务器能开。
听起来完美对吧?但非对称加密很慢,开销比对称加密大得多。你拿它去加密整个网页、几张高清大图,那性能直接原地爆炸。
那咋整捏?
混合加密:我全都要
成年人不做选择——两种一起用。
用非对称加密来"安全地交换"那把对称加密的钥匙,然后用对称加密来传真正的数据。
翻译成人话就是:
服务器先用自己的私钥保护一把对称钥匙的交换过程(保证钥匙安全送到),双方拿到同一把对称钥匙之后,后面所有聊天内容都用这把钥匙加解密(保证速度)。
这样既安全又快,这就是 TLS 的实际做法。聪明吧嘿嘿。
五、数字证书:怎么确定对面真是"本人"
加密的问题解决了,可还有个要命的环节:我凭什么相信对面给我的公钥,真的是银行的公钥?
万一对面是骗子,它把自己的公钥发给我,那我加密的东西不就全被它解开了?这就是前面说的冒充。
于是**数字证书(CA 证书)**出场了。
你可以把数字证书理解成网站的"身份证",而颁发它的机构叫CA(证书颁发机构,Certificate Authority),相当于公安局。
流程大致是:
- 网站向 CA 申请证书,提交自己的公钥和域名信息
- CA 审核通过后,用自己的私钥给网站的信息签个名,生成证书
- 浏览器访问网站时,网站把这个证书发过来
- 浏览器内置了各大 CA 的公钥,拿 CA 的公钥一验签,就能确认"这证书确实是 CA 发的,而且是给这个网站的,没被伪造"
所以浏览器信任的不是网站本身,而是**“给这个网站背书的 CA”**。全球那些知名 CA(DigiCert、Let’s Encrypt 等)早就被所有浏览器内置信任了,它们不可能为了你这一个网站砸自己招牌,所以这套信任链是成立的。
注意:证书是有有效期的!常见的坑就是"证书过期了",浏览器一样会报警告。这是很多线上事故的经典原因,后面咱细说。
六、TLS 握手:一场精心设计的"接头"
好,把前面的零件拼起来,就得到了完整的TLS 握手过程。你可以把它想象成一场地下党接头,双方得先对暗号、验身份,才能开始传文件。
大致的步骤是:
- 客户端打招呼(Client Hello):浏览器说"你好,我支持这些加密算法、这个 TLS 版本,这是我的随机数 A"
- 服务器回应(Server Hello):服务器说"收到,那咱用这套算法,这是我的随机数 B",并把证书一起发过来
- 验证证书:浏览器拿内置的 CA 公钥验证这证书靠不靠谱、域名对不对、过没过期
- 生成会话密钥:浏览器生成一个"预主密钥",用证书里的公钥加密,发给服务器(只有服务器的私钥能解开)
- 双方各自算出会话密钥:浏览器和服务器拿"随机数 A + 随机数 B + 预主密钥"这三样,各自算出一个一模一样的对称密钥(这个密钥从来没在网络上明文传过,中间人根本算不出来)
- 加密通信开始:从此以后双方就用这把对称密钥加密数据,聊得又快又安全
注意这里精妙的地方:对称密钥不是"传"过去的,而是双方各自算出来的。中间人就算全程偷听,手里没有服务器私钥,也推不出这个密钥。这一步是 HTTPS 防窃听的核心。
补充一下:上面说的是 TLS 1.2 的经典流程,握手要来回好几趟。TLS 1.3 把这套流程优化到了只需一趟往返(1-RTT),还砍掉了一堆不安全的旧算法,速度更快。现在主流浏览器和服务器基本都在用 TLS 1.3 了。
七、Java 里怎么用 HTTPS
讲完原理,咱回到老本行——Java 里访问一个 HTTPS 接口,到底要不要干啥特殊操作?
答案是:基本上不用。
前面第一篇咱聊过的 JDK 原生的HttpClient,你直接把地址写成https://就行,握手、加解密这些脏活累活它全帮你办了:
importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.net.URI;HttpClientclient=HttpClient.newHttpClient();// 直接用 https,剩下的交给 JDKHttpRequestrequest=HttpRequest.newBuilder().uri(URI.create("https://api.github.com/users/octocat")).GET().build();HttpResponse<String>response=client.send(request,HttpResponse.BodyHandlers.ofString());System.out.println(response.body());Spring 的RestTemplate、WebClient也是同理,写https://就完事了,因为 JDK 内置了一个叫做cacerts的信任库,里面躺着全球各大 CA 的根证书,验签全靠它。
那啥时候会翻车捏?
当你访问的是自签名证书(自己给自己签的证书)的网站时。
比如公司内网的一些服务、测试环境、咱自己用keytool签的证书,这类证书没有 CA 背书,浏览器和 Java 都会认为"这证书来路不明",直接拒绝连接。
浏览器里你还能点个"继续前往(不安全)"强行闯过去,但在 Java 代码里,它会直接给你甩一个异常:
javax.net.ssl.SSLHandshakeException:PKIXpath building failed:sun.security.provider.certpath.SunCertPathBuilderException:unabletofindvalid certification pathtorequestedtarget这一长串看着吓人,翻译过来其实是:“这证书我没见过,也没人给它背书,我不敢跟它聊。”
两个解决办法
方案一:把证书导入 JDK 的信任库(推荐,正规做法)
把这些自签名证书导入到 JDK 的cacerts信任库里,之后 JDK 就认它了。用 JDK 自带的keytool工具:
# 1. 先从网站上把证书导出来(浏览器点锁图标就能导出)# 假设导出来叫 server.crt# 2. 导入到 JDK 的信任库keytool-import-trustcacerts-aliasmyserver\-fileserver.crt\-keystore"$JAVA_HOME/lib/security/cacerts"\-storepasschangeit注意:
cacerts默认密码就是changeit,这是个大家都知道的公开密码,生产环境记得改。另外你这么导是改的本机 JDK,换个环境部署又得重新导一遍,有点烦但最正规。
方案二:代码里信任所有证书(方便,但只能测试用)
有些朋友觉得导证书太麻烦,就想在代码里"一把梭"——搞一个什么都信的TrustManager:
// ⚠️ 警告:这么写会完全放弃证书校验,等于裸奔,生产环境千万别用!TrustManager[]trustAll=newTrustManager[]{newX509TrustManager(){publicvoidcheckClientTrusted(X509Certificate[]c,Stringa){}// 不检查publicvoidcheckServerTrusted(X509Certificate[]c,Stringa){}// 不检查publicX509Certificate[]getAcceptedIssuers(){returnnewX509Certificate[0];}}};这么写 HTTPS 就白上了——中间人攻击、冒充全都防不住了,纯粹是把 HTTPS 当成 HTTP 在用。所以这个法子只能在本地测试图个方便,千万别带进生产代码。咱踩过这个坑,当年图省事上线了,后来被安全扫描揪出来一通批评 (。ŏ_ŏ)
八、几个咱踩过的坑
除了证书,实际开发里还有几个常见坑,提前给你们排一排。
坑一:HTTP 页面里混着 HTTPS 资源 → 浏览器拦截
浏览器现在有个规矩:HTTPS 页面里不允许再加载 HTTP 的图片/脚本/接口,这叫混合内容(Mixed Content),会被直接拦掉。
表现就是:页面明明开了 HTTPS,但图片不显示、接口报错。排查思路:打开浏览器 F12,看看 Console 里是不是一堆"Mixed Content"的报错,把那些 http:// 的资源全换成 https:// 就行。
坑二:证书过期
前面说过证书有有效期(一般 1 年,Let’s Encrypt 的免费证书是 90 天)。忘了续期,网站直接访问不了,用户看到的是一片红色的警告页。
建议:用 Let’s Encrypt 的话把自动续期配上(certbot renew),或者干脆用云厂商的免费证书 + 监控到期时间。这个坑坑过无数团队,包括一些大厂。
坑三:老 JDK 不支持的 TLS 版本
有些老项目还跑在 JDK 8 早期版本上,遇到只支持 TLS 1.3 的服务器,握手会失败。排查思路:用curl -v https://your-server.com看看握手协商的 TLS 版本,再对照 JDK 支持的版本。升级 JDK 或手动开启高版本 TLS 通常能解决。
坑四:HTTPS 就一定安全吗?
不是。HTTPS 保证的是"传输过程"的安全,不保证网站本身是好人。一个钓鱼网站照样可以申请合法证书、挂上 HTTPS。
所以看到那个小锁🔒,只能说明"你和这个网站之间的传输是加密的",不能说明这个网站可信。这个区别,咱心里要有数。
九、总结一下
咱用一张表把 HTTP 和 HTTPS 的差别收个尾:
| 对比项 | HTTP | HTTPS |
|---|---|---|
| 全称 | 超文本传输协议 | HTTP + TLS |
| 传输方式 | 明文 | 加密 |
| 默认端口 | 80 | 443 |
| 能否防窃听/篡改/冒充 | 都不能 | 都能 |
| 需要证书吗 | 不需要 | 需要(CA 签发) |
| 速度 | 略快(少一次握手) | 略慢(多一层加解密,但影响很小) |
一句话概括这篇的核心:
HTTP 只管把话传到,不管路上的安全;HTTPS 在 HTTP 下面垫了一层 TLS,通过"非对称加密换对称密钥 + 数字证书验明正身",把窃听、篡改、冒充这三大原罪挨个治好了。
现在你再看浏览器地址栏那个小锁,是不是感觉完全不一样了嘿嘿。
给个实践建议:现在做项目,https://基本已经是默认选项了。Let’s Encrypt 的证书免费、能自动续期,配合 Nginx 配一下也就十几分钟的事(不折腾的话,云厂商控制台点几下也能一键申请)。没有任何理由让自己的网站还裸奔在 HTTP 上。
以上是个人的一些经验分享,原理部分已经尽量说人话嘞。如果有哪里有什么错误的地方,也请大佬们不吝指出,咱一起学习进步!
本文完结撒花!!!ヾ(≧▽≦*)o