Using JWT RBAC
本指南解释了您的 Quarkus 应用程序如何利用 SmallRye JWT验证 JSON Web Tokens,将它们表示为 MicroProfile JWT org.eclipse.microprofile.jwt.JsonWebToken
,并使用 Bearer 令牌授权和 Role-Based Access Control为 Quarkus HTTP 端点提供安全访问。
Quarkus OpenID Connect |
- Prerequisites
- Quickstart
- Solution
- Creating the Maven project
- Examine the Jakarta REST resource
- Run the application
- Configuring the SmallRye JWT Extension Security Information
- Adding a Public Key
- Generating a JWT
- Finally, Secured Access to /secured/roles-allowed
- Using the JsonWebToken and Claim Injection
- Package and run the application
- Explore the Solution
- Reference Guide
- Supported Injection Scopes
- Supported Public Key Formats
- Dealing with the verification keys
- Parse and Verify JsonWebToken with JWTParser
- Token Decryption
- Custom Factories
- Blocking calls
- Token Propagation
- Testing
- How to check the errors in the logs
- Proactive Authentication
- How to Add SmallRye JWT directly
- Configuration Reference
- References
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
Quickstart
Solution
我们建议你按照后续章节中的说明,逐步创建应用程序。但是,你可以直接跳到已完成的示例。
克隆 Git 存储库: git clone $${quickstarts-base-url}.git
,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。
解决方案内置于 security-jwt-quickstart
directory 中。
Creating the Maven project
首先,使用以下命令创建新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令会生成 Maven 项目并导入包含了 MicroProfile JWT RBAC 支持的 smallrye-jwt
扩展。
如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令,将 smallrye-jwt
扩展添加到您的项目中:
quarkus extension add {add-extension-extensions}
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
./gradlew addExtension --extensions='{add-extension-extensions}'
这会将以下内容添加到构建文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-jwt")
implementation("io.quarkus:quarkus-smallrye-jwt-build")
Examine the Jakarta REST resource
使用以下内容在 src/main/java/org/acme/security/jwt/TokenSecuredResource.java
中创建一个 REST 端点:
package org.acme.security.jwt;
import java.security.Principal;
import jakarta.annotation.security.PermitAll;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/secured")
public class TokenSecuredResource {
@Inject
JsonWebToken jwt; (1)
@GET()
@Path("permit-all")
@PermitAll (2)
@Produces(MediaType.TEXT_PLAIN)
public String hello(@Context SecurityContext ctx) {
return getResponseString(ctx); (3)
}
private String getResponseString(SecurityContext ctx) {
String name;
if (ctx.getUserPrincipal() == null) { (4)
name = "anonymous";
} else if (!ctx.getUserPrincipal().getName().equals(jwt.getName())) { (5)
throw new InternalServerErrorException("Principal and JsonWebToken names do not match");
} else {
name = ctx.getUserPrincipal().getName(); (6)
}
return String.format("hello + %s,"
+ " isHttps: %s,"
+ " authScheme: %s,"
+ " hasJWT: %s",
name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJwt()); (7)
}
private boolean hasJwt() {
return jwt.getClaimNames() != null;
}
}
1 | 在此处,我们注入 JsonWebToken 接口,它是 java.security.Principalinterface 的扩展,可提供访问当前身份验证令牌关联请求的权限。 |
2 | @PermitAll 是一个 Jakarta 通用安全性注释,它指示给定的端点可被任何调用者(经过身份验证或未经身份验证)访问。 |
3 | 在此处,我们注入 Jakarta REST SecurityContext 以检查调用的安全状态并使用 getResponseString() 函数填充响应字符串。 |
4 | 在此处,我们通过根据空值检查请求用户/调用者 Principal 来检查调用是否不安全。 |
5 | 此处,我们检查 Principal 和 JsonWebToken 是否具有相同名称,因为 JsonWebToken 代表当前 Principal。 |
6 | 在此处,我们获取 Principal 名称。 |
7 | 我们构建的回复利用了调用者名称、请求 SecurityContext 的 isSecure() 和 getAuthenticationScheme() 状态,以及是否注入了非空 JsonWebToken 。 |
Run the application
现在,我们可以运行我们的应用程序了。使用:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
您应该会看到类似以下内容的输出:
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< org.acme:security-jwt-quickstart >-----------------------
[INFO] Building security-jwt-quickstart 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
...
Listening for transport dt_socket at address: 5005
2020-07-15 16:09:50,883 INFO [io.quarkus] (Quarkus Main Thread) security-jwt-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 1.073s. Listening on: http://0.0.0.0:8080
2020-07-15 16:09:50,885 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2020-07-15 16:09:50,885 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, mutiny, rest, rest-jackson, security, smallrye-context-propagation, smallrye-jwt, vertx, vertx-web]
现在 REST 端点正在运行,我们可以使用像 curl 这样的命令行工具访问它:
$ curl http://127.0.0.1:8080/secured/permit-all; echo
hello + anonymous, isHttps: false, authScheme: null, hasJWT: false
我们在请求中未提供任何 JWT,因此我们不会期望端点看到任何安全状态,而响应与此一致:
-
username is anonymous
-
由于未使用 https,因此 isHttps 为 false
-
authScheme is null
-
hasJWT is false
使用 Ctrl-C 停止 Quarkus 服务器。
因此,现在我们实际上需要保护一些东西。在以下内容中查看新的端点方法 helloRolesAllowed
:
package org.acme.security.jwt;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/secured")
@RequestScoped
public class TokenSecuredResource {
@Inject
JsonWebToken jwt; (1)
@GET
@Path("permit-all")
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String hello(@Context SecurityContext ctx) {
return getResponseString(ctx);
}
@GET
@Path("roles-allowed") (2)
@RolesAllowed({ "User", "Admin" }) (3)
@Produces(MediaType.TEXT_PLAIN)
public String helloRolesAllowed(@Context SecurityContext ctx) {
return getResponseString(ctx) + ", birthdate: " + jwt.getClaim("birthdate").toString(); (4)
}
private String getResponseString(SecurityContext ctx) {
String name;
if (ctx.getUserPrincipal() == null) {
name = "anonymous";
} else if (!ctx.getUserPrincipal().getName().equals(jwt.getName())) {
throw new InternalServerErrorException("Principal and JsonWebToken names do not match");
} else {
name = ctx.getUserPrincipal().getName();
}
return String.format("hello + %s,"
+ " isHttps: %s,"
+ " authScheme: %s,"
+ " hasJWT: %s",
name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJwt());
}
private boolean hasJwt() {
return jwt.getClaimNames() != null;
}
}
1 | Here we inject JsonWebToken |
2 | 此新端点将位于 /secured/roles-allowed |
3 | @RolesAllowed 是一个 Jakarta 通用安全性注释,它指示给定的端点可被具有分配的“用户”或“管理员”角色的调用者访问。 |
4 | 此处,我们采用与 hello 方法相同的方式构建回复,但还通过直接调用注入的 JsonWebToken 来添加 JWT birthdate 声明的值。 |
在您的 TokenSecuredResource
中添加此项补充后,重新运行 ./mvnw compile quarkus:dev
命令,然后尝试 curl -v [role="bare"]http://127.0.0.1:8080/secured/roles-allowed; echo
,以便尝试访问新的终结点。
您的输出应如下所示:
$ curl -v http://127.0.0.1:8080/secured/roles-allowed; echo
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /secured/roles-allowed HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Connection: keep-alive
< Content-Type: text/html;charset=UTF-8
< Content-Length: 14
< Date: Sun, 03 Mar 2019 16:32:34 GMT
<
* Connection #0 to host 127.0.0.1 left intact
出色,我们在请求中尚未提供任何 JWT,因此我们不应能够访问该终结点,并且我们也没有访问。相反,我们收到一个 HTTP 401 未授权错误。我们需要获取一个有效的 JWT 并将其传入才能访问该终结点。此过程分为两步,1) 使用有关如何验证 JWT 的信息配置我们的 SmallRye JWT 扩展,以及 2) 生成具有适当声明的匹配 JWT。
Configuring the SmallRye JWT Extension Security Information
使用以下内容创建 security-jwt-quickstart/src/main/resources/application.properties
:
mp.jwt.verify.publickey.location=publicKey.pem (1)
mp.jwt.verify.issuer=https://example.com/issuer (2)
quarkus.native.resources.includes=publicKey.pem (3)
1 | 我们设置公钥位置以指向类路径 publicKey.pem 位置。我们将在第 B 部分 Adding a Public Key 中添加此密钥。 |
2 | 我们设置颁发者到 URL 字符串 https://example.com/issuer 。 |
3 | 我们正在将公钥包含在本地可执行文件中的资源中。 |
Adding a Public Key
JWT specification 定义了可以使用的 JWT 的不同安全等级。“MicroProfile JWT RBAC” 规范要求使用 RSA-256 签名算法来签署 JWT。这反过来要求使用一个 RSA 公钥对。在 REST 终结点服务器端,您需要配置要用来验证随请求一起发送的 JWT 的 RSA 公钥的位置。之前配置的 mp.jwt.verify.publickey.location=publicKey.pem
设置期望公钥作为 publicKey.pem
在类路径上可用。为实现此目的,将以下内容复制到 security-jwt-quickstart/src/main/resources/publicKey.pem
文件中。
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq
Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR
TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e
UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9
AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn
sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x
nQIDAQAB
-----END PUBLIC KEY-----
Generating a JWT
在很多情况下,人们会从某个身份管理器(如 Keycloak)获取 JWT,但对于此快速入门教程,我们将使用 smallrye-jwt
提供的 JWT 生成 API 生成我们自己的 JWT。详情请参阅 Generate JWT tokens with SmallRye JWT。
从以下清单中获取代码并将其放入 security-jwt-quickstart/src/test/java/org/acme/security/jwt/GenerateToken.java
:
package org.acme.security.jwt;
import java.util.Arrays;
import java.util.HashSet;
import org.eclipse.microprofile.jwt.Claims;
import io.smallrye.jwt.build.Jwt;
public class GenerateToken {
/**
* Generate JWT token
*/
public static void main(String[] args) {
String token =
Jwt.issuer("https://example.com/issuer") (1)
.upn("jdoe@quarkus.io") (2)
.groups(new HashSet<>(Arrays.asList("User", "Admin"))) (3)
.claim(Claims.birthdate.name(), "2001-07-13") (4)
.sign();
System.out.println(token);
}
}
1 | iss 声明是 JWT 的颁发者。这需要与服务器端的 mp.jwt.verify.issuer 匹配,以便令牌才可被接受为有效。 |
2 | upn 声明由“MicroProfile JWT RBAC”规范定义为首选声明,用于通过容器安全性 API 看到的 Principal 。 |
3 | group 声明提供了与 JWT 持有者相关的组和顶级角色。 |
4 | birthday 声明。它可被认为是一种敏感声明,因此您可能需要考虑加密该声明,请参阅 Generate JWT tokens with SmallRye JWT。 |
请注意,为了让此代码正常工作,我们需要与 TokenSecuredResource 应用程序中所包含的公钥对应的 RSA 私钥的内容。获取以下 PEM 内容并将其放入 security-jwt-quickstart/src/test/resources/privateKey.pem
:
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa
PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H
OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN
qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh
nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM
uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6
oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv
6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY
URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6
96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB
Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3
zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF
KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP
iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B
m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS
34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG
5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2
tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL
WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y
b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09
nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB
MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d
Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe
Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt
FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8
f3cg+fr8aou7pr9SHhJlZCU=
-----END PRIVATE KEY-----
我们将使用 smallrye.jwt.sign.key.location
属性指向此私有签名密钥。
Generating Keys with OpenSSL
还可以使用 OpenSSL 命令行工具生成一个公钥和私钥对。 openssl commands for generating keys
生成私钥需要一个附加步骤,用于将它转换成 PKCS#8 格式。 openssl command for converting private key
您可以使用生成的密钥对来替代本快速入门教程中使用的密钥。 |
现在,我们可以生成一个 JWT 来配合 TokenSecuredResource
端点使用。为此,请运行以下命令:
$ mvn exec:java -Dexec.mainClass=org.acme.security.jwt.GenerateToken -Dexec.classpathScope=test -Dsmallrye.jwt.sign.key.location=privateKey.pem
eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjU5Njc2LCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1OTk3NiwiaWF0IjoxNTUxNjU5Njc2LCJqdGkiOiJhLTEyMyJ9.O9tx_wNNS4qdpFhxeD1e7v4aBNWz1FCq0UV8qmXd7dW9xM4hA5TO-ZREk3ApMrL7_rnX8z81qGPIo_R8IfHDyNaI1SLD56gVX-NaOLS2OjfcbO3zOWJPKR_BoZkYACtMoqlWgIwIRC-wJKUJU025dHZiNL0FWO4PjwuCz8hpZYXIuRscfFhXKrDX1fh3jDhTsOEFfu67ACd85f3BdX9pe-ayKSVLh_RSbTbBPeyoYPE59FW7H5-i8IE-Gqu838Hz0i38ksEJFI25eR-AJ6_PSUD0_-TV3NjXhF3bFIeT4VSaIZcpibekoJg0cQm-4ApPEcPLdgTejYHA-mupb8hSwg
JWT 字符串是通过 base64 URL 编码的字符串,有 3 个部分,用 '.' 字符分隔。第一部分 - JWT 头部,第二部分 - JWT 声明,第三部分 - JWT 签名。
Finally, Secured Access to /secured/roles-allowed
现在,让我们使用它向 /secured/roles-allowed 端点发出安全请求。确保 Quarkus 服务器仍在 dev 模式下运行,然后运行以下命令,务必使用你从上一步中生成的 JWT 的版本:
curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjUyMDkxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1MjM5MSwiaWF0IjoxNTUxNjUyMDkxLCJqdGkiOiJhLTEyMyJ9.aPA4Rlc4kw7n_OZZRRk25xZydJy_J_3BRR8ryYLyHTO1o68_aNWWQCgpnAuOW64svPhPnLYYnQzK-l2vHX34B64JySyBD4y_vRObGmdwH_SEufBAWZV7mkG3Y4mTKT3_4EWNu4VH92IhdnkGI4GJB6yHAEzlQI6EdSOa4Nq8Gp4uPGqHsUZTJrA3uIW0TbNshFBm47-oVM3ZUrBz57JKtr0e9jv0HjPQWyvbzx1HuxZd6eA8ow8xzvooKXFxoSFCMnxotd3wagvYQ9ysBa89bgzL-lhjWtusuMFDUVYwFqADE7oOSOD4Vtclgq8svznBQ-YpfTHfb9QEcofMlpyjNA" http://127.0.0.1:8080/secured/roles-allowed; echo
$ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed; echo
hello + jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13
成功!我们现在拥有:
-
一个非匿名调用者名称,即 jdoe@quarkus.io
-
Bearer 身份验证方案
-
a non-null JsonWebToken
-
birthdate claim value
Using the JsonWebToken and Claim Injection
既然我们可以生成一个 JWT 来访问安全的 REST 端点,那么让我们看看我们可以使用 JsonWebToken
接口和 JWT 声明做什么。org.eclipse.microprofile.jwt.JsonWebToken
接口扩展了 java.security.Principal
接口,实际上是 jakarta.ws.rs.core.SecurityContext#getUserPrincipal()
调用返回的对象类型,我们之前使用了该调用。这意味着未使用 CDI 但能够访问 REST 容器 SecurityContext
的代码可以通过强制转换 SecurityContext#getUserPrincipal()
来获得调用者 JsonWebToken
接口。
JsonWebToken
接口定义了用于访问基础 JWT 的方法。它可以访问 MicroProfile JWT RBAC 规范所需的常见声明,以及 JWT 中可能存在的任意声明。
所有 JWT 声明都可以注入。让我们使用另一个使用已注入的 birthdate
声明的端点 /secured/roles-allowed-admin 扩展我们的 TokenSecuredResource
(而不是从 JsonWebToken
获取该声明):
package org.acme.security.jwt;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/secured")
@RequestScoped
public class TokenSecuredResource {
@Inject
JsonWebToken jwt; (1)
@Inject
@Claim(standard = Claims.birthdate)
String birthdate; (2)
@GET
@Path("permit-all")
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String hello(@Context SecurityContext ctx) {
return getResponseString(ctx);
}
@GET
@Path("roles-allowed")
@RolesAllowed({ "User", "Admin" })
@Produces(MediaType.TEXT_PLAIN)
public String helloRolesAllowed(@Context SecurityContext ctx) {
return getResponseString(ctx) + ", birthdate: " + jwt.getClaim("birthdate").toString();
}
@GET
@Path("roles-allowed-admin")
@RolesAllowed("Admin")
@Produces(MediaType.TEXT_PLAIN)
public String helloRolesAllowedAdmin(@Context SecurityContext ctx) {
return getResponseString(ctx) + ", birthdate: " + birthdate; (3)
}
private String getResponseString(SecurityContext ctx) {
String name;
if (ctx.getUserPrincipal() == null) {
name = "anonymous";
} else if (!ctx.getUserPrincipal().getName().equals(jwt.getName())) {
throw new InternalServerErrorException("Principal and JsonWebToken names do not match");
} else {
name = ctx.getUserPrincipal().getName();
}
return String.format("hello + %s,"
+ " isHttps: %s,"
+ " authScheme: %s,"
+ " hasJWT: %s",
name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJwt());
}
private boolean hasJwt() {
return jwt.getClaimNames() != null;
}
}
1 | 我们在此注入 JsonWebToken。 |
2 | 在此处,我们注入 birthday 声明为 String - 这就是现在需要 @RequestScoped 范围的原因。 |
3 | 在此处,我们使用已注入的 birthday 声明构建最终答复。 |
现在,再次生成令牌并运行:
curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjUyMDkxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1MjM5MSwiaWF0IjoxNTUxNjUyMDkxLCJqdGkiOiJhLTEyMyJ9.aPA4Rlc4kw7n_OZZRRk25xZydJy_J_3BRR8ryYLyHTO1o68_aNWWQCgpnAuOW64svPhPnLYYnQzK-l2vHX34B64JySyBD4y_vRObGmdwH_SEufBAWZV7mkG3Y4mTKT3_4EWNu4VH92IhdnkGI4GJB6yHAEzlQI6EdSOa4Nq8Gp4uPGqHsUZTJrA3uIW0TbNshFBm47-oVM3ZUrBz57JKtr0e9jv0HjPQWyvbzx1HuxZd6eA8ow8xzvooKXFxoSFCMnxotd3wagvYQ9ysBa89bgzL-lhjWtusuMFDUVYwFqADE7oOSOD4Vtclgq8svznBQ-YpfTHfb9QEcofMlpyjNA" http://127.0.0.1:8080/secured/roles-allowed-admin; echo
$ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed-admin; echo
hello + jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13
Package and run the application
和往常一样,可以使用以下命令打包应用程序:
quarkus build
./mvnw install
./gradlew build
并使用 java -jar target/quarkus-app/quarkus-run.jar
执行:
$ java -jar target/quarkus-app/quarkus-run.jar
2019-03-28 14:27:48,839 INFO [io.quarkus] (main) Quarkus {quarkus-version} started in 0.796s. Listening on: http://[::]:8080
2019-03-28 14:27:48,841 INFO [io.quarkus] (main) Installed features: [cdi, rest, rest-jackson, security, smallrye-jwt]
你还可以按如下方式生成本机可执行文件:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
[INFO] Scanning for projects...
...
[security-jwt-quickstart-runner:25602] universe: 493.17 ms
[security-jwt-quickstart-runner:25602] (parse): 660.41 ms
[security-jwt-quickstart-runner:25602] (inline): 1,431.10 ms
[security-jwt-quickstart-runner:25602] (compile): 7,301.78 ms
[security-jwt-quickstart-runner:25602] compile: 10,542.16 ms
[security-jwt-quickstart-runner:25602] image: 2,797.62 ms
[security-jwt-quickstart-runner:25602] write: 988.24 ms
[security-jwt-quickstart-runner:25602] [total]: 43,778.16 ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 51.500 s
[INFO] Finished at: 2019-03-28T14:30:56-07:00
[INFO] ------------------------------------------------------------------------
$ ./target/security-jwt-quickstart-runner
2019-03-28 14:31:37,315 INFO [io.quarkus] (main) Quarkus 0.12.0 started in 0.006s. Listening on: http://[::]:8080
2019-03-28 14:31:37,316 INFO [io.quarkus] (main) Installed features: [cdi, rest, rest-jackson, security, smallrye-jwt]
Explore the Solution
位于 security-jwt-quickstart
directory 的解决方案存储库包含我们已在本快速入门指南中用过的所有版本,以及一些说明注入 JsonWebToken`s and their claims into those using the CDI APIs.
We suggest that you check out the quickstart solutions and explore the `security-jwt-quickstart
目录的子资源的附加端点,以了解有关 SmallRye JWT 扩展功能的更多信息。
Reference Guide
Supported Injection Scopes
当注入 org.eclipse.microprofile.jwt.JsonWebToken
时,所有 @ApplicationScoped
、@Singleton
和 @RequestScoped
外部 bean 注入范围都受支持,且对 JsonWebToken
强制执行 @RequestScoped
范围,以确保当前令牌得到体现。
但是,当将单个令牌声明注入为简单类型(例如 String
)时,必须使用 @RequestScoped
,例如:
package org.acme.security.jwt;
import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.Claims;
@Path("/secured")
@RequestScoped
public class TokenSecuredResource {
@Inject
@Claim(standard = Claims.birthdate)
String birthdate;
}
请注意,你还可以使用已注入的 JsonWebToken
访问单个声明,在这种情况下,设置 @RequestScoped
不是必需的。
Supported Public Key Formats
公钥可以采用以下任何格式,按优先级顺序指定:
-
公钥加密标准 #8 (PKCS#8) PEM
-
JSON Web Key (JWK)
-
JSON Web 密钥集 (JWKS)
-
JSON Web 密钥 (JWK) Base64 URL 编码
-
JSON Web 密钥集 (JWKS) Base64 URL 编码
Dealing with the verification keys
如果您需要使用非对称 RSA 或椭圆曲线 (EC) 密钥验证令牌签名,则使用 mp.jwt.verify.publickey.location
属性来引用本地或远程验证密钥。
使用 mp.jwt.verify.publickey.algorithm
来自定义验证算法(默认值为 RS256
),例如,在使用 EC 密钥时将其设置为 ES256
。
如果您需要使用对称密钥验证令牌签名,则必须使用 JSON Web Key
(JWK) 或 JSON Web Key Set
(JWK 集) 格式来表示此密钥,例如:
{
"keys": [
{
"kty":"oct",
"kid":"secretKey",
"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
}
]
}
此密钥 JWK 也需要使用 smallrye.jwt.verify.key.location
引用。应该将 smallrye.jwt.verify.algorithm
设置为 HS256
/HS384
/HS512
。
Parse and Verify JsonWebToken with JWTParser
如果 JWT 令牌无法注入,例如,如果它嵌入在服务请求负载中或服务端点通过带外获得它,则可以使用 JWTParser
:
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.smallrye.jwt.auth.principal.JWTParser;
...
@Inject JWTParser parser;
String token = getTokenFromOidcServer();
// Parse and verify the token
JsonWebToken jwt = parser.parse(token);
您还可以使用它来自定义令牌的验证或解密方式。例如,可以提供本地 SecretKey
:
package org.acme.security.jwt;
import io.smallrye.jwt.auth.principal.ParseException;
import jakarta.inject.Inject;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.NewCookie;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.smallrye.jwt.auth.principal.JWTParser;
import io.smallrye.jwt.build.Jwt;
@Path("/secured")
public class SecuredResource {
private static final String SECRET = "AyM1SysPpbyDfgZld3umj1qzKObwVMko";
@Inject
JWTParser parser;
@GET
@Produces("text/plain")
public Response getUserName(@CookieParam("jwt") String jwtCookie) throws ParseException {
if (jwtCookie == null) {
// Create a JWT token signed using the 'HS256' algorithm
String newJwtCookie = Jwt.upn("Alice").signWithSecret(SECRET);
// or create a JWT token encrypted using the 'A256KW' algorithm
// Jwt.upn("alice").encryptWithSecret(secret);
return Response.ok("Alice").cookie(new NewCookie("jwt", newJwtCookie)).build();
} else {
// All mp.jwt and smallrye.jwt properties are still effective, only the verification key is customized.
JsonWebToken jwt = parser.verify(jwtCookie, SECRET);
// or jwt = parser.decrypt(jwtCookie, secret);
return Response.ok(jwt.getName()).build();
}
}
}
另请参阅 How to Add SmallRye JWT directly 部分,了解如何在没有 quarkus-smallrye-jwt
提供的 HTTP
支持的情况下使用 JWTParser
。
Token Decryption
如果您的应用程序需要使用加密声明或加密内签名声明来接受令牌,您需要做的就是设置指向解密密钥的 smallrye.jwt.decrypt.key.location
。
如果这是设置的唯一密钥属性,则传入令牌预期仅包含加密声明。如果同时也设置了 mp.jwt.verify.publickey
或 mp.jwt.verify.publickey.location
验证属性,则传入令牌预期包含加密内签名令牌。
参见 Generate JWT tokens with SmallRye JWT 并了解如何快速生成加密或内签名且然后加密令牌。
Custom Factories
默认情况下使用 io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipalFactory
来解析和验证 JWT 令牌,并将它们转换为 JsonWebToken
主体。它使用 Configuration
中列出的 MP JWT
和 smallrye-jwt
属性来验证和自定义 JWT 令牌。
如果您需要提供自己的工厂,例如,为了避免再次验证已经由防火墙验证过的令牌,则可以通过提供 META-INF/services/io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory
资源使用 ServiceLoader
机制,或者只需有一个 Alternative
CDI Bean 实现,如下所示:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal;
import io.smallrye.jwt.auth.principal.JWTAuthContextInfo;
import io.smallrye.jwt.auth.principal.JWTCallerPrincipal;
import io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory;
import io.smallrye.jwt.auth.principal.ParseException;
@ApplicationScoped
@Alternative
@Priority(1)
public class TestJWTCallerPrincipalFactory extends JWTCallerPrincipalFactory {
@Override
public JWTCallerPrincipal parse(String token, JWTAuthContextInfo authContextInfo) throws ParseException {
try {
// Token has already been verified, parse the token claims only
String json = new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), StandardCharsets.UTF_8);
return new DefaultJWTCallerPrincipal(JwtClaims.parse(json));
} catch (InvalidJwtException ex) {
throw new ParseException(ex.getMessage());
}
}
}
Blocking calls
quarkus-smallrye-jwt
扩展使用 SmallRye JWT 库,而该库当前不是响应式的。
从作为响应式 Quarkus 安全架构一部分的 quarkus-smallrye-jwt
的角度来看,这意味着进入 SmallRye JWT 验证或解密代码的 IO 线程可能会在以下情况下之一中阻塞:
-
默认密钥解析器刷新包含密钥的
JsonWebKey
集,其中涉及对 OIDC 端点的远程调用 -
自定义密钥解析器,例如
AWS Application Load Balancer
(ALB
)密钥解析器,使用当前令牌的密钥标识符头值来相对于 AWS ALB 密钥端点解析密钥
在这种情况下,如果连接缓慢,例如,从密钥端点获取响应可能需要 3 秒以上,则当前事件循环线程很可能会阻塞。
要防止这种情况,请设置 quarkus.smallrye-jwt.blocking-authentication=true
。
Token Propagation
请参阅 Token Propagation 关于将 Bearer 访问令牌传播到下游服务的部分。
Testing
Wiremock
如果将 mp.jwt.verify.publickey.location
配置为指向基于 HTTP 的 JsonWebKey (JWK),则可以采用与 OpenID Connect Bearer Token Integration testing Wiremock
部分中描述相同的方法,但仅将 application.properties
更改为使用 MP JWT 配置属性:
# keycloak.url is set by OidcWiremockTestResource
mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
Keycloak
如果使用 Keycloak,并将 mp.jwt.verify.publickey.location
配置为指向基于 HTTPS 或 HTTP 的 JsonWebKey (JWK),则可以采用与 OpenID Connect Bearer Token Integration testing Keycloak 部分中描述相同的方法,但仅将 application.properties
更改为使用 MP JWT 配置属性:
# keycloak.url is set by DevServices for Keycloak
mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
请注意,Keycloak 发行的令牌的 iss
(发布者)声明被设置为领域端点地址。
如果 Quarkus 应用程序在 Docker 容器中运行,它可能会与 DevServices 通过 Keycloak 为 Keycloak 启动的 Keycloak Docker 容器共享网络接口,其中 Quarkus 应用程序和 Keycloak 会通过内部共享 Docker 网络互相通信。
在这种情况下,请使用以下配置:
# keycloak.url is set by DevServices for Keycloak,
# Quarkus will access it via an internal shared docker network interface.
mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
# Issuer is set to the docker bridge localhost endpoint address represented by the `client.quarkus.oidc.auth-server-url` property
mp.jwt.verify.issuer=${client.quarkus.oidc.auth-server-url}
Local Public Key
可以采用与 OpenID Connect Bearer Token Integration testing Local Public Key
部分中描述相同的方法,但仅将 application.properties
更改为使用 MP JWT 配置属性:
mp.jwt.verify.publickey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB
# set it to the issuer value which is used to generate the tokens
mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
# required to sign the tokens
smallrye.jwt.sign.key.location=privateKey.pem
TestSecurity annotation
添加以下依赖项:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-jwt</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-test-security-jwt")
并编写以下测试代码:
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.quarkus.test.security.jwt.Claim;
import io.quarkus.test.security.jwt.JwtSecurity;
import io.restassured.RestAssured;
@QuarkusTest
@TestHTTPEndpoint(ProtectedResource.class)
public class TestSecurityAuthTest {
@Test
@TestSecurity(user = "userJwt", roles = "viewer")
public void testJwt() {
RestAssured.when().get("test-security-jwt").then()
.body(is("userJwt:viewer"));
}
@Test
@TestSecurity(user = "userJwt", roles = "viewer")
@JwtSecurity(claims = {
@Claim(key = "email", value = "user@gmail.com")
})
public void testJwtWithClaims() {
RestAssured.when().get("test-security-jwt-claims").then()
.body(is("userJwt:viewer:user@gmail.com"));
}
}
其中 ProtectedResource
类可能如下所示:
@Path("/web-app")
@Authenticated
public class ProtectedResource {
@Inject
JsonWebToken accessToken;
@GET
@Path("test-security-jwt")
public String testSecurityOidc() {
return accessToken.getName() + ":" + accessToken.getGroups().iterator().next();
}
@GET
@Path("test-security-jwt-claims")
public String testSecurityOidcUserInfoMetadata() {
return accessToken.getName() + ":" + accessToken.getGroups().iterator().next()
+ ":" + accessToken.getClaim("email");
}
}
请注意,始终必须使用 @TestSecurity
注释,而其 user
属性会返回为 JsonWebToken.getName()
,而 roles
属性返回为 JsonWebToken.getGroups()
.@JwtSecurity
注释为可选项,可用于设置其他令牌声明。
如果需要在多个测试方法中使用同一组安全设置,这会尤其有用。 |
How to check the errors in the logs
请启用 io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator
TRACE
级别日志记录,以查看有关令牌验证或解密错误的更多详细信息:
quarkus.log.category."io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator".level=TRACE
quarkus.log.category."io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator".min-level=TRACE
Proactive Authentication
当调用公共端点方法时,如果你想要跳过令牌验证,请禁用 proactive authentication。
请注意,如果尚未执行令牌验证,则无法在公共方法中访问注入的 JsonWebToken
。
How to Add SmallRye JWT directly
要 parse and verify JsonWebToken with JWTParser,请在以下情况下直接对 quarkus-smallrye-jwt
使用 smallrye-jwt
:
-
您使用 Quarkus 扩展而其中不支持
HTTP
,例如Quarkus GRPC
。 -
您提供一个扩展特定的
HTTP
,其支持与由quarkus-smallrye-jwt`和 `Vert.x HTTP`提供的支持冲突,例如 `Quarkus AWS Lambda
。
首先添加 `smallrye-jwt`依赖:
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-jwt</artifactId>
</dependency>
implementation("io.smallrye:smallrye-jwt")
并更新 `application.properties`以将所有 `smallrye-jwt`提供的 CDI 生产者包含如下:
quarkus.index-dependency.smallrye-jwt.group-id=io.smallrye
quarkus.index-dependency.smallrye-jwt.artifact-id=smallrye-jwt
Configuration Reference
Quarkus configuration
Unresolved include directive in modules/ROOT/pages/security-jwt.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-smallrye-jwt.adoc[]
MicroProfile JWT configuration
Property Name | Default | Description |
---|---|---|
|
|
`mp.jwt.verify.publickey`配置属性允许将公钥文本本身作为字符串提供。公钥将从按照 Supported Public Key Formats中定义的顺序提供的字符串解析。 |
|
|
配置属性允许指定公钥的外部或内部位置。该值可以是相对路径或 URL。如果该值指向基于 HTTPS 的 JWK 集,则它在原生模式下工作的前提是 |
|
|
签名算法。将它设置为 `ES256`以支持椭圆曲线签名算法。 |
|
|
配置属性允许指定私钥解密密钥的外部或内部位置。 |
|
|
配置属性指定服务器将接受为有效的 JWT 的 |
|
|
包含令牌 `aud`声明可能包含的受众的逗号分隔列表。 |
|
|
令牌过期和年龄验证期间使用的秒数时钟偏差。如果当前时间在令牌过期时间后在此属性指定的秒数内,则接受过期的令牌。默认值为 60 秒。 |
|
|
令牌 |
|
|
如果您使用另一个标头(例如 |
|
|
包含令牌的 cookie 的名称。此属性仅在将 `mp.jwt.token.header`设置为 `Cookie`时有效。 |
Additional SmallRye JWT configuration
SmallRye JWT 提供更多用于自定义令牌处理的属性:
Property Name | Default | Description |
---|---|---|
|
|
验证密钥的位置,它可以指向公钥和私钥。私钥只能采用 JWK 格式。请注意,如果设置了此属性,那么会忽略“mp.jwt.verify.publickey.location”。 |
|
签名算法。此属性只应用于设置必需的对称算法,例如 |
|
|
|
将此属性设置为特定的密钥格式(例如 |
|
|
默认情况下,根据 MicroProfile JWT 规范,可以从本地文件系统读取 PEM、JWK 或 JWK 密钥组,或从 URI 中获取。将此属性设置为 `AWS_ALB`以支持 AWS 应用程序负载均衡器验证密钥解析。 |
|
|
放宽验证密钥的验证,将此属性设置为 |
|
|
如果启用了此属性,则签名 令牌必须包含“x5t”或“x5t#S256”X509Certificate 缩略指纹头。在这种情况下,验证密钥只能采用 JWK 或 PEM 证书密钥格式。JWK 密钥必须具有“x5c”(Base64 编码的 X509Certificate) 属性集。 |
|
|
如果使用 |
|
|
密钥缓存大小。使用此属性以及 |
|
|
密钥缓存条目存活时间(分钟)。使用此属性以及 |
|
|
包含令牌的 cookie 名称。此属性仅在 |
|
|
将此属性设置为 |
|
|
包含备用单一或多个方案的逗号分隔列表,例如 |
|
|
密钥标识符。如果设置了密钥标识符,则验证 JWK 密钥以及每个 JWT 令牌必须具有相匹配的 |
|
|
JWT 可签发的最长秒数。实际上,JWT 的过期日期与签发日期之间的差值不得超过此值。将此属性设置为非正值会放宽对令牌的有效“iat”(颁发时间) 声明的要求。 |
|
|
如果应用程序依赖于 |
|
|
包含主题名称的声明的路径。它从顶级 JSON 对象开始,可以包含多个片段,其中每个片段仅代表 JSON 对象名称,例如: |
|
|
当当前令牌没有可用的标准或自定义 |
|
|
包含组的声明的路径。它从顶级 JSON 对象开始,可以包含多个片段,其中每个片段仅代表 JSON 对象名称,例如: |
|
|
用于拆分可能包含多个组值的字符串的分隔符。仅当 |
|
|
当当前令牌没有可用的标准或自定义组声明时,可以使用此属性设置默认组声明值。 |
|
|
JWK 缓存刷新间隔(分钟)。除非 |
|
|
强制 JWK 缓存刷新间隔(分钟),用于限制强制刷新尝试的频率,当令牌验证失败时可能会发生这种情况,因为缓存中没有 |
|
|
过期宽限,单位:秒。默认情况下,如果当前时间不晚于 token 过期时间 1 分钟,则仍接受过期的 token。已弃用此属性。请改为使用 |
|
|
token |
|
|
token 必须包含的声明的逗号分隔列表。 |
|
|
Config 属性允许指定私有解密密钥的外部或内部位置。已弃用此属性。请改为使用 |
|
|
Decryption algorithm. |
|
|
以字符串提供的解密密钥。 |
|
|
解密密钥标识符。如果设置它,则解密 JWK 密钥以及每枚 JWT 令牌都必须具有相匹配的 `kid`标头。 |
|
|
TLS 受信证书的路径,当需要通过 `HTTPS`获取密钥时可能需要配置。 |
|
|
信任所有主机名。如果需要通过 |
|
|
受信任的主机名集。如果需要通过 |
|
|
HTTP proxy host. |
|
|
HTTP proxy port. |
|
|
如果 |
|
如果 `mp.jwt.verify.publickey.location`或 `mp.jwt.decrypt.key.location`指向 `KeyStore`文件,则可使用此属性自定义 `KeyStore`提供程序。 |
|
|
密钥库密码。如果 |
|
|
必须设置此属性,以便标识公钥验证密钥,如果 `mp.jwt.verify.publickey.location`指向 `KeyStore`文件,则将从 `KeyStore`中提取此密钥。 |
|
|
如果 `mp.jwt.decrypt.key.location`指向 `KeyStore`文件,则必须设置此属性,以便标识私有解密密钥。 |
|
|
如果 `mp.jwt.decrypt.key.location`指向 `KeyStore`文件时,`KeyStore`中私有解密密钥的密码与 `smallrye.jwt.keystore.password`不同,则可设置此属性。 |
|
|
|
将此属性设置为 true,可在应用程序启动时解析远程密钥。 |