Astra框架:自动化REST API安全测试的DevSecOps实践指南

1. 项目概述:为什么我们需要Astra这样的工具?

如果你和我一样,长期在Web应用开发或安全测试的一线工作,那你一定对API安全这个“老大难”问题深有体会。REST API作为现代微服务、前后端分离架构的通信基石,其安全性直接关系到整个系统的命脉。但现实是,传统的安全测试方法——无论是手动渗透测试还是依赖通用Web扫描器——在面对海量、动态、结构复杂的API时,常常显得力不从心。手动测试耗时耗力,覆盖不全;通用扫描器又往往“水土不服”,无法理解API特有的业务逻辑和数据流,误报漏报一大堆,最后还得靠人肉去甄别,测试效率极其低下。

正是在这种背景下,我第一次接触到Astra。它被定位为一款“革命性的REST API自动化安全测试框架”,这个名头起初让我有些怀疑。但经过几个实际项目的深度使用和踩坑后,我不得不承认,它在解决上述痛点上的确有一套独特的思路。Astra的核心设计理念,是真正将API视为一等公民,而不仅仅是Web应用的一个子集。它通过解析OpenAPI/Swagger规范、爬取API端点、理解参数类型和业务上下文,来驱动智能化的安全测试用例生成与执行。这意味着,它不再是漫无目的地“盲打”,而是有的放矢地进行测试,其效率和精准度是传统工具难以比拟的。

简单来说,Astra能为你做什么?它能自动化地帮你发现REST API中常见的、甚至一些深层次的安全漏洞,比如未授权访问、注入漏洞(SQLi、NoSQLi、命令注入等)、敏感信息泄露、不安全的直接对象引用(IDOR)、逻辑缺陷等。更重要的是,它旨在融入CI/CD流水线,实现安全左移,让安全测试像单元测试一样成为开发流程中自然而然的一环。无论你是开发人员、安全工程师还是DevOps,如果你正在为如何高效、可靠地保障API安全而头疼,那么深入了解并掌握Astra,无疑是一项极具价值的技术投资。

2. Astra框架的核心设计思路与架构拆解

要玩转一个工具,不能只停留在“怎么用”的层面,必须理解其背后的设计哲学和架构。这能帮助你在遇到复杂场景时,做出正确的配置和扩展决策。

2.1 从“扫描器”到“测试框架”的思维转变

很多安全工具自称“框架”,但骨子里还是个“扫描器”。Astra的不同之处在于,它从一开始就被设计成一个可编程、可扩展的测试框架。它的工作流可以概括为四个核心阶段:发现(Discovery) -> 解析(Parsing) -> 测试(Testing) -> 报告(Reporting)

  1. 发现阶段:Astra不仅支持直接导入标准的OpenAPI/Swagger规范文件(JSON/YAML),还能主动爬取运行中的API端点。这个爬取过程是智能的,它会尝试识别API的路由结构、HTTP方法(GET, POST, PUT, DELETE等),并记录下观察到的请求/响应模式,为后续测试构建一个动态的API地图。
  2. 解析阶段:这是Astra的“大脑”。它会深度解析API的定义,包括端点路径、请求参数(查询参数、路径参数、请求体)、参数数据类型(string, integer, array, object)、可能的枚举值、认证方式(API Key, OAuth2, JWT等)。基于这些信息,Astra会构建一个内部模型,这个模型是它生成针对性测试用例的基础。例如,知道某个参数是整数型ID,Astra就会重点测试IDOR漏洞;知道某个字段接收用户输入,就会尝试各种注入Payload。
  3. 测试阶段:Astra内置了一个丰富的安全测试插件库。每个插件针对一类特定的漏洞。框架会根据解析出的API模型,智能地调度相关的插件去测试相应的端点。例如,对于所有接收字符串输入的参数,SQL注入插件会被触发;对于所有需要认证的端点,未授权访问测试插件会尝试绕过认证。测试过程是高度并发的,以提升效率。
  4. 报告阶段:测试完成后,Astra会生成结构化的详细报告,通常包括HTML和JSON格式。报告会清晰列出发现的漏洞、风险等级、受影响的端点、触发漏洞的请求详情以及修复建议。好的报告能直接指导开发人员进行修复。

