硬核观察 #1122 微软将取消 Windows 中的第三方打印机驱动程序
微软将取消 Windows 中的第三方打印机驱动程序微软计划于 2027 年取消 Windows 中的第三方打印机驱动程序。这样做的部分原因是，Mopria 联盟为打印机提供了通用标准。该联盟由多家打印机厂商于 2013 年成立。从 Windows 10 21H2 开始，微软就在其操作系统中加入了对 Mopria 的支持，借助微软 IPP 类驱动程序，可支持通过网络或 USB 连接的设备。微软表示：“这样一来，打印设备制造商就无需提供自己的安装程序、驱动程序、实用程序等”。消息来源：The Register老王点评：这种通用外设标准化是一件好事。除了一些非常老旧的打印机，应该没什么影响。获得艺术大奖的著名 AI 绘画未能获得版权还记得那副获得 2022 年科罗拉多州博览会年度美术竞赛的《 空间歌剧院 Théâtre d'Opéra Spatial 》的 AI 绘画吗？它是艺术家使用 MidJourney AI 生成的，以其复古的笔触细腻的色彩，意外地赢得了艺术创作比赛的第一名。这件事引发了公众对 AI 绘画和 MidJourney 的极大兴趣。不出意外，美国版权局也拒绝了对该作品的版权授予，“因为它不是人类创作的产物……其中包含的人工智能创作素材超过了最低限度”。使用 MidJourney 创作这幅画的 Jason Allen 告诉版权局，他使用 Midjourney “输入了大量修改和文本提示，至少 624 次，才得到了图片的初始版本”，并用 Photoshop 对其进行了修改。消息来源：路透社老王点评：不授予版权有不授予的道理，我认为 AI 制品应该有不同于现有版权范围的新的版权认定。微软决定由它来选择你使用的安全登录方式微软本周将推出多因素身份验证（MFA）系统首选验证，它将为登录的个人选择最安全的方法，如果该方法不可用，则提供替代方法。Azure 活动目录会查看用户账户注册的身份验证方法，并选择最安全的途径。首选方法列表从临时访问通行证开始，然后依次是基于证书的身份验证、FIDO2 安全密钥、微软认证器的推送通知和基于时间的一次性密码（OTP），最后是手机。微软的总体目标是最终放弃使用用户名和密码作为身份验证方法，转而使用生物识别等其他方法。消息来源：The Register老王点评：总之，就是大家都习惯的密码是最不可靠的安全方式。回音果然，Bcachefs 没有进入 Linux 6.6，并按照 Linus Torvalds 的意见，先行 并入 了 linux-next 树，它有望进入下一个版本的 Linux 内核。
随着在设备上使用各种程序的用户的需求变得越来越复杂，程序本身也需要跟上用户的现实需求和未来期望。我发现我每天需要的东西是一个在网页浏览器保持登录多个账号的简单方法。我 可以 根据需要对我的每个账号进行登录和注销操作，但在短时间内切换多个账号时，这变得非常乏味。最初，我使用谷歌浏览器，它拥有管理多个帐户的能力。这很有效，但管理起来略显繁琐，而且明明只需 一个 谷歌账号就能搞定的事却要创建一个新的谷歌账号来完成，这显得有点儿笨拙。这是我转而使用 Firefox 多账户容器 Multi-Account Containers 功能的原因。它不仅比我在谷歌 Chrome 浏览器上的设置灵活得多，而且我还使用了由我的浏览器开发者自己创建的工具，从而在整体上获得了更流畅和更简单的体验。Firefox 中的多帐户容器是什么？如果你想将数字生活的各个部分彼此分开，多账户容器也非常有效。通过使用容器，你在一个容器中的浏览活动不会与其他容器共享。这种隔离意味着你可以在不同容器中登录同一网站上的两个不同帐户。你的登录会话、网站偏好和跟踪数据将被限制在你使用某个网站的容器中。它还有什么其他优势？想象一下，你在亚马逊或其他电子商务网站上购物。你浏览了一些商品，但没有购买任何东西。现在，如果你浏览网络，你会看到与你浏览的产品相关的广告。尽管有广告拦截器，一些网站仍会显示广告。使用容器，你可以将你的购物网站与其他网站分开。（LCTT 校注：甚至根据你的浏览历史，你再次访问同一网站时看到的价格可能会被“宰熟”——反复浏览代表了你的购买倾向。）再给大家分享一个例子。Firefox 默认提供一个 Facebook 容器。默认情况下，此容器包括 Facebook、Messenger 和 Instagram 网站。这意味着当你打开这三个网站中的任何一个时，它们都只会在“Facebook 容器”中打开。因此，Facebook 将无法跟踪你在其他网站上的活动。这是 很少有人知道或使用的 Firefox 功能 之一。使用多账户容器安装 Firefox 多账户容器是一个非常简单的过程，只需点击几下。首先，前往 Firefox 附加组件网站上的 扩展程序页面。之后你唯一需要做的就是单击 “添加到 Firefox” 按钮。安装完成！现在我们可以实际使用一下这个新的扩展。可能你还没有注意到，你的搜索栏右侧应该会出现一个新图标：这是你将用于与 Firefox 多帐户容器交互的图标。如果你单击该图标，你将看到一个小菜单：让我们使用这个扩展尝试一些例子，看看多账户容器是如何工作的。设置容器首先，我们需要生成一个容器。点击多账户容器菜单中的 ` 管理容器 Manage Containers ，然后点击 新建容器 New Container 。接着输入新容器的名称，选择颜色和图标。然后，点击 “OK” 保存新容器。大功告成！我们现在可以返回主菜单在新容器中打开一个新选项卡：你还会注意到新选项卡有一些样式，表示它正在容器内运行：观察容器工作现在让我们看看容器在使用时实际做了什么。我们将在一个普通的浏览器选项卡中访问 Linode 管理网站，我已经在其中登录：现在让我们尝试在 Firefox 容器中打开相同的页面，此时我被重定向到 Linode 登录页面：为什么我被重定向了？因为现在我没有登录。这就是 Firefox 容器的乐趣之一：在一个浏览器会话中登录后，再进入一个容器，就好像你以前从未访问过该站点一样。如果你在容器内完成对某个网站的登录，你从容器中访问该网站时将会保持登录状态。你还可以使用此功能从容器内登录网站，从而使该网站的所有数据与你的正常浏览器数据相隔开。注意：你的浏览器历史记录本身之类的内容仍会暴露给你的正常浏览器会话。容器功能只是提供了一种方法来分离本文中提到的登录帐户等内容。总结对于那些在乎自己的隐私，或者只是想真正尝试对其系统的安全性进行严格控制的人来说，多账户容器被证明是一个很棒的功能。例如，你可以在容器内登录你的谷歌帐户，谷歌永远不会知悉你在容器外的信息。 对拥有多个帐户的人来说，此扩展程序是一个不错的选择。有了它无需为你要使用的每样东西创建单独的浏览器帐户。好了，这就是 Firefox 的多帐户容器的基本知识。需要任何帮助，或者只是有点问题？请随时在评论区指出。via: https://itsfoss.com/firefox-containers/作者：Hunter Wittenborn 选题：lujun9972 译者：hanszhao80 校对：校对者ID本文由 LCTT 原创编译，Linux中国 荣誉推出
现在很多高端笔记本都配备了指纹识别器。Windows 和 macOS 支持指纹登录已经有一段时间了。在桌面 Linux 中，对指纹登录的支持更多需要极客的调整，但 GNOME 和 KDE 已经开始通过系统设置来支持它。这意味着在新的 Linux 发行版上，你可以轻松使用指纹识别。在这里我将在 Ubuntu 中启用指纹登录，但你也可以在其他运行 GNOME 3.38 的发行版上使用这些步骤。前提条件当然，这是显而易见的。你的电脑必须有一个指纹识别器。这个方法适用于任何运行 GNOME 3.38 或更高版本的 Linux 发行版。如果你不确定，你可以检查你使用的桌面环境版本。KDE 5.21 也有一个指纹管理器。当然，截图看起来会有所不同。在 Ubuntu 和其他 Linux 发行版中添加指纹登录功能进入 “设置”，然后点击左边栏的 “用户”。你应该可以看到系统中所有的用户账号。你会看到几个选项，包括 “指纹登录”。点击启用这里的指纹登录选项。它将立即要求你扫描一个新的指纹。当你点击 “+” 号来添加指纹时，它会提供一些预定义的选项，这样你就可以很容易地识别出它是哪根手指或拇指。当然，你可以点击右手食指但扫描左手拇指，不过我看不出你有什么好的理由要这么做。在添加指纹时，请按照指示旋转你的手指或拇指。系统登记了整个手指后，就会给你一个绿色的信号，表示已经添加了指纹。如果你想马上测试一下，在 Ubuntu 中按 Super+L 快捷键锁定屏幕，然后使用指纹进行登录。在 Ubuntu 上使用指纹登录的经验指纹登录顾名思义就是使用你的指纹来登录系统。就是这样。当要求对需要 sudo 访问的程序进行认证时，你不能使用手指。它不能代替你的密码。还有一件事。指纹登录可以让你登录，但当系统要求输入 sudo 密码时，你不能用手指。Ubuntu 中的 钥匙环 也仍然是锁定的。另一件烦人的事情是因为 GNOME 的 GDM 登录界面。当你登录时，你必须先点击你的账户才能进入密码界面。你在这可以使用手指。如果能省去先点击用户帐户 ID 的麻烦就更好了。我还注意到，指纹识别没有 Windows 中那么流畅和快速。不过，它可以使用。如果你对 Linux 上的指纹登录有些失望，你可以禁用它。让我在下一节告诉你步骤。禁用指纹登录禁用指纹登录和最初启用指纹登录差不多。进入 “设置→用户”，然后点击指纹登录选项。它会显示一个有添加更多指纹或删除现有指纹的页面。你需要删除现有的指纹。指纹登录确实有一些好处，特别是对于我这种懒人来说。我不用每次锁屏时输入密码，我也对这种有限的使用感到满意。用 PAM 启用指纹解锁 sudo 应该不是完全不可能。我记得我 在 Ubuntu 中设置脸部解锁时，也可以用于 sudo。看看以后的版本是否会增加这个功能吧。你有带指纹识别器的笔记本吗？你是否经常使用它，或者它只是你不关心的东西之一？via: https://itsfoss.com/fingerprint-login-ubuntu/作者：Abhishek Prakash 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
通过询问-响应身份认证提高桌面登录安全
介绍今天，Fedora 提供了多种方式来提高我们账户的身份认证的安全性。当然，它有我们熟悉的用户名密码登录方式，它也同样提供了其他的身份认证选项，比如生物识别、指纹、智能卡、一次性密码，甚至是 询问-响应 challenge-response 身份认证。每种认证方式都有明确的优缺点。这点本身就可以成为一篇相当冗长的文章的主题。Fedora 杂志之前就已经介绍过了这其中的一些选项：在 Fedora 中使用 YubiKey4Fedora 28：在 OpenSSH 中更好的支持智能卡在现在的 Fedora 版本中，最安全的方法之一就是离线硬件询问-响应。它也同样是最容易部署的方法之一。下面是具体方法。询问-响应认证从技术上来讲，当你输入密码的时候，你就正在响应用户名询问。离线的询问、响应包含了这些部分：首先是需要你的用户名，接下来，Fedora 会要你提供一个加密的物理硬件的令牌。令牌会把另一个其存储的加密密钥通过 可插入式身份认证 Pluggable Authentication Module 模块（PAM）框架来响应询问。最后，Fedora 才会提示你输入密码。这可以防止其他人仅仅使用了找到的硬件令牌，或是只使用了账户名密码而没有正确的加密密钥。这意味着除了你的账户名密码之外，你必须事先在你的操作系统中注册了一个或多个加密硬件令牌。你必须保证你的物理硬件令牌能够匹配你的用户名。一些询问-响应的方法，比如一次性密码（OTP），在硬件令牌上获取加密的代码密钥，然后将这个密钥通过网络传输到远程身份认证服务器。然后这个服务器会告诉 Fedora 的 PAM 框架，这是否是该用户的一个有效令牌。如果身份认证服务器在本地网络上，这个方法非常好。但它的缺点是如果网络连接断开或是你在没有网的远程端工作。你会被锁在系统之外，直到你能通过网络连接到身份认证服务器。有时候，生产环境会采用通过 Yubikey 使用一次性密码（OTP）的设置，然而，在家庭或个人的系统上，你可能更喜欢询问-响应设置。一切都是本地的，这种方法不需要通过远程网络调用。下面这些过程适用于 Fedora 27、28 和 29.准备硬件令牌密钥首先，你需要一个安全的硬件令牌密钥。具体来说，这个过程需要一个 Yubikey 4、Yubikey NEO，或者是最近发布的、同样支持 FIDO2 的 Yubikey 5 系列设备。你应该购买它们中的两个，一个做备份，以避免其中一个丢失或遭到损坏。你可以在不同的工作地点使用这些密钥。较为简单的 FIDO 和 FIDO U2F 版本不适用于这个过程，但是非常适合使用 FIDO 的在线服务。备份、备份，以及备份接下来，为你所有的重要数据制作备份，你可能想在克隆在 VM 里的 Fedora 27/28/29 里测试配置，来确保你在设置你自己的个人工作环境之前理解这个过程。升级，然后安装现在，确定你的 Fedora 是最新的，然后通过 dnf 命令安装所需要的 Fedora Yubikey 包。$ sudo dnf upgrade $ sudo dnf install ykclient* ykpers* pam_yubico*如果你使用的是 VM 环境，例如 Virtual Box，确保 Yubikey 设备已经插进了 USB 口，然后允许 VM 控制的 USB 访问 Yubikey。配置 Yubikey确认你的账户访问到了 USB Yubikey：$ ykinfo -v version: 3.5.0如果 Yubikey 没有被检测到，会出现下面这些错误信息：Yubikey core error: no yubikey present接下来，通过下面这些 ykpersonalize 命令初始化你每个新的 Yubikey。这将设置 Yubikey 配置插槽 2 使用 HMAC-SHA1 算法（即使少于 64 个字符）进行询问响应。如果你已经为询问响应设置好了你的 Yubikey。你就不需要再次运行 ykpersonalize 了。ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible一些用户在使用的时候将 YubiKey 留在了他们的工作站上，甚至用于对虚拟机进行询问-响应。然而，为了更好的安全性，你可能会更愿意使用手动触发 YubiKey 来响应询问。要添加手动询问按钮触发器，请添加 -ochal-btn-trig 选项，这个选项可以使得 Yubikey 在请求中闪烁其 LED。等待你在 15 秒内按下硬件密钥区域上的按钮来生成响应密钥。$ ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 -ochal-btn-trig -oserial-api-visible为你的每个新的硬件密钥执行此操作。每个密钥执行一次。完成编程之后，使用下面的命令将 Yubikey 配置存储到 ~/.yubico：$ ykpamcfg -2 -v debug: util.c:222 (check_firmware_version): YubiKey Firmware version: 4.3.4 Sending 63 bytes HMAC challenge to slot 2 Sending 63 bytes HMAC challenge to slot 2 Stored initial challenge and expected response in '/home/chuckfinley/.yubico/challenge-9992567'.如果你要设置多个密钥用于备份，请将所有的密钥设置为相同，然后使用 ykpamcfg 工具存储每个密钥的询问-响应。如果你在一个已经存在的注册密钥上运行 ykpersonalize 命令，你就必须再次存储配置信息。配置 /etc/pam.d/sudo现在要去验证配置是否有效，在同一个终端窗口中，你需要设置 sudo 来要求使用 Yubikey 的询问-响应。将下面这几行插入到 /etc/pam.d/sudo 文件中。auth required pam_yubico.so mode=challenge-response将上面的 auth 行插入到文件中的 auth include system-auth 行的上面，然后保存并退出编辑器。在默认的 Fedora 29 设置中，/etc/pam.d/sudo 应该像下面这样：#%PAM-1.0 auth required pam_yubico.so mode=challenge-response auth include system-auth account include system-auth password include system-auth session optional pam_keyinit.so revoke session required pam_limits.so session include system-auth保持这个初始的终端窗口打开，然后打开一个新的终端窗口进行测试，在新的终端窗口中输入：$ sudo echo testing你应该注意到了 Yubikey 上的 LED 在闪烁。点击 Yubikey 按钮，你应该会看见一个输入 sudo 密码的提示。在你输入你的密码之后，你应该会在终端屏幕上看见 “testing” 的字样。现在去测试确保失败也正常，启动另一个终端窗口，并从 USB 插口中拔掉 Yubikey。使用下面这条命令验证，在没有 Yubikey 的情况下，sudo 是否会不再正常工作。$ sudo echo testing fail你应该立刻被提示输入 sudo 密码，但即使你输入了正确密码，登录也应该失败。设置 Gnome 桌面管理器（GDM）一旦你的测试完成后，你就可以为图形登录添加询问-响应支持了。将你的 Yubikey 再次插入进 USB 插口中。然后将下面这几行添加到 /etc/pam.d/gdm-password 文件中：auth required pam_yubico.so mode=challenge-response打开一个终端窗口，然后运行下面这些命令。如果需要，你可以使用其他的编辑器：$ sudo vi /etc/pam.d/gdm-password你应该看到 Yubikey 上的 LED 在闪烁，按下 Yubikey 按钮，然后在提示符处输入密码。修改 /etc/pam.d/gdm-password 文件，在已有的 auth substack password-auth 行上添加新的行。这个文件的顶部应该像下面这样：auth [success=done ignore=ignore default=bad] pam_selinux_permit.so auth required pam_yubico.so mode=challenge-response auth substack password-auth auth optional pam_gnome_keyring.so auth include postlogin account required pam_nologin.so保存更改并退出编辑器，如果你使用的是 vi，输入键是按 Esc 键，然后在提示符处输入 wq! 来保存并退出。结论现在注销 GNOME。将 Yubikey 插入到 USB 口，在图形登录界面上点击你的用户名。Yubikey LED 会开始闪烁。触摸那个按钮，你会被提示输入你的密码。如果你丢失了 Yubikey，除了重置密码之外，你还可以使用备份的 Yubikey。你还可以给你的账户增加额外的 Yubikey 配置。如果有其他人获得了你的密码，他们在没有你的物理硬件 Yubikey 的情况下，仍然不能登录。恭喜！你已经显著提高了你的工作环境登录的安全性了。via: https://fedoramagazine.org/login-challenge-response-authentication/作者：nabooengineer 选题：lujun9972 译者：hopefully2333 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
无密码验证可以让你只输入一个 email 而无需输入密码即可登入系统。这是一种比传统的电子邮件/密码验证方式登入更安全的方法。下面我将为你展示，如何在 Go 中实现一个 HTTP API 去提供这种服务。流程用户输入他的电子邮件地址。服务器创建一个临时的一次性使用的代码（就像一个临时密码一样）关联到用户，然后给用户邮箱中发送一个“魔法链接”。用户点击魔法链接。服务器提取魔法链接中的代码，获取关联的用户，并且使用一个新的 JWT 重定向到客户端。在每次有新请求时，客户端使用 JWT 去验证用户。必需条件数据库：我们为这个服务使用了一个叫 CockroachDB 的 SQL 数据库。它非常像 postgres，但它是用 Go 写的。SMTP 服务器：我们将使用一个第三方的邮件服务器去发送邮件。开发的时我们使用 mailtrap。Mailtrap 发送所有的邮件到它的收件箱，因此，你在测试时不需要创建多个假邮件帐户。从 Go 的主页 上安装它，然后使用 go version（1.10.1 atm）命令去检查它能否正常工作。从 CockroachDB 的主页 上下载它，展开它并添加到你的 PATH 变量中。使用 cockroach version（2.0 atm）命令检查它能否正常工作。数据库模式现在，我们在 GOPATH 目录下为这个项目创建一个目录，然后使用 cockroach start 启动一个新的 CockroachDB 节点：cockroach start --insecure --host 127.0.0.1 它会输出一些内容，找到 SQL 地址行，它将显示像 postgresql://root@127.0.0.1:26257?sslmode=disable 这样的内容。稍后我们将使用它去连接到数据库。使用如下的内容去创建一个 schema.sql 文件。DROP DATABASE IF EXISTS passwordless_demo CASCADE; CREATE DATABASE IF NOT EXISTS passwordless_demo; SET DATABASE = passwordless_demo; CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email STRING UNIQUE, username STRING UNIQUE ); CREATE TABLE IF NOT EXISTS verification_codes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); INSERT INTO users (email, username) VALUES ('john@passwordless.local', 'john_doe'); 这个脚本创建了一个名为 passwordless_demo 的数据库、两个名为 users 和 verification_codes 的表，以及为了稍后测试而插入的一些假用户。每个验证代码都与用户关联并保存创建时间，以用于去检查验证代码是否过期。在另外的终端中使用 cockroach sql 命令去运行这个脚本：cat schema.sql | cockroach sql --insecure 环境配置需要配置两个环境变量：SMTP_USERNAME 和 SMTP_PASSWORD，你可以从你的 mailtrap 帐户中获得它们。将在我们的程序中用到它们。Go 依赖我们需要下列的 Go 包：github.com/lib/pq：它是 CockroachDB 使用的 postgres 驱动github.com/matryer/way: 路由器github.com/dgrijalva/jwt-go: JWT 实现go get -u github.com/lib/pq go get -u github.com/matryer/way go get -u github.com/dgrijalva/jwt-go 代码初始化函数创建 main.go 并且通过 init 函数里的环境变量中取得一些配置来启动。var config struct { port int appURL *url.URL databaseURL string jwtKey []byte smtpAddr string smtpAuth smtp.Auth } func init() { config.port, _ = strconv.Atoi(env("PORT", "80")) config.appURL, _ = url.Parse(env("APP_URL", "http://localhost:"+strconv.Itoa(config.port)+"/")) config.databaseURL = env("DATABASE_URL", "postgresql://root@127.0.0.1:26257/passwordless_demo?sslmode=disable") config.jwtKey = []byte(env("JWT_KEY", "super-duper-secret-key")) smtpHost := env("SMTP_HOST", "smtp.mailtrap.io") config.smtpAddr = net.JoinHostPort(smtpHost, env("SMTP_PORT", "25")) smtpUsername, ok := os.LookupEnv("SMTP_USERNAME") if !ok { log.Fatalln("could not find SMTP_USERNAME on environment variables") } smtpPassword, ok := os.LookupEnv("SMTP_PASSWORD") if !ok { log.Fatalln("could not find SMTP_PASSWORD on environment variables") } config.smtpAuth = smtp.PlainAuth("", smtpUsername, smtpPassword, smtpHost) } func env(key, fallbackValue string) string { v, ok := os.LookupEnv(key) if !ok { return fallbackValue } return v } appURL 将去构建我们的 “魔法链接”。port 将要启动的 HTTP 服务器。databaseURL 是 CockroachDB 地址，我添加 /passwordless_demo 前面的数据库地址去表示数据库名字。jwtKey 用于签名 JWT。smtpAddr 是 SMTP_HOST + SMTP_PORT 的联合；我们将使用它去发送邮件。smtpUsername 和 smtpPassword 是两个必需的变量。smtpAuth 也是用于发送邮件。env 函数允许我们去获得环境变量，不存在时返回一个回退值。主函数var db *sql.DB func main() { var err error if db, err = sql.Open("postgres", config.databaseURL); err != nil { log.Fatalf("could not open database connection: %v\n", err) } defer db.Close() if err = db.Ping(); err != nil { log.Fatalf("could not ping to database: %v\n", err) } router := way.NewRouter() router.HandleFunc("POST", "/api/users", jsonRequired(createUser)) router.HandleFunc("POST", "/api/passwordless/start", jsonRequired(passwordlessStart)) router.HandleFunc("GET", "/api/passwordless/verify_redirect", passwordlessVerifyRedirect) router.Handle("GET", "/api/auth_user", authRequired(getAuthUser)) addr := fmt.Sprintf(":%d", config.port) log.Printf("starting server at %s \n", config.appURL) log.Fatalf("could not start server: %v\n", http.ListenAndServe(addr, router)) } 首先，打开数据库连接。记得要加载驱动。import ( _ "github.com/lib/pq" ) 然后，我们创建路由器并定义一些端点。对于无密码流程来说，我们使用两个端点：/api/passwordless/start 发送魔法链接，和 /api/passwordless/verify_redirect 用 JWT 响应。最后，我们启动服务器。你可以创建空处理程序和中间件去测试服务器启动。func createUser(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented) } func passwordlessStart(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented) } func passwordlessVerifyRedirect(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented) } func getAuthUser(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented) } func jsonRequired(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { next(w, r) } } func authRequired(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { next(w, r) } } 接下来：go build ./passwordless-demo 我们在目录中有了一个 “passwordless-demo”，但是你的目录中可能与示例不一样，go build 将创建一个同名的可执行文件。如果你没有关闭前面的 cockroach 节点，并且你正确配置了 SMTP_USERNAME 和 SMTP_PASSWORD 变量，你将看到命令 starting server at http://localhost/ 没有错误输出。请求 JSON 的中间件端点需要从请求体中解码 JSON，因此要确保请求是 application/json 类型。因为它是一个通用的东西，我将它解耦到中间件。func jsonRequired(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ct := r.Header.Get("Content-Type") isJSON := strings.HasPrefix(ct, "application/json") if !isJSON { respondJSON(w, "JSON body required", http.StatusUnsupportedMediaType) return } next(w, r) } } 实现很容易。首先它从请求头中获得内容的类型，然后检查它是否是以 “application/json” 开始，如果不是则以 415 Unsupported Media Type 提前返回。响应 JSON 的函数以 JSON 响应是非常通用的做法，因此我把它提取到函数中。func respondJSON(w http.ResponseWriter, payload interface{}, code int) { switch value := payload.(type) { case string: payload = map[string]string{"message": value} case int: payload = map[string]int{"value": value} case bool: payload = map[string]bool{"result": value} } b, err := json.Marshal(payload) if err != nil { respondInternalError(w, fmt.Errorf("could not marshal response payload: %v", err)) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(code) w.Write(b) } 首先，对原始类型做一个类型判断，并将它们封装到一个 map。然后将它们编组到 JSON，设置响应内容类型和状态码，并写 JSON。如果 JSON 编组失败，则响应一个内部错误。响应内部错误的函数respondInternalError 是一个响应 500 Internal Server Error 的函数，但是也同时将错误输出到控制台。func respondInternalError(w http.ResponseWriter, err error) { log.Println(err) respondJSON(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } 创建用户的处理程序下面开始编写 createUser 处理程序，因为它非常容易并且是 REST 式的。type User struct { ID string `json:"id"` Email string `json:"email"` Username string `json:"username"` } User 类型和 users 表相似。var ( rxEmail = regexp.MustCompile("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$") rxUsername = regexp.MustCompile("^[a-zA-Z][\\w|-]{1,17}$") ) 这些正则表达式是分别用于去验证电子邮件和用户名的。这些都很简单，可以根据你的需要随意去适配。现在，在 createUser 函数内部，我们将开始解码请求体。var user User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { respondJSON(w, err.Error(), http.StatusBadRequest) return } defer r.Body.Close() 我们将使用请求体去创建一个 JSON 解码器来解码出一个用户指针。如果发生错误则返回一个 400 Bad Request。不要忘记关闭请求体读取器。errs := make(map[string]string) if user.Email == "" { errs["email"] = "Email required" } else if !rxEmail.MatchString(user.Email) { errs["email"] = "Invalid email" } if user.Username == "" { errs["username"] = "Username required" } else if !rxUsername.MatchString(user.Username) { errs["username"] = "Invalid username" } if len(errs) != 0 { respondJSON(w, errs, http.StatusUnprocessableEntity) return } 这是我如何做验证；一个简单的 map 并检查如果 len(errs) != 0，则使用 422 Unprocessable Entity 去返回。err := db.QueryRowContext(r.Context(), ` INSERT INTO users (email, username) VALUES ($1, $2) RETURNING id `, user.Email, user.Username).Scan(&user.ID) if errPq, ok := err.(*pq.Error); ok && errPq.Code.Name() == "unique_violation" { if strings.Contains(errPq.Error(), "email") { errs["email"] = "Email taken" } else { errs["username"] = "Username taken" } respondJSON(w, errs, http.StatusForbidden) return } else if err != nil { respondInternalError(w, fmt.Errorf("could not insert user: %v", err)) return } 这个 SQL 查询使用一个给定的 email 和用户名去插入一个新用户，并返回自动生成的 id，每个 $ 将被接下来传递给 QueryRowContext 的参数替换掉。因为 users 表在 email 和 username 字段上有唯一性约束，因此我将检查 “unique\_violation” 错误并返回 403 Forbidden 或者返回一个内部错误。respondJSON(w, user, http.StatusCreated) 最后使用创建的用户去响应。无密码验证开始部分的处理程序type PasswordlessStartRequest struct { Email string `json:"email"` RedirectURI string `json:"redirectUri"` } 这个结构体含有 passwordlessStart 的请求体：希望去登入的用户 email、来自客户端的重定向 URI（这个应用中将使用我们的 API）如：https://frontend.app/callback。var magicLinkTmpl = template.Must(template.ParseFiles("templates/magic-link.html")) 我们将使用 golang 模板引擎去构建邮件，因此需要你在 templates 目录中，用如下的内容创建一个 magic-link.html 文件： Magic Link Click here to login. This link expires in 15 minutes and can only be used once. 这个模板是给用户发送魔法链接邮件用的。你可以根据你的需要去随意调整它。现在， 进入 passwordlessStart 函数内部：var input PasswordlessStartRequest if err := json.NewDecoder(r.Body).Decode(&input); err != nil { respondJSON(w, err.Error(), http.StatusBadRequest) return } defer r.Body.Close() 首先，我们像前面一样解码请求体。errs := make(map[string]string) if input.Email == "" { errs["email"] = "Email required" } else if !rxEmail.MatchString(input.Email) { errs["email"] = "Invalid email" } if input.RedirectURI == "" { errs["redirectUri"] = "Redirect URI required" } else if u, err := url.Parse(input.RedirectURI); err != nil || !u.IsAbs() { errs["redirectUri"] = "Invalid redirect URI" } if len(errs) != 0 { respondJSON(w, errs, http.StatusUnprocessableEntity) return } 我们使用 golang 的 URL 解析器去验证重定向 URI，检查那个 URI 是否为绝对地址。var verificationCode string err := db.QueryRowContext(r.Context(), ` INSERT INTO verification_codes (user_id) VALUES ((SELECT id FROM users WHERE email = $1)) RETURNING id `, input.Email).Scan(&verificationCode) if errPq, ok := err.(*pq.Error); ok && errPq.Code.Name() == "not_null_violation" { respondJSON(w, "No user found with that email", http.StatusNotFound) return } else if err != nil { respondInternalError(w, fmt.Errorf("could not insert verification code: %v", err)) return } 这个 SQL 查询将插入一个验证代码，这个代码通过给定的 email 关联到用户，并且返回一个自动生成的 id。因为有可能会出现用户不存在的情况，那样的话子查询可能解析为 NULL，这将导致在 user_id 字段上因违反 NOT NULL 约束而导致失败，因此需要对这种情况进行检查，如果用户不存在，则返回 404 Not Found 或者一个内部错误。q := make(url.Values) q.Set("verification_code", verificationCode) q.Set("redirect_uri", input.RedirectURI) magicLink := *config.appURL magicLink.Path = "/api/passwordless/verify_redirect" magicLink.RawQuery = q.Encode() 现在，构建魔法链接并设置查询字符串中的 verification_code 和 redirect_uri 的值。如：http://localhost/api/passwordless/verify_redirect?verification_code=some_code&redirect_uri=https://frontend.app/callback。var body bytes.Buffer data := map[string]string{"MagicLink": magicLink.String()} if err := magicLinkTmpl.Execute(&body, data); err != nil { respondInternalError(w, fmt.Errorf("could not execute magic link template: %v", err)) return } 我们将得到的魔法链接模板的内容保存到缓冲区中。如果发生错误则返回一个内部错误。to := mail.Address{Address: input.Email} if err := sendMail(to, "Magic Link", body.String()); err != nil { respondInternalError(w, fmt.Errorf("could not mail magic link: %v", err)) return } 现在来写给用户发邮件的 sendMail 函数。如果发生错误则返回一个内部错误。w.WriteHeader(http.StatusNoContent) 最后，设置响应状态码为 204 No Content。对于成功的状态码，客户端不需要很多数据。发送邮件函数func sendMail(to mail.Address, subject, body string) error { from := mail.Address{ Name: "Passwordless Demo", Address: "noreply@" + config.appURL.Host, } headers := map[string]string{ "From": from.String(), "To": to.String(), "Subject": subject, "Content-Type": `text/html; charset="utf-8"`, } msg := "" for k, v := range headers { msg += fmt.Sprintf("%s: %s ", k, v) } msg += " " msg += body return smtp.SendMail( config.smtpAddr, config.smtpAuth, from.Address, []string{to.Address}, []byte(msg)) } 这个函数创建一个基本的 HTML 邮件结构体并使用 SMTP 服务器去发送它。邮件的内容你可以随意定制，我喜欢使用比较简单的内容。无密码验证重定向的处理程序var rxUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") 首先，这个正则表达式去验证一个 UUID（即验证代码）。现在进入 passwordlessVerifyRedirect 函数内部：q := r.URL.Query() verificationCode := q.Get("verification_code") redirectURI := q.Get("redirect_uri") /api/passwordless/verify_redirect 是一个 GET 端点，以便于我们从查询字符串中读取数据。errs := make(map[string]string) if verificationCode == "" { errs["verification_code"] = "Verification code required" } else if !rxUUID.MatchString(verificationCode) { errs["verification_code"] = "Invalid verification code" } var callback *url.URL var err error if redirectURI == "" { errs["redirect_uri"] = "Redirect URI required" } else if callback, err = url.Parse(redirectURI); err != nil || !callback.IsAbs() { errs["redirect_uri"] = "Invalid redirect URI" } if len(errs) != 0 { respondJSON(w, errs, http.StatusUnprocessableEntity) return } 类似的验证，我们保存解析后的重定向 URI 到一个 callback 变量中。var userID string if err := db.QueryRowContext(r.Context(), ` DELETE FROM verification_codes WHERE id = $1 AND created_at >= now() - INTERVAL '15m' RETURNING user_id `, verificationCode).Scan(&userID); err == sql.ErrNoRows { respondJSON(w, "Link expired or already used", http.StatusBadRequest) return } else if err != nil { respondInternalError(w, fmt.Errorf("could not delete verification code: %v", err)) return } 这个 SQL 查询通过给定的 id 去删除相应的验证代码，并且确保它创建之后时间不超过 15 分钟，它也返回关联的 user_id。如果没有检索到内容，意味着代码不存在或者已过期，我们返回一个响应信息，否则就返回一个内部错误。expiresAt := time.Now().Add(time.Hour * 24 * 60) tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ Subject: userID, ExpiresAt: expiresAt.Unix(), }).SignedString(config.jwtKey) if err != nil { respondInternalError(w, fmt.Errorf("could not create JWT: %v", err)) return } 这些是如何去创建 JWT。我们为 JWT 设置一个 60 天的过期值，你也可以设置更短的时间（大约 2 周），并添加一个新端点去刷新令牌，但是不要搞的过于复杂。expiresAtB, err := expiresAt.MarshalText() if err != nil { respondInternalError(w, fmt.Errorf("could not marshal expiration date: %v", err)) return } f := make(url.Values) f.Set("jwt", tokenString) f.Set("expires_at", string(expiresAtB)) callback.Fragment = f.Encode() 我们去规划重定向；你可使用查询字符串去添加 JWT，但是更常见的是使用一个哈希片段。如：https://frontend.app/callback#jwt=token_here&expires_at=some_date.过期日期可以从 JWT 中提取出来，但是这样做的话，就需要在客户端上实现一个 JWT 库来解码它，因此为了简化，我将它加到这里。http.Redirect(w, r, callback.String(), http.StatusFound) 最后我们使用一个 302 Found 重定向。无密码的流程已经完成。现在需要去写 getAuthUser 端点的代码了，它用于获取当前验证用户的信息。你应该还记得，这个端点使用了 guard 中间件。使用 Auth 中间件在编写 guard 中间件之前，我将编写一个不需要验证的分支。目的是，如果没有传递 JWT，它将不去验证用户。type ContextKey struct { Name string } var keyAuthUserID = ContextKey{"auth_user_id"} func withAuth(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { a := r.Header.Get("Authorization") hasToken := strings.HasPrefix(a, "Bearer ") if !hasToken { next(w, r) return } tokenString := a[7:] p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}} token, err := p.ParseWithClaims( tokenString, &jwt.StandardClaims{}, func (*jwt.Token) (interface{}, error) { return config.jwtKey, nil }, ) if err != nil { respondJSON(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } claims, ok := token.Claims.(*jwt.StandardClaims) if !ok || !token.Valid { respondJSON(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } ctx := r.Context() ctx = context.WithValue(ctx, keyAuthUserID, claims.Subject) next(w, r.WithContext(ctx)) } } JWT 将在每次请求时以 Bearer <token_here> 格式包含在 Authorization 头中。因此，如果没有提供令牌，我们将直接通过，进入接下来的中间件。我们创建一个解析器来解析令牌。如果解析失败则返回 401 Unauthorized。然后我们从 JWT 中提取出要求的内容，并添加 Subject（就是用户 ID）到需要的地方。Guard 中间件func guard(next http.HandlerFunc) http.HandlerFunc { return withAuth(func(w http.ResponseWriter, r *http.Request) { _, ok := r.Context().Value(keyAuthUserID).(string) if !ok { respondJSON(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } next(w, r) }) } 现在，guard 将使用 withAuth 并从请求内容中提取出验证用户的 ID。如果提取失败，它将返回 401 Unauthorized，提取成功则继续下一步。获取 Auth 用户在 getAuthUser 处理程序内部：ctx := r.Context() authUserID := ctx.Value(keyAuthUserID).(string) user, err := fetchUser(ctx, authUserID) if err == sql.ErrNoRows { respondJSON(w, http.StatusText(http.StatusTeapot), http.StatusTeapot) return } else if err != nil { respondInternalError(w, fmt.Errorf("could not query auth user: %v", err)) return } respondJSON(w, user, http.StatusOK) 首先，我们从请求内容中提取验证用户的 ID，我们使用这个 ID 去获取用户。如果没有获取到内容，则发送一个 418 I'm a teapot，或者一个内部错误。最后，我们将用这个用户去响应。获取 User 函数下面你看到的是 fetchUser 函数。func fetchUser(ctx context.Context, id string) (User, error) { user := User{ID: id} err := db.QueryRowContext(ctx, ` SELECT email, username FROM users WHERE id = $1 `, id).Scan(&user.Email, &user.Username) return user, err } 我将它解耦是因为通过 ID 来获取用户是个常做的事。以上就是全部的代码。你可以自己去构建它和测试它。这里 还有一个 demo 你可以试用一下。如果你在 mailtrap 上点击之后出现有关 脚本运行被拦截，因为文档的框架是沙箱化的，并且没有设置 'allow-scripts' 权限 的问题，你可以尝试右键点击 “在新标签中打开链接“。这样做是安全的，因为邮件内容是 沙箱化的。我在 localhost 上有时也会出现这个问题，但是我认为你一旦以 https:// 方式部署到服务器上应该不会出现这个问题了。如果有任何问题，请在我的 GitHub repo 留言或者提交 PRs以后，我为这个 API 写了一个客户端作为这篇文章的第二部分。via: https://nicolasparada.netlify.com/posts/passwordless-auth-server/作者：Nicolás Parada 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
了解如何在 Linux 中创建登录导语，来向要登录或登录后的用户显示不同的警告或消息。无论何时登录公司的某些生产系统，你都会看到一些登录消息、警告或关于你将登录或已登录的服务器的信息，如下所示。这些是 登录导语 login banner 。在本文中，我们将引导你配置它们。你可以配置两种类型的导语。用户登录前显示的导语信息（在你选择的文件中配置，例如 /etc/login.warn）用户成功登录后显示的导语信息（在 /etc/motd 中配置）如何在用户登录前连接系统时显示消息当用户连接到服务器并且在登录之前，这个消息将被显示给他。意味着当他输入用户名时，该消息将在密码提示之前显示。你可以使用任何文件名并在其中输入信息。在这里我们使用 /etc/login.warn 并且把我们的消息放在里面。# cat /etc/login.warn !!!! Welcome to KernelTalks test server !!!! This server is meant for testing Linux commands and tools. If you are not associated with kerneltalks.com and not authorized please dis-connect immediately. 现在，需要将此文件和路径告诉 sshd 守护进程，以便它可以为每个用户登录请求获取此标语。对于此，打开 /etc/sshd/sshd_config 文件并搜索 #Banner none。这里你需要编辑该配置文件，并写下你的文件名并删除注释标记（#）。它应该看起来像：Banner /etc/login.warn。保存文件并重启 sshd 守护进程。为避免断开现有的连接用户，请使用 HUP 信号重启 sshd。root@kerneltalks # ps -ef | grep -i sshd root 14255 1 0 18:42 ? 00:00:00 /usr/sbin/sshd -D root 19074 14255 0 18:46 ? 00:00:00 sshd: ec2-user [priv] root 19177 19127 0 18:54 pts/0 00:00:00 grep -i sshd root@kerneltalks # kill -HUP 14255 就是这样了！打开新的会话并尝试登录。你将看待你在上述步骤中配置的消息。你可以在用户输入密码登录系统之前看到此消息。如何在用户登录后显示消息消息用户在成功登录系统后看到的 当天消息 Message Of The Day （MOTD）由 /etc/motd 控制。编辑这个文件并输入当成功登录后欢迎用户的消息。root@kerneltalks # cat /etc/motd W E L C O M E Welcome to the testing environment of kerneltalks. Feel free to use this system for testing your Linux skills. In case of any issues reach out to admin at info@kerneltalks.com. Thank you. 你不需要重启 sshd 守护进程来使更改生效。只要保存该文件，sshd 守护进程就会下一次登录请求时读取和显示。你可以在上面的截图中看到：黄色框是由 /etc/motd 控制的 MOTD，绿色框就是我们之前看到的登录导语。你可以使用 cowsay、banner、figlet、lolcat 等工具创建出色的引人注目的登录消息。此方法适用于几乎所有 Linux 发行版，如 RedHat、CentOs、Ubuntu、Fedora 等。via: https://kerneltalks.com/tips-tricks/how-to-configure-login-banners-in-linux/作者：kerneltalks 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Facebook、 Gmail、 Twitter 是我们每天都会用的网站（LCTT 译注：才不是呢）。它们的共同点在于都需要你登录进去后才能做进一步的操作。只有你通过认证并登录后才能在 twitter 发推，在 Facebook 上评论，以及在 Gmail上处理电子邮件。那么登录的原理是什么？网站是如何认证的？它怎么知道是哪个用户从哪儿登录进来的？下面我们来对这些问题进行一一解答。用户登录的原理是什么?每次你在网站的登录页面中输入用户名和密码时，这些信息都会发送到服务器。服务器随后会将你的密码与服务器中的密码进行验证。如果两者不匹配，则你会得到一个错误密码的提示。如果两者匹配，则成功登录。登录时发生了什么?登录后，web 服务器会初始化一个 会话 session 并在你的浏览器中设置一个 cookie 变量。该 cookie 变量用于作为新建会话的一个引用。搞晕了？让我们说的再简单一点。会话的原理是什么?服务器在用户名和密码都正确的情况下会初始化一个会话。会话的定义很复杂，你可以把它理解为“关系的开始”。认证通过后，服务器就开始跟你展开一段关系了。由于服务器不能象我们人类一样看东西，它会在我们的浏览器中设置一个 cookie 来将我们的关系从其他人与服务器的关系标识出来。什么是 Cookie?cookie 是网站在你的浏览器中存储的一小段数据。你应该已经见过他们了。当你登录后，服务器为你创建一段关系或者说一个会话，然后将唯一标识这个会话的会话 id 以 cookie 的形式存储在你的浏览器中。什么意思?所有这些东西存在的原因在于识别出你来，这样当你写评论或者发推时，服务器能知道是谁在发评论，是谁在发推。当你登录后，会产生一个包含会话 id 的 cookie。这样，这个会话 id 就被赋予了那个输入正确用户名和密码的人了。也就是说，会话 id 被赋予给了拥有这个账户的人了。之后，所有在网站上产生的行为，服务器都能通过他们的会话 id 来判断是由谁发起的。如何让我保持登录状态?会话有一定的时间限制。这一点与现实生活中不一样，现实生活中的关系可以在不见面的情况下持续很长一段时间，而会话具有时间限制。你必须要不断地通过一些动作来告诉服务器你还在线。否则的话，服务器会关掉这个会话，而你会被登出。不过在某些网站上可以启用“保持登录”功能，这样服务器会将另一个唯一变量以 cookie 的形式保存到我们的浏览器中。这个唯一变量会通过与服务器上的变量进行对比来实现自动登录。若有人盗取了这个唯一标识（我们称之为 cookie stealing），他们就能访问你的账户了。结论我们讨论了登录系统的工作原理以及网站是如何进行认证的。我们还学到了什么是会话和 cookies，以及它们在登录机制中的作用。我们希望你们以及理解了用户登录的工作原理，如有疑问，欢迎提问。via: http://www.theitstuff.com/sessions-cookies-user-login-work作者：Rishabh Kandari 译者：lujun9972 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
如何在 Linux 上让一段时间不活动的用户自动登出
让我们想象这么一个场景。你有一台服务器经常被网络中各系统的很多个用户访问。有可能出现某些用户忘记登出会话让会话保持会话处于连接状态。我们都知道留下一个处于连接状态的用户会话是一件多么危险的事情。有些用户可能会借此故意做一些损坏系统的事情。而你，作为一名系统管理员，会去每个系统上都检查一遍用户是否有登出吗？其实这完全没必要的。而且若网络中有成百上千台机器，这也太耗时了。不过，你可以让用户在本机或 SSH 会话上超过一定时间不活跃的情况下自动登出。本教程就将教你如何在类 Unix 系统上实现这一点。一点都不难。跟我做。在 Linux 上实现一段时间后自动登出非活动用户有三种实现方法。让我们先来看第一种方法。方法 1：编辑 ~/.bashrc 或 ~/.bash_profile 文件：$ vi ~/.bashrc 或，$ vi ~/.bash_profile 将下面行加入其中：TMOUT=100 这会让用户在停止动作 100 秒后自动登出。你可以根据需要定义这个值。保存并关闭文件。运行下面命令让更改生效：$ source ~/.bashrc 或，$ source ~/.bash_profile 现在让会话闲置 100 秒。100 秒不活动后，你会看到下面这段信息，并且用户会自动退出会话。timed out waiting for input: auto-logout Connection to 192.168.43.2 closed. 该设置可以轻易地被用户所修改。因为，~/.bashrc 文件被用户自己所拥有。要修改或者删除超时设置，只需要删掉上面添加的行然后执行 source ~/.bashrc 命令让修改生效。此外，用户也可以运行下面命令来禁止超时：$ export TMOUT=0 或，$ unset TMOUT 若你想阻止用户修改该设置，使用下面方法代替。方法 2：以 root 用户登录。创建一个名为 autologout.sh 的新文件。# vi /etc/profile.d/autologout.sh 加入下面内容：TMOUT=100 readonly TMOUT export TMOUT 保存并退出该文件。为它添加可执行权限：# chmod +x /etc/profile.d/autologout.sh 现在，登出或者重启系统。非活动用户就会在 100 秒后自动登出了。普通用户即使想保留会话连接但也无法修改该配置了。他们会在 100 秒后强制退出。这两种方法对本地会话和远程会话都适用（即本地登录的用户和远程系统上通过 SSH 登录的用户）。下面让我们来看看如何实现只自动登出非活动的 SSH 会话，而不自动登出本地会话。方法 3：这种方法，我们只会让 SSH 会话用户在一段时间不活动后自动登出。编辑 /etc/ssh/sshd_config 文件：$ sudo vi /etc/ssh/sshd_config 添加/修改下面行：ClientAliveInterval 100 ClientAliveCountMax 0 保存并退出该文件。重启 sshd 服务让改动生效。$ sudo systemctl restart sshd 现在，在远程系统通过 ssh 登录该系统。100 秒后，ssh 会话就会自动关闭了，你也会看到下面消息：$ Connection to 192.168.43.2 closed by remote host. Connection to 192.168.43.2 closed. 现在，任何人从远程系统通过 SSH 登录本系统，都会在 100 秒不活动后自动登出了。希望本文能对你有所帮助。我马上还会写另一篇实用指南。如果你觉得我们的指南有用，请在您的社交网络上分享，支持 我们！祝您好运！via: https://www.ostechnix.com/auto-logout-inactive-users-period-time-linux/作者：SK 译者：lujun9972 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
有人说，安全不是一个产品，而是一个过程（LCTT 注：安全公司 McAfee 认为，安全风险管理是一个方法论，而不是安全产品的堆叠）。虽然 SSH 协议被设计成使用加密技术来确保安全，但如果使用不当，别人还是能够破坏你的系统：比如弱密码、密钥泄露、使用过时的 SSH 客户端等，都能引发安全问题。在考虑 SSH 认证方案时，大家普遍认为公钥认证比密码认证更安全。然而，公钥认证技术并不是为公共环境设置的，如果你在一台公用电脑上使用公钥认证登录 SSH 服务器，你的服务器已经毫无安全可言了，公用的电脑可能会记录你的公钥，或从你的内存中读取公钥。如果你不信任本地电脑，那你最好还是使用其他方式登录服务器。现在就是“一次性密码（OTP）”派上用场的时候了，就像名字所示，一次性密码只能被使用一次。这种一次性密码非常合适在不安全的环境下发挥作用，就算它被窃取，也无法再次使用。有个生成一次性密码的方法是通过谷歌认证器，但在本文中，我要介绍的是另一种 SSH 登录方案：OTPW，它是个一次性密码登录的软件包。不像谷歌认证，OTPW 不需要依赖任何第三方库。OTPW 是什么OTPW 由一次性密码生成器和 PAM 认证规则组成。在 OTPW 中一次性密码由生成器事先生成，然后由用户以某种安全的方式获得（比如打印到纸上）。另一方面，这些密码会通过 Hash 加密保存在 SSH 服务器端。当用户使用一次性密码登录系统时，OTPW 的 PAM 模块认证这些密码，并且保证它们不能再次使用。步骤1：OTPW 的安装和配置在 Debian, Ubuntu 或 Linux Mint 发行版上使用 apt-get 安装：$ sudo apt-get install libpam-otpw otpw-bin 打开针对 SSH 服务的 PAM 配置文件（/etc/pam.d/sshd），注释掉下面这行（目的是禁用 PAM 的密码认证功能）：#@include common-auth 添加下面两行（用于打开一次性密码认证功能）：auth required pam_otpw.so session optional pam_otpw.so 在 Fedora 或 CentOS/RHEL 发行版上在基于 RedHat 的发行版中没有编译好的 OTPW，所以我们需要使用源代码来安装它。首先，安装编译环境：$ sudo yum git gcc pam-devel $ git clone https://www.cl.cam.ac.uk/~mgk25/git/otpw $ cd otpw 打开 Makefile 文件，编辑以“PAMLIB=”开头的那行配置：64 位系统：PAMLIB=/usr/lib64/security 32 位系统：PAMLIB=/usr/lib/security 编译安装。需要注意的是安装过程会自动重启 SSH 服务一下，所以如果你是使用 SSH 连接到服务器，做好被断开连接的准备吧（LCTT 译注：也许不会被断开连接，即便被断开连接，请使用原来的方式重新连接即可，现在还没有换成一次性口令方式。）。$ make $ sudo make install 现在你需要更新 SELinux 策略，因为 /usr/sbin/sshd 会往你的 home 目录写数据，而 SELinux 默认是不允许这么做的。如果没有使用 SELinux 服务（LCTT 注：使用 getenforce 命令查看结果，如果是 enforcing，就是打开了 SELinux 服务），请跳过这一步。$ sudo grep sshd /var/log/audit/audit.log | audit2allow -M mypol $ sudo semodule -i mypol.pp 接下来打开 PAM 配置文件（/etc/pam.d/sshd），注释下面这行（为了禁用密码认证）：#auth substack password-auth 添加下面两行（用于打开一次性密码认证功能）：auth required pam_otpw.so session optional pam_otpw.so 步骤2：配置 SSH 服务器，使用一次性密码打开 /etc/ssh/sshd\_config 文件，设置下面三个参数。你要确保下面的参数不会重复存在，否则 SSH 服务器可能会出现异常。UsePrivilegeSeparation yes ChallengeResponseAuthentication yes UsePAM yes 你还需要禁用默认的密码认证功能。另外可以选择开启公钥认证功能，那样的话你就可以在没有一次性密码的时候使用公钥进行认证。PubkeyAuthentication yes PasswordAuthentication no 重启 SSH 服务器。Debian, Ubuntu 或 Linux Mint 发行版：$ sudo service ssh restart Fedora 或 CentOS/RHEL 7 发行版：$ sudo systemctl restart sshd （LCTT 译注：虽然这里重启了 sshd 服务，但是你当前的 ssh 连接应该不受影响，只是在你完成下述步骤之前，无法按照原有方式建立新的连接了。因此，保险起见，要么多开一个 ssh 连接，避免误退出当前连接；要么将重启 sshd 服务器步骤放到步骤3完成之后。）步骤3：使用 OTPW 产生一次性密码之前提到过，你需要事先创建一次性密码，并保存起来。使用 otpw-gen 命令创建密码：$ cd ~ $ otpw-gen > temporary_password.txt 这个命令会让你输入密码前缀，当你以后登录的时候，你需要同时输入这个前缀以及一次性密码。密码前缀是另外一层保护，就算你的一次性密码表被泄漏，别人也无法通过暴力破解你的 SSH 密码。设置好密码前缀后，这个命令会产生 280 个一次性密码（LCTT 译注：保存到 ~/.otpw 下），并将它们导出到一个文本文件中（如 temporary\_password.txt）。每个密码（默认是 8 个字符）由一个 3 位十进制数索引。你需要将这个密码表打印出来，并随身携带。查看 ./.otpw 文件，它存放了一次性密码的 HASH 值。头 3 位十进制数与你随身携带的密码表的索引一一对应，在你登录 SSH 服务器的时候会被用到。$ more ~/.otpw OTPW1 280 3 12 8 191ai+:ENwmMqwn 218tYRZc%PIY27a 241ve8ns%NsHFmf 055W4/YCauQJkr: 102ZnJ4VWLFrk5N 2273Xww55hteJ8Y 1509d4b5=A64jBT 168FWBXY%ztm9j% 000rWUSdBYr%8UE 037NvyryzcI+YRX 122rEwA3GXvOk=z 测试一次性密码登录 SSH 服务器使用普通的方式登录 SSH 服务器：$ ssh user@remote_host 如果 OTPW 成功运行，你会看到一点与平时登录不同的地方：Password 191: 现在打开你的密码表，找到索引号为 191 的密码。023 kBvp tq/G 079 jKEw /HRM 135 oW/c /UeB 191 fOO+ PeiD 247 vAnZ EgUt 从上表可知，191 号密码是“fOO+PeiD”。你需要加上密码前缀，比如你设置的前缀是“000”，则你实际需要输入的密码是“000fOO+PeiD”。成功登录后，你这次输入的密码自动失效。查看 ~/.otpw 文件，你会发现第一行变成“---------------”，这表示 191 号密码失效了。OTPW1 280 3 12 8 --------------- 218tYRZc%PIY27a 241ve8ns%NsHFmf 055W4/YCauQJkr: 102ZnJ4VWLFrk5N 2273Xww55hteJ8Y 1509d4b5=A64jBT 168FWBXY%ztm9j% 000rWUSdBYr%8UE 037NvyryzcI+YRX 122rEwA3GXvOk=z 总结在这个教程中，我介绍了如何使用 OTPW 工具来设置一次性登录密码。你也许意识到了在这种双因子的认证方式中，打印一张密码表让人感觉好 low，但是这种方式是最简单的，并且不用依赖任何第三方软件。无论你用哪种方式创建一次性密码，在你需要在一个不可信任的环境登录 SSH 服务器的时候，它们都很有用。你可以就这个主题来分享你的经验和观点。via: http://xmodulo.com/secure-ssh-login-one-time-passwords-linux.html作者：Dan Nanni 译者：bazz2 校对：wxy本文由 LCTT 原创翻译，Linux中国 荣誉推出
大家好，今天我来向大家介绍如何在 Ubuntu12.04 上设置 SSH 的无密码登录功能。仅在工作站上有正确的（公私）密钥对以供匹配时SSH服务端才会允许你登录，反之访问将不会被允许。正常情况下，我们需要连上SSH的控制台输入用户名及其密码才行。如果两者全部正确，我们就可以访问，反之访问被服务端拒绝。不过相比而言还有一种比用密码更安全的登录方式，我们可以在登录SSH时通过加密密钥进行无密码登录。如果你想启用这个安全的方式，我们只需简单的禁用密码登录并只允许加密密钥登录即可。使用这种方式时，客户端计算机上会产生一对私钥和公钥。接着客户端得把公钥上传到SSH服务端的authorized\_key文件中去。在授予访问前，服务器及客户端电脑会校验这个密钥对。如果服务器上的公钥与客服端提交的私钥匹配则授予访问权限，否则访问被拒绝。这是认证到SSH服务器的非常安全的一种做法，如果你想为单一的SSH用户登录实现安全登录，这也是备受推崇的方式。这里快速的过一遍如何启用无密码登录SSH的配置过程。1.安装Openssh服务端首先，我们需要更新我们的本地库索引。所以如下所见，我们需要先输入“apt-get update”$ sudo apt-get update 现在我们可以通过以下命令安装openssh-server：$ sudo apt-get install openssh-server 2. 开启openssh服务在OpenSSH已经成功安装在Ubuntu14.04操作系统上了之后，我们要启动OpenSSH的服务。以下命令让你启动/开启服务。$ sudo service ssh start 或$ sudo /etc/init.d/ssh start 3. 配置密钥对在我们安装并启动了OpenSSH服务以后。现在终于到了要我们搞定公私钥对的时候了，在终端中运行以下命令：$ ssh-keygen -t rsa 在运行完以上命令了以后，我们需要回答一系列的问题。首先选择保存密钥的路径，按回车将会选择默认路径即家目录的一个隐藏的.ssh文件夹。下一个提示是请输入口令提醒。我个人将此留空（直接回车）。之后密钥对就会创建，大功告成。在密钥对生成以后，我们需要将客户端上的公钥复制到SSH服务端或者主机，来创建对客户端的信任关系。运行以下命令复制客户端的公钥到服务端。$ ssh-copy-id user@ip_address 在公钥上传之后，我们现在可以禁用通过密码登陆SSH的方式了。为此，我们需要通过以下命令用文本编辑器打开/etc/ssh/ssh\_config。$ sudo nano /etc/ssh/sshd_config 现在，我们需要按照下图所示去掉几行注释并进行一些赋值。4. 重启SSH服务最后，在我们配置完SSH服务端后，为了使改动生效我们需要重启SSH服务。在终端或控制台运行以下命令重启。$ sudo service ssh restart 或$ sudo /etc/init.d/ssh restart 现在，我们可以试试不用密码仅用密钥对的方式登录ssh服务端了。总结太好了！我们成功的配置了无密码登录SSH。使用加密密钥对进行SSH服务器认证是非常安全的一种做法，如果你想为SSH的单一用户登录实施安全的认证这也是备受推崇的方式。所以，如果你还有什么问题或建议，请在意见框中向我们反馈。很欣慰你能读完，希望你能喜欢加密的SSH安全登录 :-)via: http://linoxide.com/ubuntu-how-to/setup-passwordless-ssh-logon-ubuntu-14-04/作者：Arun Pyasi 译者：martin2011qi 校对：wxy本文由 LCTT 原创翻译，Linux中国 荣誉推出