SSH首次连接安全验证:公钥指纹与known_hosts机制详解
1. 问题现象与本质为什么SSH会“不信任”新主机当你第一次尝试用SSH连接一台新的服务器、虚拟机甚至是同事的电脑时终端里大概率会跳出这样一段让人心头一紧的提示The authenticity of host ‘192.168.1.100 (192.168.1.100)’ can‘t be established. ECDSA key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. Are you sure you want to continue connecting (yes/no/[fingerprint])?很多新手看到“can‘t be established”无法建立和那一长串指纹第一反应是“是不是出错了”然后下意识地输入yes问题似乎就解决了。但如果你每次都这么干其实是在系统性地关闭一项至关重要的安全防护机制。这个提示不是错误而是OpenSSH客户端在尽职尽责地对你进行“安全考问”。它的本质是公钥指纹验证。SSH协议采用非对称加密服务器端存有私钥客户端连接时服务器会出示其公钥。为了确保你连接的是“真”服务器而不是一个伪装成目标服务器的“中间人”客户端需要验证这个公钥的身份。验证的依据就是本地的一个“信任名单”——~/.ssh/known_hosts文件。当你第一次连接某台主机时它的“指纹”公钥的哈希值自然不在这个名单里于是SSH客户端就会暂停连接要求你人工确认“嘿我从未见过这个家伙这是它的身份证指纹你确认要信任它吗”你输入yes客户端就会把这个指纹记录到known_hosts文件里下次连接就不再询问。所以这个提示是一个安全特性而非故障。理解这一点是正确处理所有相关问题的前提。2. 公钥指纹与known_hosts文件信任机制的基石要深入解决连接问题我们必须先拆解这个信任机制的核心组件。2.1 公钥指纹主机的“数字身份证”服务器SSH服务的公钥通常保存在/etc/ssh/ssh_host_*key.pub文件中。直接对比一长串公钥字符很不方便因此SSH采用了“指纹”。指纹是使用加密哈希函数如SHA256对公钥进行计算后得到的一串简短、唯一的字符串。例如一个ECDSA密钥的SHA256指纹看起来像这样SHA256:zH0Z5LQcLpPcQkLqQbWjU7pKsT8JxYlM6nAqBvCdEfG这串字符就是服务器身份的浓缩代表。当客户端提示你“无法建立真实性”并展示指纹时它是在说“这是对方提供的身份证请你核验。” 理论上最安全的做法是你通过另一个绝对可信的渠道比如登录服务器控制台或者询问服务器管理员获取该服务器正确的指纹然后与客户端提示的指纹进行比对。如果一致说明你连接的就是目标服务器可以放心输入yes如果不一致则极有可能存在中间人攻击应立即断开连接。2.2 known_hosts文件客户端的“通讯录”~/.ssh/known_hosts文件是SSH客户端的信任数据库。它的每一行记录了一条信任信息格式通常为[hostname],[ip] ssh-key-type key-fingerprint例如github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQaPXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMfSe8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjBweqqUUmpaaasXVal72JUX2B2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7wC604ydGXA8VJiS5ap43JXiUFFAaQ这个文件的作用是当再次连接github.com时客户端会取出服务器发来的公钥计算其指纹然后与文件中记录的github.com对应的指纹进行比对。如果匹配则静默通过如果不匹配则会抛出更严重的警告WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!提示你主机密钥可能已变更或存在安全风险。2.3 密钥类型演进从RSA到Ed25519随着加密技术的发展SSH使用的密钥类型也在升级RSA历史最悠久兼容性最好但密钥较长通常2048位以上才安全。DSA已不被推荐使用存在安全缺陷。ECDSA基于椭圆曲线加密在相同安全强度下密钥比RSA短效率更高是目前的主流选择之一。Ed25519另一种椭圆曲线算法被认为更安全、更快且密钥更短是当前的最佳实践。你可能会在指纹行或known_hosts文件中看到ssh-rsa、ecdsa-sha2-nistp256、ssh-ed25519等前缀它们就代表了不同的密钥类型。一台服务器通常同时支持多种类型的密钥。3. 安全连接的正确姿势从首次验收到自动化面对“无法建立真实性”的提示我们有几种不同安全级别的应对策略。3.1 手动验证最安全适用于生产环境或敏感服务器这是教科书式的标准操作流程首次连接前先获取官方指纹。如果你要连接公司的一台重要服务器应该先通过内部Wiki、邮件或直接联系运维获取该服务器SSH主机密钥的正确指纹。获取命令通常在服务器上执行ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub # 或者查看所有类型的密钥指纹 for key in /etc/ssh/ssh_host_*.pub; do ssh-keygen -lf $key; done发起连接比对指纹。在客户端发起SSH连接当出现提示时仔细比对终端显示的指纹与官方提供的指纹是否完全一致包括SHA256:前缀和后面的所有字符。谨慎确认。只有完全一致才输入yes。输入no会拒绝连接。现在新版本的OpenSSH还支持直接输入你看到的完整指纹来进行验证这比输入yes更精确。3.2 临时跳过用于测试或受控环境在某些情况下比如快速测试一个刚用Docker启动的、生命周期很短的开发环境手动验证显得繁琐。此时可以通过环境变量或命令行参数临时改变客户端的行为ssh -o StrictHostKeyCheckingno userhostname或者export SSH_OPTS-o StrictHostKeyCheckingno ssh $SSH_OPTS userhostname重要警告StrictHostKeyCheckingno是一个危险的参数。它意味着客户端将无条件接受任何服务器提供的公钥并自动将其加入known_hosts。这完全绕过了中间人攻击的防护。绝对不要在脚本、自动化工具或生产环境的配置中永久设置此选项仅限在完全信任、隔离的网络环境中临时使用。3.3 预置指纹自动化场景的平衡方案对于自动化运维Ansible、CI/CD流水线等既不能手动输入yes也不能关闭安全检查。这时预置指纹是最佳实践。方法是在执行自动化任务之前先手动或通过一个安全的管理流程将目标主机的公钥指纹预先添加到执行机用户的known_hosts文件中。有几种方式使用ssh-keyscan命令这个命令可以安全地从目标主机获取公钥注意它不进行验证所以仍需确保目标主机地址正确。ssh-keyscan -H hostname ~/.ssh/known_hosts-H选项会对主机名进行哈希存储增强一点隐私性。你可以将这条命令整合到自动化环境的初始化脚本中。直接编辑known_hosts文件将从可信渠道获取的完整known_hosts条目直接写入文件。使用Ansible的known_hosts模块如果你使用Ansible它提供了专门的模块来管理known_hosts更为优雅。3.4 应对“Host Key Changed”警告如果你曾经连接过一台服务器某天突然收到WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!的强烈警告并拒绝连接这通常意味着服务器操作系统重装。SSH服务重新生成主机密钥例如执行了rm /etc/ssh/ssh_host_*并重启了sshd。极少数情况下可能是你遭到了中间人攻击。处理步骤首先确认变更是否合法。联系服务器管理员确认主机密钥是否预期变更。如果变更合法需要清除旧的指纹。使用以下命令从known_hosts中删除对应主机的旧记录ssh-keygen -R hostname # 或 ssh-keygen -R ip_address执行后再次连接就会像首次连接一样提示你验证新的指纹。切勿直接忽略或覆盖。不要在不确认的情况下盲目地编辑known_hosts文件删除对应行更不要设置StrictHostKeyCheckingno来绕过。这个警告是SSH保护你的最后一道重要防线。4. 高级场景与深度排错解决了基本的信任问题在一些复杂场景下你可能会遇到更深层次的连接故障。以下是一些常见难题的排查思路。4.1 网络与防火墙导致的连接超时现象连接时长时间卡住最后报错Connection timed out或No route to host根本看不到密钥验证的提示。 排查基础连通性检查ping hostname_or_ip如果ping不通说明网络层就不通问题不在SSH。端口探测SSH默认使用22端口。使用telnet或nc检查端口是否开放。telnet hostname 22 # 或 nc -zv hostname 22如果连接被拒绝或超时可能是目标服务器防火墙如firewalld、iptables、ufw未放行22端口。目标sshd服务未运行。在服务器上检查systemctl status sshd。SSH服务监听了非标准端口。需要确认连接命令是否指定了端口-p。4.2 服务器配置拒绝连接现象连接很快被拒绝提示Connection refused或Permission denied (publickey)。 排查检查sshd配置服务器端的/etc/ssh/sshd_config文件是关键。PermitRootLogin是否允许root直接登录。PasswordAuthentication是否允许密码认证。如果设为no你必须使用密钥对登录。AllowUsers/DenyUsers是否限制了可登录的用户。PortSSH服务监听的端口号。 修改配置后需重启服务sudo systemctl restart sshd。用户目录权限对于密钥登录服务器上对应用户的~/.ssh目录权限必须为700~/.ssh/authorized_keys文件权限必须为600。权限错误会导致静默失败。SELinux/AppArmor在某些严格的安全策略下这些安全模块可能会阻止SSH进程读取密钥文件。可以尝试临时设置为宽容模式排查。4.3 客户端配置与多主机管理当需要管理大量不同配置的服务器时客户端的~/.ssh/config文件是神器。它可以为不同主机或主机组设置别名、端口、密钥文件、用户名等。一个典型的配置示例Host myserver HostName 192.168.1.100 User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519_myserver StrictHostKeyChecking accept-new Host *.internal.company.com User admin IdentityFile ~/.ssh/id_rsa_internalStrictHostKeyChecking accept-new这是一个比no更安全的选项。它表示“如果主机不在known_hosts中则自动接受并添加但如果主机密钥发生变化则拒绝”。非常适合自动化场景。通过定义Host你可以直接用ssh myserver连接无需记忆复杂的参数。4.4 密钥对登录失败排查如果配置了密钥登录却失败可以启用SSH客户端的详细模式查看握手过程ssh -vvv userhostname关注输出中的这些关键行debug1: Offering public key: ...客户端是否在尝试发送你的公钥debug1: Server accepts key: ...服务器是否接受了你的公钥debug1: Authentications that can continue: publickey,password如果最后落到密码认证说明密钥认证未通过。常见原因客户端密钥未加载确保你的私钥文件如id_rsa存在且权限为600。使用ssh-add ~/.ssh/id_rsa将密钥添加到ssh-agent。公钥未正确部署服务器~/.ssh/authorized_keys文件中的公钥内容必须与客户端公钥文件如id_rsa.pub的内容完全一致包括末尾的注释。多一个换行符都可能导致失败。私钥格式问题旧版本的OpenSSH可能不支持新格式的私钥。可以使用ssh-keygen -p -f ~/.ssh/id_rsa来更新私钥格式通常需要输入旧密码。5. 集成开发环境中的SSH连接实践现代IDE如VSCode、PyCharm、IntelliJ IDEA都提供了强大的远程开发功能其底层依赖SSH。在这些图形化工具中遇到问题往往需要到底层找原因。5.1 VSCode Remote-SSH 常见问题“过程试图写入的管道不存在”或连接反复断开根因这通常是VSCode Remote-SSH扩展使用的稳定连接机制与某些网络环境或服务器配置不兼容导致的。VSCode会通过SSH建立连接后启动一个守护进程来保持通信网络波动或服务器资源限制可能中断此进程。排查在VSCode的命令面板F1中执行Remote-SSH: Open Configuration File编辑你的SSH配置文件。尝试添加以下参数来优化连接Host myserver HostName ... ... ServerAliveInterval 30 ServerAliveCountMax 5 TCPKeepAlive yesServerAliveInterval 30表示客户端每30秒发送一个保活包ServerAliveCountMax 5表示连续5次无响应才断开。这能有效应对短暂网络中断。权限问题确保VSCode使用的SSH密钥通常是id_rsa或id_ed25519权限正确600并且对应的公钥已部署到服务器。连接成功但无法列出或打开远程文件夹这通常与远程服务器上你的用户权限有关。通过VSCode的终端检查你登录后的当前目录pwd以及目标目录的权限ls -la /path/to/folder。确保VSCode Remote-SSH扩展在远程服务器上成功安装了必要的服务组件。首次连接时它会在远程~/.vscode-server目录下安装后端需要网络畅通。如果失败可以尝试手动下载对应版本的安装包进行离线安装。5.2 Git客户端SSH配置Git通过SSH协议与仓库GitHub, GitLab, Gitee交互时同样遵循SSH规则。测试Git仓库连接ssh -T gitgithub.com成功会返回You‘ve successfully authenticated, but GitHub does not provide shell access.“Bad permissions”错误Git客户端对密钥文件的权限检查非常严格。如果你的私钥文件权限过于开放如644它会拒绝使用并报错bad permissions。解决方法是chmod 600 ~/.ssh/id_rsa chmod 700 ~/.ssh管理多个Git托管平台的密钥如果你有多个平台的账户需要为每个平台生成不同的密钥对并在~/.ssh/config中配置Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github IdentitiesOnly yes Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_rsa_gitee IdentitiesOnly yesIdentitiesOnly yes告诉SSH只使用配置文件指定的密钥不尝试其他默认密钥避免混淆。6. 安全加固与最佳实践总结围绕SSH连接建立信任的过程我们可以总结出一套安全且高效的最佳实践永远重视首次连接的指纹验证对于重要的服务器坚持手动比对指纹。这是抵御中间人攻击最有效的一环。禁用密码登录使用密钥对在服务器sshd_config中设置PasswordAuthentication no强制使用密钥认证。密钥的强度远高于任何复杂密码。为密钥对添加强密码短语生成密钥时ssh-keygen或之后ssh-keygen -p为私钥设置一个密码短语。这样即使私钥文件泄露攻击者也无法直接使用。使用ssh-agent管理密钥将添加了密码短语的私钥交给ssh-agent管理只需在会话开始时输入一次密码短语后续连接无需重复输入兼顾安全与便利。妥善管理known_hosts文件定期检查这个文件。对于不再需要连接的主机可以使用ssh-keygen -R进行清理。对于团队可以考虑使用集中化的、受控的known_hosts文件分发机制。升级到更安全的密钥算法在新环境中优先使用Ed25519算法生成密钥对ssh-keygen -t ed25519 -C your_emailexample.com。限制SSH监听端口和访问来源修改sshd默认端口如2222可以减少自动化攻击脚本的扫描。结合防火墙只允许特定的IP地址段访问SSH端口。使用Fail2ban等工具安装Fail2ban来监控SSH登录日志短时间内多次密码或密钥尝试失败则自动封禁对应IP地址有效抵御暴力破解。SSH连接的“The authenticity of host can‘t be established”提示是一个经典的安全与便利的权衡点。盲目跳过会引入风险而完全手动操作在自动化时代又显得低效。理解其背后的公钥基础设施原理根据不同的场景个人开发、团队协作、生产运维选择合适的策略——是手动验证、预置指纹还是使用StrictHostKeyCheckingaccept-new这样的折中选项正是一名系统工程师或开发者专业性的体现。每一次安全的连接建立都是对这套运行了数十年的、简洁而强大的信任协议的一次正确实践。