这个流程的核心思想是“基于理解的测试”。Astra试图理解API的契约和上下文,从而让自动化测试变得更聪明,减少无意义的噪音。

2.2 模块化架构与扩展性

Astra采用模块化设计,主要组件包括:

  • 核心引擎(Core Engine):负责协调整个测试流程,管理插件生命周期,处理并发和资源调度。
  • 连接器(Connectors):负责与API交互。除了标准的HTTP客户端,还可能包括处理GraphQL、gRPC(如果未来支持)或特定认证协议(如处理JToken刷新)的专用连接器。
  • 解析器(Parsers):专门用于解析不同格式的API定义文件,如OpenAPI 3.0、Swagger 2.0、Postman集合等。这是其理解API的入口。
  • 测试插件(Test Plugins):这是Astra的能力核心。每个插件独立负责检测一种或一类漏洞。例如:sql_injection.py,xss.py,auth_bypass.py,idor.py等。这种设计使得社区贡献新漏洞检测方法变得非常容易。
  • 报告生成器(Report Generators):将测试结果转换为人类可读和机器可读的格式。

这种架构带来的最大好处是扩展性。如果你公司的API使用了一种特殊的认证机制,或者有一种业务逻辑漏洞模式是Astra现有插件覆盖不到的,你可以相对轻松地编写自己的连接器或测试插件,集成到Astra的框架中,复用它的发现、调度和报告能力。这才是“框架”的真正威力。

注意:虽然Astra的设计很先进,但它并非银弹。它严重依赖于API规范文件的准确性和完整性。如果你们的API文档陈旧或缺失,Astra的“理解”就会出偏差,测试效果会大打折扣。因此,推动团队维护良好的API文档,本身也是提升安全性的重要一步。

3. 从零开始:Astra的安装、配置与踩坑实录

理论讲得再多,不如动手实践。这一部分,我将带你完整走一遍Astra的安装和初步配置流程,并分享我遇到的那些“坑”以及填坑方法。

3.1 环境准备与安装

Astra通常是一个Python工具,因此首先需要Python环境(建议3.7及以上版本)。最直接的安装方式是通过pip安装其开源版本。

# 1. 创建并激活一个虚拟环境(强烈推荐,避免污染系统Python环境) python -m venv astra-env source astra-env/bin/activate # Linux/macOS # 或 .\astra-env\Scripts\activate # Windows # 2. 使用pip安装Astra pip install astra-security-scanner

听起来很简单,对吧?但这里就是第一个坑。网络问题与依赖冲突。由于Astra依赖不少安全相关的库(如requests,pyyaml,lxml,可能还有cryptography等),在复杂的网络环境下或与其他项目共存时,很容易安装失败。

实操心得1:解决依赖安装失败

  • 使用国内镜像源:如果下载慢或超时,使用清华、阿里云等镜像。
    pip install astra-security-scanner -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 升级pip和setuptools:老版本的包管理工具可能无法正确处理某些依赖。
    pip install --upgrade pip setuptools wheel
  • 分步安装:如果直接安装失败,可以尝试先安装核心依赖。
    pip install requests pyyaml pip install astra-security-scanner
  • 注意系统依赖:在Linux系统上,某些底层库(如用于XML解析的libxml2)可能需要通过系统包管理器先安装。例如在Ubuntu上:sudo apt-get install libxml2-dev libxslt1-dev

安装成功后,可以通过命令行验证:

astra --version

或者

astra --help

查看支持的命令和选项。

3.2 基础配置与首次扫描

假设我们有一个正在运行的待测API,并且它提供了一份OpenAPI规范文件swagger.json。最简单的扫描命令如下:

astra -o swagger.json -t http://your-api-server.com
  • -o--openapi: 指定OpenAPI规范文件路径。
  • -t--target: 指定API的基础URL。

执行后,Astra会开始工作。但首次运行,你很可能会遇到以下几个典型问题:

常见问题1:证书验证错误如果目标API使用自签名证书或内部CA签发的证书,Astra(底层使用requests库)可能会抛出SSLError

  • 解决方案:对于测试环境,可以添加--insecure-k参数来跳过SSL证书验证(生产环境慎用)。
    astra -o swagger.json -t https://your-api-server.com --insecure

