# 2.1 用户名和密码

当我们登录 Linux 系统时，需要输入用户名和密码。虽然我们输入的是用户名，但其实 Linux 主机并不直接认识我们的用户名，它只认识 ID，用户名只是为了让人们更容易记住而已。

> <mark style="color:purple;">UID</mark>，User ID，用户 ID\ <mark style="color:purple;">GID</mark>，Group ID，用户组 ID

今天，我们就来了解下 Linux 是如何辨认每个用户的。

## 1. 流程

当我们通过 `ssh` 命令输入用户名和密码的时候，Linux 系统会做以下工作：

1. 先查找 `/etc/passwd` 里是否有输入的用户名
   * 如果没有，则退出
   * 如果有，那就将该用户名对应的 UID 和 GID（在 `/etc/group` 中）取出来，同时也会取出该用户的 home 目录和 shell 设置
2. 核对密码表 `/etc/shadow`
   * Linux 会根据用户名，从 `/etc/shadow` 里找出对应的密码进行校验
   * 如果校验成功，则会进入 shell 管理的阶段

上面提到了两个和用户账号相关的重要文件：

* `/etc/passwd`：管理着用户名、UID 和 GID
* `/etc/shadow`：专门管理着密码

## 2. `/etc/passwd`

### 2.1 文件内容

比如，在阿里云上，运行命令 `cat /etc/passwd`，会显示如下内容：

```shell
## UID = 0, 系统管理员
root:x:0:0:root:/root:/bin/bash

## UID ∈ [1,999], 系统账号
##   由于在系统上启动的网络服务或后台服务，希望使用较小的权限去运行
##      所以，不希望使用 root 的身份来执行这些服务
##      因此，就为运行中的程序提供了这些系统账号
##   这些系统账号通常是不可登录的（即没法使用 bash 或其它 shell 来登录系统）
##      所以才会有 /sbin/nologin 这个特殊的 shell 存在
# 1~200 由 Linux 自行建立的系统账号
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/false
nscd:x:28:28:NSCD Daemon:/:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
# 201~999 若用户有系统账号的需求时，可以使用的账号 UID
dockerroot:x:996:993:Docker User:/var/lib/docker:/sbin/nologin
nginx:x:997:995:Nginx web server:/var/lib/nginx:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
polkitd:x:999:997:User for polkitd:/:/sbin/nologin

## UID >= 1000, 一般用户
shareread:x:1000:1000::/home/shareread:/bin/bash
```

### 2.2 文件格式

文件 `/etc/passwd` 里的每一行就代表一个账号，有几行就代表系统中有几个账号。

每行以冒号`:`为分隔符，分为 7 列，如下：

<table><thead><tr><th width="78.33333333333331">列</th><th width="155">含义</th><th>说明</th></tr></thead><tbody><tr><td>1</td><td>账号名称</td><td></td></tr><tr><td>2</td><td>密码</td><td>值都为 x</td></tr><tr><td>3</td><td>UID</td><td>0 是系统管理员<br>1~999 是系统账号，通常是不可登录的<br>1000+ 是可登录的账号，给一般用户用的</td></tr><tr><td>4</td><td>GID</td><td>和 <code>/etc/group</code> 有关，用来规范组名的</td></tr><tr><td>5</td><td>用户信息说明</td><td>一般没啥用<br>但是如果提供了 figure 功能，这个字段可以提供很多信息</td></tr><tr><td>6</td><td>home 目录</td><td>当用户登录成功之后，就会自动进入到 home 目录里</td></tr><tr><td>7</td><td>shell</td><td>当用户登录系统之后，就会获取一个 shell 来和<br>系统的内核进行沟通，以完成用户的操作任务。<br>eg.<br>- <code>/bin/sync</code><br>- <code>/sbin/shutdown</code><br>- <code>/sbin/halt</code><br>- <code>/bin/false</code><br>- <code>/sbin/nologin</code> 为不可登录的系统账号准备的<br>- <code>/bin/bash</code> 可正常登录的用户</td></tr></tbody></table>

如果将一般用户的 UID 改成 0，那他也就具有 root 的权限了。不过，非常不建议这么做。

### 2.3 文件权限

很多程序的运行都和权限有关，而权限又和 UID 和 GID 相关，所以各个程序都要读取 `/etc/passwd` 来了解不同账号的权限，所以它的权限需要设置成 `-rw -r--r--`。

```shell
# ls -l /etc/passwd
-rw-r--r-- 1 root root 1236 Dec 18  2018 /etc/passwd
```

