当前位置: 首页 > news >正文

Spring Boot对访问密钥加解密——HMAC-SHA256

HMAC-SHA256 简介

HMAC-SHA256 是一种基于 哈希函数 的消息认证码(Message Authentication Code, MAC),它结合了哈希算法(如 SHA-256)和一个密钥,用于验证消息的完整性和真实性。

HMAC 是 “Hash-based Message Authentication Code” 的缩写,它广泛应用于网络通信中,用于保证消息在传输过程中未被篡改,同时也可以校验消息是否来自可信方。


HMAC-SHA256 的组成

HMAC-SHA256 的运算公式如下:

HMAC(key, message) = SHA256((key ⊕ opad) || SHA256((key ⊕ ipad) || message))
  • key:一个秘密密钥,用于认证消息,只有通信双方知道。

  • message:需要进行签名的消息内容。

  • SHA256:一种安全的哈希函数,用于生成固定长度的摘要值。

  • :按位异或操作(XOR)。

  • opadipad

    • opad:外部填充字节 (0x5c)。
    • ipad:内部填充字节 (0x36)。
流程解释:
  1. 将密钥与 ipad 结合,并将其与消息一起进行第一次哈希。
  2. 将密钥与 opad 结合,并将其与第一次哈希的结果一起进行第二次哈希。
  3. 最终输出 HMAC 值。

HMAC-SHA256 的特点

  1. 基于密钥
    • 与普通的 SHA256 不同,HMAC-SHA256 引入了密钥,只有通信双方知道密钥,保证了消息的认证性。
  2. 抗篡改
    • 如果消息在传输过程中被篡改,HMAC 认证将失败。
  3. 抗重放攻击
    • 通常结合时间戳(timestamp)或随机数(nonce),防止消息被恶意重放。
  4. 性能高效
    • 基于哈希函数的算法速度快,适合高并发场景。
  5. 简单易用
    • 常用于 REST API、签名验证和网络协议中。

HMAC-SHA256 的应用场景

  1. API 鉴权
    • 许多云服务和 API 平台(如 AWS、阿里云等)使用 HMAC-SHA256 来验证 API 请求的真实性和完整性。
  2. 消息完整性校验
    • 用于验证消息在传输过程中未被篡改。
  3. 数据完整性校验
    • 用于验证文件或数据传输的完整性(结合密钥,防止恶意伪造)。
  4. 通信协议
    • 用于加密通信协议(如 TLS)的消息认证。
  5. Token 签名
    • 用于生成和验证基于密钥的 Token,如 JWT(JSON Web Token)的签名部分。

HMAC-SHA256 的优点

  1. 安全性强
    • 基于 SHA-256 的安全性,结合密钥使用,安全性更高。
  2. 对抗攻击
    • 有效抵抗暴力破解、哈希碰撞和中间人攻击。
  3. 跨平台支持
    • 各种编程语言和库都支持 HMAC-SHA256。
  4. 性能优越
    • 计算量小,适合高性能需求的场景。

HMAC 与其他加密算法的对比

特性HMAC-SHA256RSA 签名普通哈希
是否需要密钥
加密/认证用途认证认证和加密数据完整性校验
性能高效较慢高效
应用场景API 鉴权、消息验证数字签名、PKI文件校验、数据校验

常见问题

  1. HMAC-SHA256 与普通 SHA-256 有什么区别?
    • SHA-256 是一种单向哈希算法,用于生成固定长度的摘要值;
    • HMAC-SHA256 是基于 SHA-256 和密钥的消息认证码,除了生成摘要外,还能验证消息来源和完整性。
  2. HMAC-SHA256 的密钥如何管理?
    • 密钥必须严格保密,通常存储在安全的配置文件或密钥管理服务(如 AWS KMS)中。
  3. 是否需要 HTTPS?
    • HMAC-SHA256 可以防止消息篡改,但不能保护消息的机密性。建议配合 HTTPS 使用。
  4. 可以用 HMAC-SHA256 替代 RSA 签名吗?
    • 如果双方共享密钥,HMAC-SHA256 是更高效的选择;
    • 如果需要非对称签名或验证,应该使用 RSA。

