This is an automated email from the ASF dual-hosted git repository. yuluo-yx pushed a commit to branch 1008-yuluo/auth-defaults in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
commit c5cd8d51c7dd38362766e8c34d262994ef68bf15 Author: yuluo-yx <[email protected]> AuthorDate: Thu Oct 8 13:37:16 2026 +0800 [bugfix] Disable Digest authentication by default --- .github/workflows/backend-build-test.yml | 16 +- .github/workflows/docker-compose-config-test.yml | 6 +- e2e/README.md | 3 + e2e/auth_contract_test.py | 177 +++++++++++++++++++++ .../src/main/resources/application.yml | 1 - home/docs/start/docker-compose-deploy.md | 2 + home/docs/start/docker-deploy.md | 17 ++ home/docs/start/quickstart.md | 2 +- .../current/start/docker-compose-deploy.md | 2 + .../current/start/docker-deploy.md | 17 ++ .../current/start/quickstart.md | 2 +- script/application.yml | 1 - script/ci/check-quickstart-compose.sh | 23 ++- script/docker-compose/README.md | 10 ++ .../hertzbeat-mysql-iotdb/conf/application.yml | 1 - .../hertzbeat-mysql-tdengine/conf/application.yml | 1 - .../conf/application.yml | 1 - .../conf/application.yml | 1 - .../conf/application.yml | 1 - 19 files changed, 270 insertions(+), 14 deletions(-) diff --git a/.github/workflows/backend-build-test.yml b/.github/workflows/backend-build-test.yml index 3220c5fde5..c81d6e0858 100644 --- a/.github/workflows/backend-build-test.yml +++ b/.github/workflows/backend-build-test.yml @@ -112,11 +112,21 @@ jobs: push: false tags: apache/hertzbeat:test - - name: Run E2E + - name: Start E2E environment + working-directory: e2e run: | - cd e2e docker compose version - docker compose up --exit-code-from testing --remove-orphans + docker compose up -d --wait hertzbeat checker + + - name: Run authentication contract tests + working-directory: e2e + env: + SERVER: http://localhost:1157 + run: python3 auth_contract_test.py + + - name: Run API E2E tests + working-directory: e2e + run: docker compose up --exit-code-from testing --remove-orphans # upload application logs - name: Upload logs & API test reports diff --git a/.github/workflows/docker-compose-config-test.yml b/.github/workflows/docker-compose-config-test.yml index ed259afc39..f222c171a3 100644 --- a/.github/workflows/docker-compose-config-test.yml +++ b/.github/workflows/docker-compose-config-test.yml @@ -23,12 +23,16 @@ on: paths: - '.github/workflows/docker-compose-config-test.yml' - 'script/ci/check-quickstart-compose.sh' + - 'hertzbeat-startup/src/main/resources/application.yml' + - 'script/application.yml' - 'script/docker-compose/**' pull_request: branches: [ master, dev ] paths: - '.github/workflows/docker-compose-config-test.yml' - 'script/ci/check-quickstart-compose.sh' + - 'hertzbeat-startup/src/main/resources/application.yml' + - 'script/application.yml' - 'script/docker-compose/**' concurrency: @@ -44,5 +48,5 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - name: Validate quick-start listener bindings + - name: Validate quick-start configuration run: sh script/ci/check-quickstart-compose.sh diff --git a/e2e/README.md b/e2e/README.md index d372428010..342b837b96 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -13,6 +13,7 @@ Please add the corresponding e2e (aka end-to-end) test cases if you add or updat ``` * Run the E2E tests via [api-testing](https://github.com/LinuxSuRen/api-testing) + * `e2e/auth_contract_test.py` uses only the Python standard library to verify the default Basic/JWT behavior, including the absence of browser authentication challenges * The test cases run from top to bottom * You can add the necessary assertions there * Test data files are under `e2e/data/` @@ -28,6 +29,8 @@ Please follow these steps if you want to run the E2E tests locally. * Change the directory to `e2e`, then run: ```bash + docker compose up -d --wait hertzbeat checker + SERVER=http://localhost:1157 python3 auth_contract_test.py docker compose up --exit-code-from testing --remove-orphans ``` diff --git a/e2e/auth_contract_test.py b/e2e/auth_contract_test.py new file mode 100644 index 0000000000..c23b5708cd --- /dev/null +++ b/e2e/auth_contract_test.py @@ -0,0 +1,177 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""验证默认 HertzBeat 镜像的认证契约。""" + +import base64 +import json +import os +import sys +import urllib.error +import urllib.request + + +SERVER = os.getenv("SERVER", "http://localhost:1157").rstrip("/") +USERNAME = os.getenv("AUTH_USERNAME", "admin") +PASSWORD = os.getenv("AUTH_PASSWORD", "hertzbeat") +TIMEOUT = float(os.getenv("AUTH_TEST_TIMEOUT", "10")) +PROTECTED_RESOURCE = "/api/monitors?pageIndex=0&pageSize=1" + + +def request(path, method="GET", payload=None, authorization=None): + headers = {"Accept": "application/json"} + body = None + if payload is not None: + headers["Content-Type"] = "application/json" + body = json.dumps(payload).encode("utf-8") + if authorization is not None: + headers["Authorization"] = authorization + + req = urllib.request.Request( + f"{SERVER}{path}", data=body, headers=headers, method=method + ) + try: + with urllib.request.urlopen(req, timeout=TIMEOUT) as response: + return response.status, response.headers, response.read() + except urllib.error.HTTPError as error: + return error.code, error.headers, error.read() + + +def decode_json(body, context): + try: + return json.loads(body.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise AssertionError(f"{context} 未返回有效 JSON") from error + + +def assert_success_response(status, body, context): + if status != 200: + raise AssertionError(f"{context} HTTP 状态码应为 200,实际为 {status}") + data = decode_json(body, context) + if data.get("code") != 0: + raise AssertionError(f"{context} 业务状态码应为 0,实际为 {data.get('code')}") + return data.get("data") + + +def assert_no_authentication_challenge(headers, context): + challenges = headers.get_all("WWW-Authenticate", []) + if challenges: + schemes = [challenge.split(maxsplit=1)[0] for challenge in challenges] + raise AssertionError(f"{context} 不应返回 WWW-Authenticate,实际协议为 {schemes}") + + +def run_case(name, case): + try: + result = case() + print(f"PASS: {name}") + return result + except Exception as error: + print(f"FAIL: {name}: {error}", file=sys.stderr) + raise + + +def anonymous_access_is_rejected_without_challenge(): + status, headers, _ = request(PROTECTED_RESOURCE) + if status != 401: + raise AssertionError(f"匿名访问应返回 401,实际为 {status}") + assert_no_authentication_challenge(headers, "匿名访问") + + +def invalid_bearer_is_rejected_without_challenge(token): + parts = token.split(".") + if len(parts) != 3 or not parts[2]: + raise AssertionError("登录接口返回的 token 不是 JWT") + replacement = "A" if parts[2][0] != "A" else "B" + parts[2] = replacement + parts[2][1:] + invalid_token = ".".join(parts) + status, headers, _ = request( + PROTECTED_RESOURCE, authorization=f"Bearer {invalid_token}" + ) + if status != 401: + raise AssertionError(f"无效 JWT 应返回 401,实际为 {status}") + assert_no_authentication_challenge(headers, "无效 JWT") + + +def login(): + status, _, body = request( + "/api/account/auth/form", + method="POST", + payload={"type": 0, "identifier": USERNAME, "credential": PASSWORD}, + ) + data = assert_success_response(status, body, "表单登录") + if not isinstance(data, dict): + raise AssertionError("表单登录响应缺少 data 对象") + token = data.get("token") + refresh_token = data.get("refreshToken") + if not isinstance(token, str) or not token: + raise AssertionError("表单登录响应缺少 token") + if not isinstance(refresh_token, str) or not refresh_token: + raise AssertionError("表单登录响应缺少 refreshToken") + return token, refresh_token + + +def bearer_access_succeeds(token): + status, _, body = request( + PROTECTED_RESOURCE, authorization=f"Bearer {token}" + ) + assert_success_response(status, body, "JWT 访问") + + +def basic_access_succeeds(): + credentials = base64.b64encode(f"{USERNAME}:{PASSWORD}".encode("utf-8")) + status, _, body = request( + PROTECTED_RESOURCE, + authorization=f"Basic {credentials.decode('ascii')}", + ) + assert_success_response(status, body, "Basic 访问") + + +def refresh_token_succeeds(refresh_token): + status, _, body = request( + "/api/account/auth/refresh", + method="POST", + payload={"token": refresh_token}, + ) + data = assert_success_response(status, body, "刷新令牌") + if not isinstance(data, dict) or not data.get("token") or not data.get("refreshToken"): + raise AssertionError("刷新令牌响应缺少 token 或 refreshToken") + + status, _, body = request( + PROTECTED_RESOURCE, authorization=f"Bearer {data['token']}" + ) + assert_success_response(status, body, "刷新后的 JWT 访问") + + +def main(): + print(f"HertzBeat 认证契约测试: {SERVER}") + run_case("匿名访问返回 401 且不触发浏览器认证", anonymous_access_is_rejected_without_challenge) + token, refresh_token = run_case("表单登录返回访问令牌和刷新令牌", login) + run_case( + "无效 JWT 返回 401 且不触发浏览器认证", + lambda: invalid_bearer_is_rejected_without_challenge(token), + ) + run_case("有效 JWT 可访问受保护接口", lambda: bearer_access_succeeds(token)) + run_case("默认 Basic 认证可访问受保护接口", basic_access_succeeds) + run_case("刷新令牌可签发并使用新 JWT", lambda: refresh_token_succeeds(refresh_token)) + print("认证契约测试全部通过。") + + +if __name__ == "__main__": + try: + main() + except (AssertionError, OSError, urllib.error.URLError): + sys.exit(1) diff --git a/hertzbeat-startup/src/main/resources/application.yml b/hertzbeat-startup/src/main/resources/application.yml index c6cd53d4e1..db22a026a5 100644 --- a/hertzbeat-startup/src/main/resources/application.yml +++ b/hertzbeat-startup/src/main/resources/application.yml @@ -84,7 +84,6 @@ springdoc: sureness: container: jakarta_servlet auths: - - digest - basic - jwt jwt: diff --git a/home/docs/start/docker-compose-deploy.md b/home/docs/start/docker-compose-deploy.md index e21b01c78b..b219eb9df5 100644 --- a/home/docs/start/docker-compose-deploy.md +++ b/home/docs/start/docker-compose-deploy.md @@ -73,6 +73,8 @@ Run the `docker compose version` command to check if you have a Docker Compose e This setting also opens `1158` for remote Collectors; `14317` (OTLP/gRPC) is controlled separately by `HERTZBEAT_OTLP_BIND_ADDRESS`. See the `README.md` of the deployment solution for details. + The supplied `conf/application.yml` enables Basic and JWT authentication by default, and the Web UI uses JWT. Digest is opt-in. If you add `digest` to `sureness.auths`, unauthenticated protected APIs return a `WWW-Authenticate: Digest` challenge and a browser may display its native username/password dialog. Restart the HertzBeat container after changing the configuration. See [Authentication modes](docker-deploy#authentication-modes) for examples and TLS guidance. + ::: **HAVE FUN** diff --git a/home/docs/start/docker-deploy.md b/home/docs/start/docker-deploy.md index bdeebc70e0..c0202d5534 100644 --- a/home/docs/start/docker-deploy.md +++ b/home/docs/start/docker-deploy.md @@ -56,6 +56,23 @@ It is necessary to have Docker environment in your environment. If not installed 2. Start to explore HertzBeat Access [http://ip:1157/](http://ip:1157/) using browser. You can explore HertzBeat with default account `admin/hertzbeat` now! +### Authentication modes + +The default image enables Basic and JWT authentication. The Web UI signs in through the form endpoint and then uses JWT, while Basic remains available for API clients. Use TLS whenever credentials or tokens cross an untrusted network. + +Digest authentication is disabled by default. To change the enabled methods, download the complete [`application.yml`](https://github.com/apache/hertzbeat/raw/master/script/application.yml), edit its `sureness.auths` list, and mount the complete file as shown above. For example, enable Digest explicitly with: + +```yaml +sureness: + container: jakarta_servlet + auths: + - digest + - basic + - jwt +``` + +When Digest is enabled, an unauthenticated request to a protected API returns a `WWW-Authenticate: Digest` challenge. Opening such an API in a browser may therefore display the browser's native username/password dialog; this is an HTTP authentication prompt, not a TLS or certificate error. To use JWT only, set `auths` to a single `jwt` entry. Restart the container after changing the file. + ### Deploy HertzBeat Collector Cluster(Optional) :::note diff --git a/home/docs/start/quickstart.md b/home/docs/start/quickstart.md index d68cf330de..c26df67b5d 100644 --- a/home/docs/start/quickstart.md +++ b/home/docs/start/quickstart.md @@ -37,7 +37,7 @@ HertzBeat provides multiple installation options: ```docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat apache/hertzbeat``` -2. Access `http://localhost:1157` to start, default account: `admin/hertzbeat` +2. Access `http://localhost:1157` to start, default account: `admin/hertzbeat`. The default image enables Basic and JWT authentication; the Web UI uses JWT. Digest authentication is opt-in because its browser challenge can open a native username/password dialog. See [Install HertzBeat via Docker](docker-deploy#authentication-modes) for configuration details. 3. Deploy collector clusters(Optional) diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-compose-deploy.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-compose-deploy.md index 021cb28003..6076d22ed0 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-compose-deploy.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-compose-deploy.md @@ -71,6 +71,8 @@ sidebar_label: Docker Compose方式安装 快速启动方案默认将所有宿主机端口绑定到 `127.0.0.1`,因此 Web 页面只能在运行 Docker 的本机访问。如需从其他主机访问,请在 `.env` 中把 `HERTZBEAT_BIND_ADDRESS` 设置为其他主机可达的地址(建议通过 TLS 反向代理开放,而不是直接使用 `0.0.0.0` 这类通配地址),然后执行 `docker compose config` 检查最终端口映射,再重新执行 `docker compose up -d`。该变量同时会开放 `1158` 供远程 Collector 连接;`14317`(OTLP/gRPC)由独立的 `HERTZBEAT_OTLP_BIND_ADDRESS` 控制。详见部署方案目录下的 `README.md`。 + 随附的 `conf/application.yml` 默认启用 Basic 和 JWT 认证,Web UI 使用 JWT;Digest 需要显式开启。如果在 `sureness.auths` 中加入 `digest`,未认证请求访问受保护接口时会收到 `WWW-Authenticate: Digest` 质询,浏览器可能弹出原生用户名密码框。修改配置后需要重启 HertzBeat 容器。示例和 TLS 建议参见 [认证方式](docker-deploy#认证方式)。 + ::: **HAVE FUN** diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-deploy.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-deploy.md index 061f4eb852..f1a7ae45cc 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-deploy.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/docker-deploy.md @@ -54,6 +54,23 @@ sidebar_label: Docker方式安装 2. 开始探索 HertzBeat 浏览器访问 [http://ip:1157/](http://ip:1157/) 即可开始探索使用HertzBeat,默认账户密码 admin/hertzbeat。 +### 认证方式 + +默认镜像启用 Basic 和 JWT 认证。Web UI 通过表单接口登录,后续使用 JWT;Basic 保留给 API 客户端使用。凭据或令牌经过不可信网络时,请使用 TLS。 + +Digest 认证默认不启用。如需调整认证方式,请下载完整的 [`application.yml`](https://github.com/apache/hertzbeat/raw/master/script/application.yml),修改其中的 `sureness.auths` 列表,并按上面的命令挂载完整文件。例如,显式启用 Digest: + +```yaml +sureness: + container: jakarta_servlet + auths: + - digest + - basic + - jwt +``` + +启用 Digest 后,未认证请求访问受保护接口时会收到 `WWW-Authenticate: Digest` 质询,因此在浏览器中直接打开这类接口可能弹出浏览器原生用户名密码框;这是 HTTP 认证提示,不是 TLS 或证书错误。如只需 JWT,可将 `auths` 设置为仅包含 `jwt`。修改文件后需要重启容器。 + ### 部署 HertzBeat Collector 集群(可选) :::note diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/quickstart.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/quickstart.md index ecb018ac8d..f071238283 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/quickstart.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/quickstart.md @@ -41,7 +41,7 @@ HertzBeat 提供多种安装选项: ```docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat quay.io/tancloud/hertzbeat``` -2. 浏览器访问 `http://localhost:1157` 即可开始,默认账号密码 `admin/hertzbeat` +2. 浏览器访问 `http://localhost:1157` 即可开始,默认账号密码 `admin/hertzbeat`。默认镜像启用 Basic 和 JWT 认证,Web UI 使用 JWT。Digest 认证需要显式开启,因为其浏览器质询可能弹出原生用户名密码框。配置方法参见 [通过 Docker 方式安装 HertzBeat](docker-deploy#认证方式)。 3. 部署采集器集群(可选) diff --git a/script/application.yml b/script/application.yml index c6cd53d4e1..db22a026a5 100644 --- a/script/application.yml +++ b/script/application.yml @@ -84,7 +84,6 @@ springdoc: sureness: container: jakarta_servlet auths: - - digest - basic - jwt jwt: diff --git a/script/ci/check-quickstart-compose.sh b/script/ci/check-quickstart-compose.sh index 24f0de50af..a3763066c3 100755 --- a/script/ci/check-quickstart-compose.sh +++ b/script/ci/check-quickstart-compose.sh @@ -62,6 +62,26 @@ assert_non_hertzbeat_bindings() { "$config_file" > /dev/null } +assert_default_auths() { + application_file=$1 + actual_auths=$(awk ' + /^sureness:$/ { in_sureness = 1; next } + in_sureness && /^ auths:$/ { in_auths = 1; next } + in_auths && /^ - / { sub(/^ - /, ""); print; next } + in_auths { exit } + ' "$application_file") + + if [ "$actual_auths" != "basic +jwt" ]; then + echo "Unexpected default sureness.auths in ${application_file}:" >&2 + echo "$actual_auths" >&2 + return 1 + fi +} + +assert_default_auths "${repository_root}/hertzbeat-startup/src/main/resources/application.yml" +assert_default_auths "${repository_root}/script/application.yml" + for variant in \ hertzbeat-mysql-iotdb \ hertzbeat-mysql-tdengine \ @@ -70,6 +90,7 @@ for variant in \ hertzbeat-postgresql-victoria-metrics do compose_file="${repository_root}/script/docker-compose/${variant}/docker-compose.yaml" + assert_default_auths "${repository_root}/script/docker-compose/${variant}/conf/application.yml" default_config="${temporary_directory}/${variant}-default.json" override_config="${temporary_directory}/${variant}-override.json" @@ -89,4 +110,4 @@ do assert_non_hertzbeat_bindings "$override_config" 127.0.0.1 done -echo "Quick-start Compose listener bindings are valid." +echo "Quick-start Compose listener bindings and authentication defaults are valid." diff --git a/script/docker-compose/README.md b/script/docker-compose/README.md index 721c70550d..ae7b4d828b 100644 --- a/script/docker-compose/README.md +++ b/script/docker-compose/README.md @@ -28,6 +28,16 @@ only for trusted OTLP senders. Before using `0.0.0.0`, replace bundled/default credentials, apply firewall or security-group restrictions, and configure TLS. Run `docker compose config` to inspect the final bindings before startup. +## Authentication defaults + +Every quick-start variant enables Basic and JWT authentication by default; the +web UI uses JWT. Digest authentication is opt-in. If `digest` is added to +`sureness.auths` in the variant's `conf/application.yml`, unauthenticated +protected APIs return a `WWW-Authenticate: Digest` challenge and browsers may +display a native username/password dialog. Restart the HertzBeat container +after changing the file, and use TLS when credentials or tokens cross an +untrusted network. + - Use Postgresql + GreptimeDB as HertzBeat dependent storage -> [HertzBeat+PostgreSQL+GreptimeDB Solution](hertzbeat-postgresql-greptimedb) - Use Postgresql + VictoriaMetrics as HertzBeat dependent storage -> [HertzBeat+PostgreSQL+VictoriaMetrics Solution](hertzbeat-postgresql-victoria-metrics) diff --git a/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml b/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml index 47be747361..091bec61e2 100644 --- a/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml +++ b/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml @@ -87,7 +87,6 @@ springdoc: sureness: container: jakarta_servlet auths: - - digest - basic - jwt jwt: diff --git a/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml b/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml index 10c4df7d4e..6e762a6fab 100644 --- a/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml +++ b/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml @@ -87,7 +87,6 @@ springdoc: sureness: container: jakarta_servlet auths: - - digest - basic - jwt jwt: diff --git a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml index 5e11381cfb..cf940fca73 100644 --- a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml +++ b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml @@ -87,7 +87,6 @@ springdoc: sureness: container: jakarta_servlet auths: - - digest - basic - jwt jwt: diff --git a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml index 4f4c476e86..5dc0e44b36 100644 --- a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml +++ b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml @@ -87,7 +87,6 @@ springdoc: sureness: container: jakarta_servlet auths: - - digest - basic - jwt jwt: diff --git a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml index 961848fba3..3bf98cd6df 100644 --- a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml +++ b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml @@ -87,7 +87,6 @@ springdoc: sureness: container: jakarta_servlet auths: - - digest - basic - jwt jwt: --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