早期的 Unix 系统，密码是存在 `/etc/passwd` 文件里的，虽然也有加密，但因为这个文件的特性是所有的程序都能读取，这就导致密码容易被窃取进而被暴力破解出来，所以后来就将密码移到了 `/etc/shadow` 文件里，该文件的权限是 `-rw-------` 或者 `----------`，即只有 root 才能读写。

```shell
# ls -l /etc/shadow
---------- 1 root root 862 Dec 18  2018 /etc/shadow
```

## 3. `/etc/shadow`

### 3.1 文件内容

比如，在阿里云上，运行命令 `cat /etc/shadow`，会显示如下内容：

```shell
# 系统账号的
bin:*:17110:0:99999:7:::
daemon:*:17110:0:99999:7:::
adm:*:17110:0:99999:7:::
lp:*:17110:0:99999:7:::
sync:*:17110:0:99999:7:::
shutdown:*:17110:0:99999:7:::
halt:*:17110:0:99999:7:::
mail:*:17110:0:99999:7:::
operator:*:17110:0:99999:7:::
games:*:17110:0:99999:7:::
ftp:*:17110:0:99999:7:::
mysql:!!:17624::::::
nscd:!!:17454::::::
ntp:!!:17454::::::
apache:!!:17883::::::
tcpdump:!!:17454::::::
sshd:!!:17454::::::
dbus:!!:17454::::::
postfix:!!:17454::::::
nobody:*:17110:0:99999:7:::
systemd-network:!!:17454::::::

dockerroot:!!:17883::::::
nginx:!!:17624::::::
chrony:!!:17454::::::
polkitd:!!:17454::::::

# root 用户的
root:$6$.PENTCpQ$L55HQMW5tf4Yj4x5Fvls95MyeWI1J5Rp1xbtbsXw0EQhW/X0QBuO1vwGLQ3OqJxdm6py40IJvvPG8yYZQx0Pw0:17623:0:99999:7:::

# 一般用户的
shareread:$6$RqqEr.T/$Ld84B.mtqKmrXdL3KUgLKiujpo.udClGH1JlKr4Nps5R6ZQa/X5a2voE65Qorqmcbm6rl9tkaYo1c3wyEH3rk/:17686:0:99999:7:::
```

### 3.2 文件格式

每行以冒号`:`为分隔符，分为 9 列，如下：

<table><thead><tr><th width="74.33333333333331">列</th><th width="261">含义</th><th>说明</th></tr></thead><tbody><tr><td>1</td><td>账号名称</td><td></td></tr><tr><td>2</td><td>密码</td><td>是经过编码的密码（摘要）</td></tr><tr><td>3</td><td>最近修改密码的日期</td><td>整数，是以1970年1月1日作为1而累加的</td></tr><tr><td>4</td><td>密码不可被修改的天数</td><td>相对于第3个字段<br>若是0则表示密码可以随时修改</td></tr><tr><td>5</td><td>密码需要重新修改的天数</td><td>相对于第3个字段<br>如果是99999(即273年)则表示不强制</td></tr><tr><td>6</td><td>密码需要修改期限前的警告天数</td><td>相对于第5个字段</td></tr><tr><td>7</td><td>密码过期后的账号宽限时间</td><td>相对于第5个字段<br><br>虽然密码过期但是该账号还是能执行其它任务的<br>不过如果密码过期了，当我们登录系统时，<br>系统会强制要求必须要重新设置密码之后<br>才能继续使用，这就是密码过期特性<br><br>如果在密码过去的n天后，用户还是没有登录<br>更改密码，那么这个账号的密码就会失效</td></tr><tr><td>8</td><td>账号失效日期</td><td>账号在此日期之后，将无法再使用</td></tr><tr><td>9</td><td>保留字段</td><td></td></tr></tbody></table>

要想知道 shadow 使用的加密机制，可以使用 `authconfig` 命令查询。如下：

```shell
# authconfig --test | grep hashing
password hashing algorithm is sha512
```

### 3.3 密码忘了，怎么办？

如果是一般用户，可以让系统管理员帮忙。他可以直接重置我们的密码而不需要知道旧密码，即以 root 的身份使用 `passwd` 命令。

如果是 root 用户，那可以使用各种可行的方法进入 Linux 后再去修改。比如重新启动系统之后进入单人维护模式，系统会主动给予 root 权限的 bash 接口，此时再以 `passwd` 命令修改密码即可。或者以 Live CD 启动后挂载根目录去修改 `/etc/shadow`，将里面 root 的密码字段清空，再重新启动之后，root 不用密码即可登录，登录后再用 `passwd` 命令设置 root 的密码即可。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://anjia1.gitbook.io/cs/linux/account/username-and-password.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
