SSH 与密钥
这一课你将能做到
- 生成 ed25519 密钥对并部署到服务器(
ssh-copy-id 或手动 authorized_keys),实现免密登录,并说清私钥/公钥各自的权限要求
- 用
~/.ssh/config 给服务器起别名(ssh prod 一键登录),指定用户、端口、私钥
- 用 scp/rsync 向服务器传产物(JAR、前端 dist),并完成「禁用 root 密码登录」的安全加固
为什么要学
SSH 是你与生产服务器之间唯一的门。这扇门如果只有密码,等于每天在全球公网上被人试锁——云厂商日志里每天有成千上万次针对 22 端口的暴力破解。密钥登录(公钥锁、私钥钥匙)让攻击者没有钥匙永远进不来,还顺便让你免输密码、让 CI(GitHub Actions)能自动部署。
本课的产出直接决定部署课的顺畅度:ssh prod 秒连、scp backend.jar prod: 秒传——这是后端工程师每天摸的路径。
先建立图像
密钥登录的原理(非对称加密)
你本机 服务器
┌──────────────────┐ ┌─────────────────────────┐
│ id_ed25519 │ 私钥=钥匙 │ ~/.ssh/authorized_keys │
│ (永不离开本机) │ │ (门上贴的锁,公钥列表) │
│ │ 握手验证 │ │
│ id_ed25519.pub ──┼──公钥=锁──拷贝──►│ www@server 可登录 │
└──────────────────┘ └─────────────────────────┘
登录时服务器用公钥出一道「只有对应私钥能解的题」,你本机用私钥作答。私钥全程不出本机,所以没有密码在网络里飞,抓包也没用。
权限是 SSH 安全模型的一半
~/.ssh/ 必须是 700
~/.ssh/id_ed25519 必须是 600 ← 宽了 ssh 直接拒绝使用:UNPROTECTED PRIVATE KEY
~/.ssh/authorized_keys 必须是 600(且属主=登录用户)
跟着做
步骤 1:本机生成密钥对
在本机终端(Windows 用 PowerShell,自带 OpenSSH;macOS/Linux 用 Terminal):
# ed25519:现代算法,密钥短、快、安全(取代 rsa 2048)
ssh-keygen -t ed25519 -C "leebf@coderoadmap"
# 三个提示都可直接回车:
# 保存路径:默认 ~/.ssh/id_ed25519
# passphrase:可留空(本机受信环境);留空=免密一键连
ls ~/.ssh/
# id_ed25519 私钥(谁拿到谁能登你的服务器,绝不外传)
# id_ed25519.pub 公钥(贴到服务器上的锁)
cat ~/.ssh/id_ed25519.pub # 一行内容:ssh-ed25519 AAAA... leebf@coderoadmap
老系统需要 rsa 时的兼容写法:ssh-keygen -t rsa -b 4096(云厂商老镜像、某些跳板机可能只认 rsa)。
步骤 2:把公钥装到服务器上
方式 A(推荐,一条命令):
ssh-copy-id deploy@你的服务器IP
# 输最后一次 deploy 的密码,之后就再也不用了
方式 B(手动,理解文件本质;Windows 无 ssh-copy-id 时用):
# 本机看公钥内容并复制:
cat ~/.ssh/id_ed25519.pub
# 服务器上(先用密码 ssh 登一次):
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA...你的公钥... leebf@coderoadmap" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
验证:新开一个终端 ssh deploy@服务器IP——不再问密码即成功。
步骤 3:~/.ssh/config 别名——从长命令到两个字
本机编辑(没有就新建):vim ~/.ssh/config(Windows 在 C:\Users\你\.ssh\config):
Host prod
HostName 你的服务器IP
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60 # 每 60s 发心跳防断连
Host test
HostName 192.168.1.100
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
chmod 600 ~/.ssh/config # config 过宽也会被拒
ssh prod # 等价于 ssh -i ~/.ssh/id_ed25519 deploy@IP
从今天起,所有连接、传输命令都用别名:ssh prod、scp x prod:~/——这就是效率。
步骤 4:scp 与 rsync——传文件的两种刀
# scp:简单直接,适合单文件/小目录(在【本机】执行)
scp backend.jar prod:/www/wwwroot/coderoadmap/ # 本机 → 服务器
scp prod:/var/log/nginx/error.log ./ # 服务器 → 本机
scp -r dist prod:/tmp/dist # -r 传目录
# rsync:增量同步,适合目录/大文件/重复发布
rsync -avz dist/ prod:/www/wwwroot/coderoadmap/platform/
# -a 保留权限时间戳 -v 过程可见 -z 压缩传输
# 第二次执行只传差异,快得多 —— CI 部署前端产物就用它
# 排除某些文件再同步:
rsync -avz --exclude 'node_modules' --exclude '.env' dist/ prod:/www/wwwroot/coderoadmap/platform/
选择口诀:单文件 scp,目录与重复同步 rsync。
步骤 5:安全加固——关掉密码与 root 登录
密钥能登录之后,密码通道就该关了(先确认至少一个密钥会话能通,再动配置):
ssh prod # 确认免密可用!
# 然后 sudo vim /etc/ssh/sshd_config,确认/修改三行:
# PasswordAuthentication no # 禁密码登录
# PermitRootLogin prohibit-password # root 只许密钥(更狠:no)
# PubkeyAuthentication yes
# Ubuntu 22.10+ 还要检查 /etc/ssh/sshd_config.d/*.conf 里有没有覆盖项(50-cloud-init.conf 常见)
sudo sshd -t # 语法检查(改错不重启才安全)
sudo systemctl restart ssh # Ubuntu 服务名叫 ssh(CentOS 叫 sshd)
验证顺序是保命的关键:保持当前会话不退出 → 新开窗口 ssh prod 确认密钥能进 → 再用 ssh -o PubkeyAuthentication=no deploy@IP 验证密码确实被拒 → 一切正常再关旧会话。配错把自己锁在外面,就只能走云控制台 VNC 救援了。
步骤 6:常用的 SSH 周边命令
# 远程执行单条命令(不进交互 shell,脚本/CI 必备):
ssh prod "sudo systemctl restart coderoadmap-backend"
ssh prod "df -h && free -m"
# 远端多行(引号包裹整段):
ssh prod "cd /www/wwwroot/coderoadmap && tail -20 logs/backend.log"
# 查看/调试连接问题(-v 逐级加到 -vvv):
ssh -v prod 2>&1 | tail -20 # 卡在哪一步一目了然
# 断线重连不丢会话(可选神器):
sudo apt install -y mosh # UDP 20100-20100+,漫游网络不冻屏
讲清楚要点
- 私钥=身份本体:它加密存于本机,权限 600,不进聊天窗、不进 Git、不进网盘。泄露的处置:把对应公钥从所有服务器 authorized_keys 删掉 + 换新密钥对。
- authorized_keys 是「门上的锁列表」:一行一把锁,按用户挂在各自家目录。追加=发钥匙,删行=收钥匙。
wc -l ~/.ssh/authorized_keys 数一数你有几把锁——很多被入侵的机器就是多了陌生行。
- SSH 权限检查是双向的:本机私钥 600/700,服务器 .ssh 700 + authorized_keys 600 且属主必须是登录用户。ssh 对权限的容忍度为零——这是特性不是 bug(宽权限=可能被篡改)。
- sshd_config 生效链:改文件 →
sshd -t 校验 → restart。Ubuntu 22.10+ 的 sshd_config.d/*.conf 会覆盖主文件(Include 在前),排障时两个地方都要看。
- scp 是「一次性搬运」,rsync 是「对齐两端」:rsync 校验大小与修改时间、只传差异、支持 --delete 做镜像、断了能续——发布产物、备份数据库目录(配合快照)都是 rsync 的主场。
- CI 部署的 SSH 就是本课知识的自动版:GitHub Actions 里把私钥放 Secrets,配
known_hosts(避免交互确认指纹),然后 ssh -i key deploy@host "systemctl restart ..."。 fingerprints 首次连接问 yes 的那一步,本质是防中间人。
你可能会懵的地方
- id_rsa 和 id_ed25519 都在 ~/.ssh 里,连接时用哪个?
默认按 config 里的 IdentityFile 指定;没指定时 ssh 依次尝试 ~/.ssh/id_ed25519、id_rsa 等。多把钥匙并存时(公司 GitLab 一把、服务器一把),一定要用 config 分 Host 指定,否则「时好时坏」。
- passphrase 留空安全吗?
私钥文件本身在本机被偷才需要 passphrase 保护。笔记本加密盘 + 不共享账号的环境下留空可接受;更稳的做法是设 passphrase 并用 ssh-agent 缓存(每次开机输一次)。原则:私钥离开本机的可能性越大,越要设 passphrase。
- known_hosts 是什么?提示 host key changed 怎么办?
known_hosts 记录你连过的服务器的指纹,防 DNS 劫持/中间人。重装系统后指纹变了会报 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED——确认是重装后:
ssh-keygen -R 服务器IP 清掉旧记录再连。如果不是你重装的,立即停下排查。
- 多台服务器要发同一把钥匙吗?
同一把公钥贴多台服务器可以(管理简单),但私钥一丢全军覆没。分级实践:个人日常一把;CI/自动化单独一把(权限白名单只够 restart 服务);高敏感机器独立钥匙。
- SSH 端口改成 2222 能防扫描吗?
能减少噪音扫描(机器人只扫 22),但不是安全边界(端口扫描一样能发现)。真正的防线是密钥 + 防火墙白名单(下一课)+ fail2ban(自动封爆破 IP)。改端口属于「锦上添花」,本课的密钥加固才是「雪中送炭」。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 把私钥提交进 Git 仓库 | 等于把家门钥匙发到公开场合 | 私钥只存本机/Secrets;.gitignore 拦住 |
| 私钥权限 644 还硬连 | ssh 拒用:UNPROTECTED PRIVATE KEY | chmod 600 私钥、700 .ssh |
| authorized_keys 属主是 root | 密钥登录静默失败(PermitRootLogin 无关) | chown 登录用户: ~/.ssh -R |
| 关密码登录前没验证密钥 | 配错全员锁死 | 先确认密钥会话可进,再关密码 |
| scp 传目录忘 -r | 报 not a regular file | scp -r 或改用 rsync |
| rsync 尾斜杠乱写 | 多一层目录或覆盖错位 | 明确:src/ 同步内容,src 连目录一起拷 |
| 重装系统后直接 -o StrictHostKeyChecking=no | 绕过中间人检查成习惯 | ssh-keygen -R IP 后正常重连 |
卡点急救
- Permission denied (publickey):五连查——① 服务器上
cat ~/.ssh/authorized_keys 是否真有你的公钥;② .ssh 700、authorized_keys 600、属主对(ls -la ~/.ssh);③ 本机 ssh -v prod 看它尝试了哪把钥匙(Offering public key...);④ config 的 IdentityFile 是否指对;⑤ 服务器 /var/log/auth.log | tail 看拒绝原因(Ubuntu)。
- 配完 sshd_config 后所有连接被拒:走云控制台 VNC 登录恢复——改回
PasswordAuthentication yes 或修语法(sshd -t 会告诉你第几行错)。教训:改 sshd 前永远留一个已连接的会话不退出。
- 连接卡住超时(Connection timed out):网络层问题——安全组 22 没放行、服务器没开机、IP 写错、本机代理干扰。与密钥无关,密钥问题报的是 permission denied。
- Windows 找不到 ssh-copy-id:用方式 B 手动追加公钥;或 PowerShell 一行流:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh deploy@IP "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"。
- 每次都提示输 passphrase:设了 passphrase 又没 agent。Linux/macOS:
eval $(ssh-agent) + ssh-add ~/.ssh/id_ed25519;Windows:ssh-agent 服务设为自动启动后 ssh-add。
面试常问
-
SSH 密钥登录的原理?比密码好在哪?
非对称加密:服务器持公钥发起挑战,客户端用私钥应答,私钥全程不传输。优势:① 不可暴力破解(2048 位以上熵 vs 人类密码);② 无密码在网络飞,抗嗅探;③ 天然支持免密与自动化(CI);④ 随时可吊销(删 authorized_keys 行)。密码可另加二次因素,密钥+禁密码是最小安全闭环。
-
authorized_keys 需要什么权限?为什么这么严格?
.ssh 700、authorized_keys 600、属主必须是登录用户。sshd 设计上:若 authorized_keys 可能被他人篡改(组/他人可写),信任链断裂,直接拒绝使用该文件。这不是玄学,是把「文件系统完整性」作为认证前提。
-
scp 和 rsync 的区别?部署时怎么选?
scp 基于 SSH 的一次性整文件拷贝,简单可靠;rsync 增量算法(校验大小+修改时间,可校验块级差异)只传变化部分,支持 --delete 镜像、断点续传、--exclude 排除。部署选择:单个 JAR 用 scp;前端构建产物目录、日志备份用 rsync -avz。
-
如何加固一台新服务器的 SSH?
标准清单:① 密钥登录配置好并验证;② PasswordAuthentication no、PermitRootLogin no(或 prohibit-password);③ 改用低权用户+sudo(用户课);④ 防火墙/安全组只放 22(或改端口+白名单来源 IP);⑤ fail2ban 封爆破;⑥ last/auth.log 定期审计登录记录。
-
GitHub Actions 里怎么安全地 SSH 到服务器部署?
专用部署密钥对:公钥上服务器(对应 deploy 用户,sudoers 只许 restart 本服务),私钥放 GitHub Secrets;workflow 里写 known_hosts(ssh-keyscan 预先取指纹)避免交互;然后 ssh -i 私钥 deploy@host 执行上传(rsync/scp)与重启命令。私钥不过代码、不过日志。
自测三问
- 私钥、公钥分别放在哪?各承担什么角色?本机与服务器的权限要求分别是什么?
- 新装的服务器,写出「生成密钥 → 部署公钥 → 配别名 → 关闭密码登录」的完整命令序列与验证点。
rsync -avz --exclude 'node_modules' dist/ prod:/www/wwwroot/app/ 中尾斜杠去掉会怎样?
小结一句话
SSH 密钥登录 = 本机私钥(600)作答 + 服务器公钥(authorized_keys,600)出题,配好 ~/.ssh/config 别名后「ssh prod」两字直达;密钥通了就关密码——这是生产服务器的第一道也是最硬的一道门。
延伸