相关新闻

Python 办公自动化从入门到入土 | 07数据容器之元组

Python 办公自动化从入门到入土 | 07数据容器之元组

前言上一节我们学习了列表 list,列表可增删改,适合数据清洗、动态存储表格数据;但办公场景里存在一类不能修改、需要保证数据安全的数据,比如固定报表表头、客户身份证号、固定配置参数,这时候就要用到元组 tuple。元组…

2026/10/7 23:19:44 阅读更多 →
Diablo Edit2:暗黑破坏神2存档修改器完全指南

Diablo Edit2:暗黑破坏神2存档修改器完全指南

Diablo Edit2:暗黑破坏神2存档修改器完全指南 【免费下载链接】diablo_edit Diablo II Character editor. 项目地址: https://gitcode.com/gh_mirrors/di/diablo_edit 你是否曾经想过完全掌控暗黑破坏神2的游戏体验?是否想要自由调整角色属性、装…

2026/10/11 6:46:22 阅读更多 →
eBay信任机制解析:如何构建陌生人交易的安全基石

eBay信任机制解析:如何构建陌生人交易的安全基石

1. 为什么eBay能成为陌生人交易的信任基石2003年夏天,我在eBay上卖掉了人生中第一件二手物品——一台老式尼康胶片相机。当时我忐忑地把相机寄给素未谋面的买家,没想到三天后不仅收到了全款,还获得了一条热情洋溢的好评。这种陌生人之间的信任…