常见问题2:认证失败大部分API都需要认证(如API Key、JWT、OAuth2)。Astra支持多种认证方式,但需要在配置中正确设置。

  • 解决方案:使用-H参数添加认证头。例如,对于Bearer Token:
    astra -o swagger.json -t http://your-api-server.com -H "Authorization: Bearer your_jwt_token_here"
    对于更复杂的认证流程(如需要先调用登录接口获取token),Astra通常支持通过配置文件或自定义脚本进行认证预处理。你需要查阅其文档,编写一个认证插件或使用--auth相关参数指定认证流程。

常见问题3:扫描速度过快导致被限流或封禁Astra的并发测试可能会对API服务器造成较大压力,触发服务器的速率限制或WAF(Web应用防火墙)的防御规则。

  • 解决方案
    • 使用--delay参数在请求之间添加延迟(毫秒)。
    • 使用--threads参数减少并发线程数。
    • 与运维或安全团队协调,将测试IP加入白名单,或在测试环境进行扫描。

实操心得2:配置文件是你的好朋友对于复杂的扫描任务,每次都输入一长串命令行参数非常麻烦且容易出错。Astra支持使用YAML或JSON格式的配置文件。

创建一个astra_config.yaml文件:

target: "http://your-api-server.com" openapi: "./swagger.json" headers: Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." User-Agent: "Astra-Security-Scanner/1.0" insecure: true # 仅用于测试环境 plugins: - "sql_injection" - "xss" - "idor" - "auth_bypass" # 排除某些敏感端点,如注销或删除接口,避免测试造成破坏 exclude_paths: - "/api/v1/users/logout" - "/api/v1/users/*/delete"

然后运行:

astra -c astra_config.yaml

使用配置文件不仅使命令更简洁,也便于版本控制和团队共享扫描策略。

4. 深入核心:Astra测试策略与插件机制详解

掌握了基础用法后,我们需要深入Astra的“心脏”,了解它如何工作,以及如何让它更好地为我们服务。

4.1 测试插件的运作原理与调度

Astra的每个测试插件都是一个独立的Python模块。当引擎决定对某个API端点进行测试时,它会遍历所有启用的插件,检查该插件是否“感兴趣”。

插件“感兴趣”的判定通常基于:

  1. HTTP方法:例如,一个测试CSRF的插件可能只对状态改变请求(POST, PUT, DELETE)感兴趣。
  2. 参数类型:SQL注入插件会寻找字符串类型的参数(查询参数、请求体中的字段)。
  3. 端点上下文:根据URL路径关键词(如/api/users/{id}可能触发IDOR测试)或响应内容进行判断。

插件被触发后,它会接收当前端点的详细信息(URL、方法、参数、头部等),然后生成一系列变异后的请求。例如,SQL注入插件会生成包含' OR '1'='1sleep(5)等Payload的请求,替换掉原始参数值,然后发送这些请求并分析响应。

引擎会并发地发送这些测试请求,并收集响应。插件负责分析响应状态码、响应时间、响应体内容、错误信息等,来判断是否存在漏洞。例如,如果响应中包含数据库错误信息(如MySQL、PostgreSQL的错误栈),SQL注入插件就会标记一个高危漏洞。

4.2 关键配置参数与性能调优

要让Astra在复杂环境中稳定高效运行,理解并调整以下几个关键参数至关重要:

参数说明建议与调优
--threads并发线程数。默认值可能较高(如20)。对于敏感或性能较差的目标,建议从较低值(如5)开始,逐步增加。线程数过高可能导致网络连接耗尽或目标服务瘫痪。
--delay每个请求之间的延迟(毫秒)。用于规避速率限制。如果扫描触发429(Too Many Requests)错误,应增加此值,例如设置为--delay 500
--timeout单个请求的超时时间(秒)。对于响应慢的API或进行时间盲注测试时,需要适当调高,例如--timeout 30
--depth爬虫爬取链接的深度。当不使用OpenAPI文件而纯靠爬虫发现API时使用。设置过深可能导致扫描范围失控和时间过长。通常1-3足矣。
--plugins/--exclude-plugins包含或排除特定插件。如果确定目标API不存在某类漏洞(如目标为纯JSON API,无XSS风险),可以排除xss插件以加快扫描速度。反之,可以只启用关心的插件进行快速检查。
--scan-mode扫描模式,如fast,normal,fullfast模式运行基础、快速的测试;full模式会进行更耗时、更深入的测试(如大量Payload的模糊测试)。根据测试窗口时间选择。

实操心得3:制定合理的扫描策略不要一上来就对生产环境进行“全量深度扫描”。我的建议是:

  1. 分阶段扫描
    • 阶段一(快速):在开发或测试环境,对每次代码提交后的新构建,运行fast模式扫描,作为CI/CD门禁。
    • 阶段二(完整):在预发布环境或定期的安全测试日,运行full模式扫描,进行深度检查。
    • 阶段三(定向):针对已识别的风险模块或发生过安全问题的API,使用特定插件组合进行定向强化测试。
  2. 利用排除列表:务必在配置文件中维护一个exclude_paths列表,将注销、删除、支付回调等可能产生副作用或破坏数据的接口排除在外。安全测试的原则是“只读”或“可逆”,避免造成业务影响。
  3. 关注误报:自动化工具必然有误报。初期要花时间分析Astra的报告,将误报归类。有些误报可以通过调整插件敏感度阈值、添加自定义规则来消除。建立一个“已知误报”清单,后续扫描时可以快速过滤。

5. 集成与进阶:将Astra融入DevSecOps流水线

一个工具的价值,不仅在于其本身的能力,更在于它能否融入现有工作流,持续产生价值。将Astra集成到CI/CD流水线中,是实现安全左移、打造DevSecOps文化的关键一步。

5.1 与Jenkins/GitLab CI/CD的集成

核心思路是将Astra扫描作为一个独立的CI阶段或Job来执行。以下是一个GitLab CI.gitlab-ci.yml的示例:

stages: - build - test - security-scan - deploy api-security-scan: stage: security-scan image: python:3.9-slim # 使用包含Python的Docker镜像 before_script: - pip install astra-security-scanner # 假设我们的API规范文件在项目根目录,并且测试服务器已启动 # 通常需要先启动一个测试环境的服务容器,这里简化处理 script: - | # 运行Astra扫描,输出JSON报告 astra -o ./docs/openapi.json -t $TEST_API_URL \ --config ./astra_config.yaml \ --report-json ./astra-report.json \ --report-html ./astra-report.html after_script: - | # 解析JSON报告,如果发现高危漏洞,则让Job失败 # 可以使用jq工具来解析JSON if [ -f ./astra-report.json ]; then HIGH_VULNS=$(python -c "import json; data=json.load(open('./astra-report.json')); print(sum(1 for v in data.get('vulnerabilities', []) if v.get('risk') == 'High'))") if [ "$HIGH_VULNS" -gt 0 ]; then echo "发现 $HIGH_VULNS 个高危漏洞,流水线终止!" cat ./astra-report.html # 在日志中输出报告摘要(可选) exit 1 else echo "安全扫描通过,未发现高危漏洞。" fi else echo "扫描报告未生成,扫描可能失败。" exit 1 fi artifacts: paths: - ./astra-report.html - ./astra-report.json expire_in: 1 week only: - main # 仅对主分支进行安全扫描 - merge_requests # 对合并请求也进行扫描,提前发现问题

在这个配置中:

  1. 我们定义了一个security-scan阶段。
  2. 在Job中安装Astra,然后针对测试环境的API($TEST_API_URL是一个预定义的CI变量,指向已部署的测试实例)进行扫描。
  3. 扫描生成JSON和HTML报告。
  4. after_script中,我们编写了一个简单的脚本,使用Python解析JSON报告,检查是否有“高危(High)”级别的漏洞。如果有,则通过exit 1让Job失败,从而阻断流水线,防止不安全的代码进入下一阶段或生产环境。
  5. 将报告文件保存为制品(artifacts),供后续下载查看。
  6. 通过only规则控制扫描触发时机,通常在合并到主分支或创建合并请求时触发。

注意事项:你需要确保测试环境的API服务在扫描Job运行时是可用的。这通常可以通过在CI中先启动一个服务容器(docker-compose up)来实现,或者扫描一个独立部署的、稳定的测试环境。

5.2 结果管理与趋势分析

单纯的单次扫描报告价值有限。我们需要建立持续的安全状态监控和趋势分析。

  1. 报告集中化:不要将报告散落在各个CI Job的制品里。可以将Astra生成的JSON报告推送到一个集中的存储或安全管理平台,如:
    • 内部Wiki或文档系统:定期归档。
    • 安全信息和事件管理(SIEM)系统:将漏洞作为安全事件纳入监控。
    • 专门的应用安全平台:如DefectDojo、Mozilla的Observatory(需适配)等,这些平台可以聚合多源安全数据,跟踪漏洞生命周期(从发现到修复)。
  2. 趋势跟踪:通过对比历史扫描报告,可以跟踪以下指标:
    • 漏洞总数趋势:是在增加还是减少?
    • 高危漏洞修复周期:从发现到修复平均需要多长时间?
    • 复发漏洞:同一类漏洞是否在不同API或不同时间反复出现?这可能指向开发流程或培训的短板。
  3. 与工单系统集成:当Astra发现高危漏洞时,可以自动在Jira、GitLab Issues等系统中创建工单,并分配给相应的开发团队或负责人,实现漏洞管理的自动化流转。

5.3 自定义插件开发:应对特定业务逻辑漏洞

Astra内置的通用漏洞检测插件很强,但无法覆盖业务逻辑层面的漏洞,例如:

  • 绕过业务流程顺序(未下单先支付)。
  • 权限提升(普通用户访问管理员接口)。
  • 竞争条件(并发请求导致的库存超卖)。

这时,就需要开发自定义插件。Astra的插件架构使得这个过程相对标准化。

一个最简单的自定义插件骨架如下(假设我们想检测一个“优惠券重复使用”的逻辑漏洞):

# 文件名: custom_coupon_reuse.py import logging from astra.core.plugin_base import AstraPlugin class CouponReusePlugin(AstraPlugin): """检测优惠券是否可以重复使用的业务逻辑漏洞插件。""" # 插件名称和类别 name = "coupon_reuse" category = "business_logic" def __init__(self): super().__init__() self.logger = logging.getLogger(__name__) def is_applicable(self, endpoint, method, params, **kwargs): """判断此插件是否适用于当前端点。 我们只关心使用优惠券的订单提交接口。""" # 示例:如果端点路径包含 '/order/submit' 且方法是POST,并且参数中有 'coupon_code' if method.upper() == 'POST' and '/order/submit' in endpoint: # 这里需要更精细地检查params,取决于参数传递方式(JSON body, form-data等) # 假设我们从上下文中能获取到解析后的参数列表 if kwargs.get('parsed_body') and 'coupon_code' in kwargs['parsed_body']: return True return False def test(self, context): """执行测试逻辑。""" # context 包含了端点、会话、目标等信息 target_url = context['target'] + context['endpoint'] original_coupon_code = context['parsed_body']['coupon_code'] # 测试步骤1:正常使用一次优惠券 data = context['parsed_body'].copy() response1 = self.http_request('POST', target_url, json=data) if response1.status_code not in [200, 201]: self.logger.warning(f"首次请求失败: {response1.status_code}") return [] # 测试步骤2:尝试用同一个优惠券码再次请求 # 注意:这里需要处理会话/认证状态,确保是同一用户上下文。 # 假设context['session']是一个保持登录状态的requests.Session对象。 response2 = self.http_request('POST', target_url, json=data, session=context.get('session')) vulnerabilities = [] # 漏洞判定逻辑:如果第二次请求也成功(并且不是返回“优惠券已使用”的错误),则可能存在问题。 if response2.status_code == 200: # 进一步分析响应内容,确认订单是否再次创建成功 # 这里简化处理,仅通过状态码判断 vuln = { 'name': '优惠券重复使用漏洞', 'risk': 'Medium', # 风险等级 'description': '系统未有效校验优惠券的一次性使用限制,可能导致优惠券被重复使用。', 'endpoint': context['endpoint'], 'method': 'POST', 'evidence': f'首次请求响应: {response1.status_code}, 重复请求响应: {response2.status_code}。', 'solution': '在服务端对优惠券使用状态进行原子性校验,确保每个优惠券码在有效期内仅能使用一次。' } vulnerabilities.append(vuln) return vulnerabilities

开发完插件后,将其放到Astra的插件目录,或在配置文件中通过路径引入,Astra就会在扫描时加载并运行它。

注意:自定义插件开发需要对目标业务的API逻辑有深刻理解,并且测试行为要格外小心,避免对测试数据造成破坏或产生垃圾数据。建议在专门的、隔离的测试环境中进行这类深度业务逻辑测试。

6. 避坑指南与最佳实践总结

经过多个项目的实践,我总结了以下关键注意事项和最佳实践,希望能帮你少走弯路。

6.1 常见问题排查速查表

问题现象可能原因排查步骤与解决方案
扫描无结果或进度卡住1. 目标API不可达。
2. 认证失败,所有请求被403/401拦截。
3. 网络代理问题。
4. Astra自身Bug。
1. 用curl或Postman手动访问目标API,确认连通性。
2. 检查认证头是否正确,Token是否过期。开启Astra的调试日志(--verbose),查看原始请求和响应。
3. 检查系统代理设置,或为Astra配置代理(--proxy)。
4. 尝试简化扫描(单端点、单插件),定位问题。查看项目Issue列表。
误报率极高1. 插件敏感度阈值设置不当。
2. API的常规错误响应被误判为漏洞迹象。
3. WAF或网关返回了干扰性的统一错误页面。
1. 研究具体插件的配置选项,调整敏感度参数(如果支持)。
2. 分析误报案例,总结模式。通过Astra的排除规则(--exclude-signatures)过滤已知误报模式。
3. 尝试在扫描中设置特定的User-Agent或头,或与运维协调,在测试时临时绕过WAF的某些检测规则(需谨慎)。
扫描导致API服务异常或数据污染1. 测试Payload触发了业务逻辑bug。
2. 对写操作(POST/PUT/DELETE)接口进行了测试,创建了垃圾数据或删除了真实数据。
这是最严重的问题!
1.务必在测试环境进行扫描,并与生产环境隔离。
2.务必使用配置文件的exclude_paths,排除所有具有破坏性的端点。
3. 使用测试专用的账号和数据集,并确保有数据回滚机制。
4. 考虑在扫描前备份测试数据库。
报告不清晰或信息不足默认报告模板可能不符合团队需求。1. Astra通常支持自定义报告模板。研究其报告模块,定制HTML模板。
2. 利用JSON报告,编写自己的脚本进行二次分析和可视化,集成到内部仪表盘。
无法解析复杂的OpenAPI文件OpenAPI文件包含不规范的扩展或Astra不支持的特性。1. 使用OpenAPI验证工具(如Swagger Editor)检查规范文件的有效性。
2. 尝试简化OpenAPI文件,移除不必要的扩展x-字段。
3. 如果问题持续,考虑向Astra项目提交Issue,并提供简化后的问题文件示例。

6.2 安全测试最佳实践清单

  1. 环境隔离原则:永远不在生产环境进行主动安全扫描。建立独立的、与生产环境一致的测试环境(包括数据)。
  2. 最小权限原则:为Astra扫描使用的测试账号分配最小必要的权限。不要使用管理员账号。
  3. 无害化测试:精心设计测试数据,使用前缀(如test_astra_)标识测试创建的资源,并确保有清理机制。
  4. 沟通与协调:扫描前通知相关的开发和运维团队,告知扫描时间窗口、源IP地址和可能的影响。避免在业务高峰时段扫描。
  5. 持续集成,快速反馈:将Astra集成到CI/CD中,让安全反馈在开发早期就能触达开发者,修复成本最低。
  6. 漏洞管理闭环:建立流程,确保Astra发现的漏洞被记录、分配、修复和验证。工具发现漏洞只是开始,修复漏洞才是目的。
  7. 工具是辅助,人是核心:Astra等自动化工具不能替代安全工程师的深度分析和手动渗透测试。应将自动化扫描作为第一道防线和持续监控手段,再结合定期的人工安全审计,构成纵深防御体系。

最后,我想强调的是,引入Astra或任何自动化安全测试框架,不仅仅是一个技术决策,更是一个流程和文化变革的契机。它促使开发、测试、运维和安全团队更早地关注API安全,将安全内建于开发和交付流程之中。这个过程可能会有磨合期的阵痛,比如要应对误报、调整流程,但长期来看,它对提升整个组织的安全水位和研发效率,有着不可估量的价值。从我个人的经验看,坚持使用并不断优化这套流程的团队,其API的安全性和健壮性都会得到显著且持续的改善。