代码示例

客户端示例

假设这是一个Java客户端(可能是后端服务或桌面应用等),要调用你的服务接口 /api/secure,并用 HMAC-SHA256 做签名。

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;public class HmacClientExample {public static void main(String[] args) throws Exception {// 1) 准备必要参数String accessKeyId = "myKeyId";String accessKeySecret = "myKeySecret"; // 保密String method = "POST";String path = "/api/secure";// 例如携带一个 timestamp (yyyyMMddHHmmss)String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));// 2) 如果有请求体,需要计算 bodyHash (这里只是示例)//   实际可对 JSON 字符串做 MD5 或 SHA256,再 Hex 或 Base64String requestBody = "{\"foo\":\"bar\"}"; // JSONString bodyHash = sha256Hex(requestBody);// 3) 拼装 StringToSign (示例逻辑,可自定义)//    这里用换行分隔 method, path, timestamp, bodyHashString stringToSign = method + "\n" + path + "\n" + timestamp + "\n" + bodyHash;// 4) 做 HMAC-SHA256String signature = hmacSha256Base64(stringToSign, accessKeySecret);// 5) 将签名和 keyId、timestamp 放到 HTTP 头部//    伪代码: 构建 HTTP 请求System.out.println("X-AccessKeyId: " + accessKeyId);System.out.println("X-Timestamp: " + timestamp);System.out.println("X-Signature: " + signature);// 之后再把 requestBody 当作 JSON 发出 (POST)// ...// 这是示例演示,真实项目中可用 HttpClient、OkHttp 等发请求}/*** 计算字符串的 SHA-256 再转 hex (可选:也可用 Base64)*/private static String sha256Hex(String data) throws Exception {MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] bytes = digest.digest(data.getBytes(StandardCharsets.UTF_8));return bytesToHex(bytes);}/*** HMAC-SHA256 + Base64*/private static String hmacSha256Base64(String data, String secret) throws Exception {Mac mac = Mac.getInstance("HmacSHA256");SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");mac.init(keySpec);byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(rawHmac);}private static String bytesToHex(byte[] bytes) {StringBuilder sb = new StringBuilder(bytes.length * 2);for (byte b : bytes) {sb.append(String.format("%02x", b));}return sb.toString();}
}
  • 核心StringToSign 拼装 + HMAC-SHA256 计算签名 + 在请求头中带上 accessKeyIdtimestampsignature
  • bodyHash 的计算方式可自行定义,也可以用 MD5、直接放明文 body 等。只要客户端和服务端保持一致即可。

服务端示例 (Spring Boot)

下面以 Spring Boot + Controller 为例,展示如何验证签名。主要逻辑:

  1. 从 HTTP 头中取 X-AccessKeyId, X-Timestamp, X-Signature
  2. 根据 accessKeyId 找到 secret;
  3. 用相同的方式拼装 StringToSign
  4. 做同样的 HMAC-SHA256 计算;
  5. 比对与客户端传来的 signature 是否相同。

2.1 Controller 示例

@RestController
@RequestMapping("/api")
public class SecureApiController {// 示例:内存中保存 keyId -> keySecret 映射private Map<String, String> keyStore = new HashMap<>();public SecureApiController() {// 假设这里初始化了一个myKeyId -> myKeySecretkeyStore.put("myKeyId", "myKeySecret");}@PostMapping("/secure")public ResponseEntity<?> secureEndpoint(HttpServletRequest request,@RequestBody(required=false) String body // raw JSON) {try {// 1) 从header读取String accessKeyId = request.getHeader("X-AccessKeyId");String timestamp = request.getHeader("X-Timestamp");String clientSignature = request.getHeader("X-Signature");if (accessKeyId == null || timestamp == null || clientSignature == null) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Missing auth headers");}// 2) 查找keySecretString keySecret = keyStore.get(accessKeyId);if (keySecret == null) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid accessKeyId");}// 3) 计算 bodyHash(可选)//    假设客户端用了 sha256Hex(body)String bodyHash = sha256Hex(body == null ? "" : body);// 4) 与客户端相同的拼接方式String method = request.getMethod(); // "POST"String path = request.getRequestURI(); // "/api/secure"// StringToSignString stringToSign = method + "\n" + path + "\n" + timestamp + "\n" + bodyHash;// 5) 服务端做 HMAC-SHA256String serverSignature = hmacSha256Base64(stringToSign, keySecret);// 6) 比对签名if (!serverSignature.equals(clientSignature)) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Signature mismatch");}// 7) 可选校验: timestamp 是否过期if (!checkTimestampValid(timestamp)) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Timestamp expired or invalid");}// 8) 一切正常return ResponseEntity.ok("Success! Request body was: " + body);} catch (Exception e) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Auth error: " + e.getMessage());}}// 计算 SHA256Hexprivate String sha256Hex(String data) throws Exception {MessageDigest md = MessageDigest.getInstance("SHA-256");byte[] digest = md.digest(data.getBytes(StandardCharsets.UTF_8));return bytesToHex(digest);}// HMAC-SHA256 + Base64private String hmacSha256Base64(String data, String secret) throws Exception {Mac mac = Mac.getInstance("HmacSHA256");SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");mac.init(keySpec);byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(rawHmac);}private String bytesToHex(byte[] bytes) {StringBuilder sb = new StringBuilder(bytes.length * 2);for (byte b : bytes) {sb.append(String.format("%02x", b));}return sb.toString();}// 时间戳校验 (±15分钟示例)private boolean checkTimestampValid(String timestampStr) {try {// 这里假设 timestampStr 是 yyyyMMddHHmmssDateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");LocalDateTime reqTime = LocalDateTime.parse(timestampStr, fmt);LocalDateTime now = LocalDateTime.now();return !reqTime.isBefore(now.minusMinutes(15)) && !reqTime.isAfter(now.plusMinutes(15));} catch (Exception e) {return false;}}
}
  • 注意:上面为了演示方便,用 @RequestBody(required=false) String body 直接拿到原始 JSON 字符串,再做 sha256Hex;如果是对象映射,你要注意读取流计算摘要的先后顺序。
  • 你也可以在 FilterInterceptor 里做这个签名验签逻辑,避免在每个 Controller 里写。
  • timestamp 校验 + 可能的 nonce 防重放(可用 Redis 记录 5 分钟内出现过的 (accessKeyId,timestamp,nonce)),以更好地防御重复调用。

总结

  1. 客户端
    • 准备 accessKeyId, accessKeySecret
    • 拼出 StringToSign(通常包含 method、path、timestamp、bodyHash 等);
    • HMAC-SHA256( StringToSign, accessKeySecret ) → signature
    • 在 HTTP 请求头里带上 accessKeyId, signature, timestamp
    • 用 JSON 作为请求体时,别忘了和服务端在 bodyHash 算法上保持一致。
  2. 服务端
    • 通过 accessKeyId 找到对应的 accessKeySecret
    • 按同样规则构造 StringToSign
    • 计算 HMAC-SHA256 并和客户端的 signature 对比;
    • 一致则通过,不一致则 401/403;
    • 可加时间戳nonce限流 等加强安全性。
  3. 优点
    • 不需要公钥/私钥,也不需要RSA 加解密;
    • 计算速度快、实现相对简单;
    • 通用性强,许多云厂商、API网关都采用类似 HMAC 签名模式。
  4. 注意
    • 一定要保护好 accessKeySecret,客户端泄露就会被冒用。
    • 使用 HTTPS 来保证传输安全,防止中间人截获签名或篡改。
    • 若对大文件、流式上传等,需要在数据处理上稍作适配(hash可能需分段计算)。

这套 HMAC-SHA256 签名鉴权就是在很多云服务(阿里云、AWS、腾讯云)都在用的模式。只要客户端与服务端约定好StringToSign 的拼装方式、accessKeyId → secret 映射、时间戳/nonce防重放,就能形成一套轻量、高效的对外接口鉴权机制。

相关文章:

Spring Boot对访问密钥加解密——HMAC-SHA256

HMAC-SHA256 简介 HMAC-SHA256 是一种基于 哈希函数 的消息认证码&#xff08;Message Authentication Code, MAC&#xff09;&#xff0c;它结合了哈希算法&#xff08;如 SHA-256&#xff09;和一个密钥&#xff0c;用于验证消息的完整性和真实性。 HMAC 是 “Hash-based M…...

HTML 元素:网页构建的基础

HTML 元素:网页构建的基础 HTML(HyperText Markup Language,超文本标记语言)是构建网页的基石。它定义了网页的结构和内容,而HTML元素则是构成HTML文档的基石。在本篇文章中,我们将深入探讨HTML元素的概念、类型、用法,以及如何在网页设计中有效地使用它们。 什么是HT…...

HEIC 是什么图片格式?如何把 iPhone 中的 HEIC 转为 JPG?

在 iPhone 拍摄照片时&#xff0c;默认的图片格式为 HEIC。虽然 HEIC 格式具有高压缩比、高画质等优点&#xff0c;但在某些设备或软件上可能存在兼容性问题。因此&#xff0c;将 HEIC 格式转换为更为通用的 JPG 格式就显得很有必要。本教程将介绍如何使用简鹿格式工厂&#xf…...

爆肝1个月:DDR4 的信号完整性(万字长文SI)

前言&#xff1a; 大学里面&#xff0c;总有很多课程&#xff0c;很浪费时间&#xff0c;学了没点用处&#xff0c;问过老师&#xff0c;为什么信号完整性&#xff0c;示波器使用等课程不开呢&#xff0c;这种是对工作真实有帮助的&#xff1f; 老师&#xff1a;因为老师…...

前端js验证码插件

相关代码,在最上方的绑定资源...

关于easy-es对时间范围查询遇到的小bug

前言&#xff1a;在使用easy-es之前作为一个小白的我只有es原生查询的基础&#xff0c;在自己通过查看官方文档自学easy-es遇到了一个挫折&#xff0c;其他的还好语法和MybatisPlus差不多&#xff0c;正以为我觉得很快就能入手&#xff0c;在对时间范围的判断就给我当头一棒&am…...

Mask R-CNN

目录 摘要 Abstract Mask R-CNN 网络架构 Backbone RPN Proposal Layer ROIAlign bbox检测 Mask分割 损失计算 实验复现 总结 摘要 Mask R-CNN是在Faster R-CNN的基础上进行改进的目标检测和实例分割网络。Faster R-CNN主要用于目标检测&#xff0c;输出对象的边…...

大模型-Dify使用笔记

大模型-Dify使用笔记 0、调整docker镜像源1、安装1、Docker Compose方式部署 2、访问 Dify 0、调整docker镜像源 由于墙的存在&#xff0c;所以默认的docker镜像源很难拉取项目&#xff0c;需要调整相关的docker配置文件 vim /etc/docker/daemon.json添加如下docker镜像源 { …...

Suno Api V4模型无水印开发「综合实战开发自己的音乐网站」 —— 「Suno Api系列」第14篇

历史文章 Suno AI API接入 - 将AI音乐接入到自己的产品中&#xff0c;支持120并发任务 Suno Api V4模型无水印开发「灵感模式」 —— 「Suno Api系列」第1篇 Suno Api V4模型无水印开发「自定义模式」 —— 「Suno Api系列」第2篇 Suno Api V4模型无水印开发「AI生成歌词」…...

云原生架构中的中间件容器化:优劣势与实践探索

在云原生架构逐步推进的过程中&#xff0c;许多企业已经开始将应用和服务容器化&#xff0c;以充分利用云计算带来的弹性和自动化。随着容器技术的发展&#xff0c;容器化不仅仅限于应用层&#xff0c;越来越多的中间件也被考虑纳入容器化范畴&#xff0c;包括Redis、Kafka、Ra…...

如何测试模型推理性能:从零开始的Python指南

如何测试模型推理性能&#xff1a;从零开始的Python指南 什么是模型推理性能&#xff1f;测试模型推理性能的步骤1. 监测内存使用情况2. 测试模型吞吐量 运行测试总结 在机器学习和深度学习中&#xff0c;模型的推理性能是一个非常重要的指标。它可以帮助我们了解模型在实际应用…...

我们来学activiti -- bpmn

bpmn 题记bpmn结余 题记 在《Activiti很难学》提到学习知识点需要面对的思想钢印问题 按常见步骤&#xff0c;先展示下官方的客套话 BPMN&#xff08;Business Process Model and Notation&#xff09;是一种业务流程建模符号&#xff0c; 它是一种图形化的语言&#xff0c;用…...

【每日学点鸿蒙知识】节点析构问题、区分手机和pad、 Navigation路由问题、Tabs组件宽度、如何监听Map

1、HarmonyOS 只调用根节点的dispose,是否其下的子节点都能析构掉还是需要遍历子节点&#xff0c;都执行dispose才能正常析构&#xff1f; 前端持有引用关系的需要dispose&#xff0c;new出来的builderNode和FrameNode也需要dispose。只调用根节点的dispose,无法保证其下的子节…...

敏捷测试文化的转变

敏捷文化是敏捷测试转型的基础&#xff0c;只有具备敏捷文化的氛围&#xff0c;对组织架构、流程和相关测试实践的调整才能起作用。在前面的敏捷测试定义中&#xff0c;敏捷测试是遵从敏捷软件开发原则的一种测试实践&#xff0c;这意味着敏捷的价值观。 此外&#xff0c;从传…...

如何配置线程池参数,才能创建性能最好、最稳定的Spring异步线程池?

配置性能最好、最稳定的Spring异步线程池&#xff0c;需要综合考虑业务场景、硬件资源&#xff08;CPU核心数、内存等&#xff09;、并发量、任务特性&#xff08;CPU密集型、IO密集型等&#xff09;以及线程池参数。 以下是优化线程池配置的关键点及代码示例&#xff1a; 线程…...

【时间之外】IT人求职和创业应知【80】-特殊日子

目录 北京冬季招聘会 OpenAI CEO炮轰马斯克 英伟达推出全新AI芯片B300 莫欢喜&#xff0c;总成空。本周必须要谨行慎言。 感谢所有打开这个页面的朋友。人生不如意&#xff0c;开越野车去撒野&#xff0c;会害了自己&#xff0c;不如提升自己。提升自己的捷径就是学习和思考…...

Vue中接入萤石等直播视频(更新中ing)

一、萤石&#xff1a; 1. 萤石云开发文档&#xff1a; https://open.ys7.com/help/31 2、安装&#xff1a; npm install ezuikit-js --save 3、在文件中引用&#xff1a;import EZUIKit from ezuikit-js 4、具体代码&#xff1a; 获取accessToken&#xff1a;https://open.…...

如何学习、使用Ai,才能跟上时代的步伐?

目录 1. 打好基础&#xff1a;理解AI的核心概念 2. 学习AI的核心领域 3. 实践&#xff1a;动手做项目&#xff0c;积累经验 4. 利用AI工具提升工作效率 5. 培养AI思维与批判性思维 6. 关注AI领域的最新研究与趋势 7. 培养跨学科能力 总结&#xff1a; 在AI时代&#xf…...

RabbitMQ中的异步Confirm模式:提升消息可靠性的利器

在现代分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff09;扮演着至关重要的角色&#xff0c;它能够解耦系统组件、提高系统的可扩展性和可靠性。RabbitMQ作为一款广泛使用的消息队列中间件&#xff0c;提供了多种机制来确保消息的可靠传递。其中&#xff…...

Linux(Centos 7.6)目录结构详解

Linux(Centos 7.6)是一个操作系统&#xff0c;其核心设计理念是将一切资源抽象为文件&#xff0c;即一切皆文件。比如系统中的硬件设备硬盘、网络接口等都被视为文件。Windows系统一般是分为C、D、E盘。而Linux(Centos 7.6)是以斜线"/"作为文件系统的开始目录&#x…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...