2026/10/8 22:49:51 阅读更多 →

最新新闻

HoRain云--Python深度学习实战:PyTorch基础与项目

HoRain云--Python深度学习实战:PyTorch基础与项目

PyTorch是深度学习研究和应用的主流框架。本文从张量操作到神经网络训练,带你入门PyTorch。一、张量基础python复制下载import torcha torch.tensor([1, 2, 3]) b torch.randn(3, 4) c torch.zeros(2, 3) d torch.ones(2, 3)print(a 1) print(torch.matmul(b, …

2026/10/11 7:29:50 阅读更多 →
C++重构实战指南:从时机判断、命名规范到STL容器与智能指针

C++重构实战指南:从时机判断、命名规范到STL容器与智能指针

说到 C 代码重构,有点年头的开发者第一反应往往是:能跑就行,改它干嘛。这个想法曾经也是我的,直到我在一个接近上万行的源文件里改一个简单的打印逻辑,改了半小时还不敢提交——那会儿才意识到,重构这件事不…

2026/10/11 7:29:50 阅读更多 →
Android WebSocket长连接实战:OkHttp心跳与断线重连指南

Android WebSocket长连接实战:OkHttp心跳与断线重连指南

简介:这是一套Android端WebSocket通信的示例工程,对应博客《Android端WebSocket基本用法》的配套源码,面向需要实现长连接、实时数据交互的移动端开发者,重点演示了添加依赖、声明网络权限、自定义WebSocketClient客户端类、建立连…

2026/10/11 7:29:50 阅读更多 →
一维字符数组完全指南:C语言字符串函数与安全操作

一维字符数组完全指南:C语言字符串函数与安全操作

在C语言开发里,一维字符数组大概是接触最频繁、也最容易阴沟翻船的语法点。一个命令行参数、一段日志拼接、一个网络收发的缓冲区,背后都是它。我最早用char name[20]存名字时,直接把用户输入塞进去,结果printf出一串乱码&#xf…

2026/10/11 7:29:50 阅读更多 →
子数组按位或结果计数:利用位运算单调性压缩状态

子数组按位或结果计数:利用位运算单调性压缩状态

1. 题目拆解:从“枚举所有子数组”到“集合压缩”先把这个题目的原型摆出来:给定一个非负整数数组,返回所有子数组的按位或结果中,不同结果的数量。很多朋友第一眼看到“所有子数组”就兴奋了,枚举左端点、右端点、再做…

2026/10/11 7:29:50 阅读更多 →
噪声频谱分析与仿真:从白噪声到量化噪声的工程实践

噪声频谱分析与仿真:从白噪声到量化噪声的工程实践

1. 噪声是信号处理的“底色”——为什么把噪声单独拿出来讲傅里叶变换与频谱分析这个系列,前面几讲我一直在和确定性信号打交道:正弦波、矩形脉冲、调幅信号、扫频信号。这些信号有个共同点——波形能写出确定的数学表达式,频谱图算出来就摆在…

2026/10/11 7:28:50 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →