1.3. 参考指南


1.3.1. 支持的注入范围

org.eclipse.microprofile.jwt.JsonWebToken 被注入时,@ApplicationScoped@Singleton @RequestScoped outer bean 注入范围都是支持。

但是,当将单个令牌声明注入为简单类型(如 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;
}
Copy to Clipboard Toggle word wrap

请注意,您也可以使用注入的 JsonWebToken 来访问单个声明,但本例中不需要设置 @RequestScoped

如需了解更多详细信息,请参阅 MP JWT CDI Injection 要求

1.3.2. 支持的公钥格式

可以使用以下格式格式化公钥,按优先级顺序指定:

  • 公钥加密标准 #8 (PKCS""8) PEM
  • JSON Web Key (JWK)
  • JSON Web Key Set (JWKS)
  • JSON Web 密钥(JWK) Base64 URL 编码
  • JSON Web 密钥集(JWKS) Base64 URL 编码

1.3.3. 处理验证密钥

如果您需要使用非对称 RSA 或 Elliptic Curve (EC)密钥验证令牌签名,请使用 mp.jwt.verify.publickey.location 属性来引用本地或远程验证密钥。

使用 mp.jwt.verify.publickey.algorithm 自定义验证算法(默认为 RS256),例如,在使用 EC 密钥时将其设置为 ES256

如果您需要使用对称 secret 密钥验证令牌签名,则必须使用 JSON Web 密钥集(JWK)或 JSON Web 密钥集 (JWK Set)格式来代表此 secret 密钥,例如:

{
 "keys": [
   {
     "kty":"oct",
     "kid":"secretKey",
     "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
   }
 ]
}
Copy to Clipboard Toggle word wrap

此机密密钥 JWK 还必须通过 smallrye.jwt.verify.key.location 来引用。smallrye.jwt.verify.algorithm 应设置为 HS256/HS384/HS512

1.3.4. 使用 JWTParser解析并验证 JsonWebToken

如果 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);
Copy to Clipboard Toggle word wrap

您还可以使用它来自定义令牌被验证或解密的方式。例如,一个可以提供本地 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 by using the 'HS256' algorithm
            String newJwtCookie = Jwt.upn("Alice").signWithSecret(SECRET);
            // or create a JWT token encrypted by 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();
        }
    }
}
Copy to Clipboard Toggle word wrap

另请参阅 How to Add SmallRye JWT 部分有关如何使用 JWTParser 部分,而无需 quarkus-smallrye-jwt 提供的 HTTP 支持。

1.3.5. 令牌解密

如果您的应用需要接受带有加密声明或加密声明的令牌,只需设置 smallrye.jwt.decrypt.key.location 属性以指向解密密钥。

如果这是唯一设定的 key 属性,则传入的令牌应该仅包含加密的声明。如果同时设置了 mp.jwt.verify.publickeymp.jwt.verify.publickey.location 验证属性,则传入的令牌应该包含加密的内部令牌。

请参阅 使用 SmallRye JWT 生成 JWT 令牌,了解如何生成加密或内部签名,然后快速生成加密的令牌。

1.3.6. 自定义工厂

io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipalFactory 是用于解析和验证 JWT 令牌的默认实施,将它们转换为 JsonWebToken 主体。此工厂依赖于 MP JWTsmallrye-jwt 属性,如 Configuration 部分所述,以验证和自定义 JWT 令牌。

如果您需要实现自定义工厂(如跳过已由防火墙验证的令牌),您可以使用以下方法之一完成此操作:

  • 通过创建 META-INF/services/io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory 资源来使用 ServiceLoader 机制。
  • 提供 替代 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());
        }
    }
}
Copy to Clipboard Toggle word wrap

1.3.7. 阻塞调用

quarkus-smallrye-jwt 扩展使用 SmallRye JWT 库,该库目前不是被动的。

quarkus-smallrye-jwt 的视角中,它作为 reactive Quarkus 安全架构的一部分运行,是输入 SmallRye JWT 验证或解密代码之一的 IO 线程可能会在以下情况之一中阻止:

  • 默认密钥解析器会刷新包含密钥的 JsonWebKey 设置,该密钥涉及对 OIDC 端点的远程调用。
  • 自定义密钥解析器,如 AWS Application Load Balancer (ALB)密钥解析器,使用当前令牌的密钥标识符标头值针对 AWS ALB 密钥端点解析密钥。

在这种情况下,如果连接速度较慢,对键端点需要超过 3 秒的时间响应 - 当前事件循环线程可能会被阻止。

要防止它阻止,请设置 quarkus.smallrye-jwt.blocking-authentication=true

1.3.8. 令牌传播

请参阅有关 Bearer 访问令牌传播到下游服务的 Token Propagation 部分。

1.3.9. 测试

1.3.9.1. Wiremock

如果您将 mp.jwt.verify.publickey.location 配置为指向 HTTPS 或基于 HTTP 的 JsonWebKey (JWK)集,则您可以使用与 OpenID Connect Bearer Token Integration 测试 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
Copy to Clipboard Toggle word wrap

1.3.9.2. Keycloak

如果您使用 Keycloak,并将 mp.jwt.verify.publickey.location 指向 HTTPS 或基于 HTTP 的 JsonWebKey (JWK)集,您可以使用与 OpenID Connect Bearer Token Integration 测试 部分中描述的相同方法,但只需要更改 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
Copy to Clipboard Toggle word wrap

请注意,Keycloak 发布的令牌会将 iss (issuer)声明设置为 realm 端点地址。

如果您的 Quarkus 应用程序在 Docker 容器中运行,它可能会与由 DevServices for Keycloak 启动的 Keycloak 容器共享一个网络接口。在这种情况下,Quarkus 应用程序和 Keycloak 通过内部共享 Docker 网络进行通信。

在这种情况下,使用以下配置:

# keycloak.url is set by DevServices for Keycloak,
# Quarkus accesses it through 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}
Copy to Clipboard Toggle word wrap

1.3.9.3. 本地公钥

您可以使用与 OpenID Connect Bearer Token Integration 测试 本地公钥 部分中所述的方法相同,但仅将 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
Copy to Clipboard Toggle word wrap

1.3.9.4. TestSecurity 注解

添加以下依赖项:

  • 使用 Maven:

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-test-security-jwt</artifactId>
        <scope>test</scope>
    </dependency>
    Copy to Clipboard Toggle word wrap
  • 使用 Gradle:

    testImplementation("io.quarkus:quarkus-test-security-jwt")
    Copy to Clipboard Toggle word wrap

然后,编写测试代码,如下所示:

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"));
    }

}
Copy to Clipboard Toggle word wrap

其中 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");
    }
}
Copy to Clipboard Toggle word wrap

请注意,必须始终使用 @TestSecurity 注释,并且其 user 属性返回为 JsonWebToken.getName ()roles 属性 - 作为 JsonWebToken.getGroups ()@JwtSecurity 注释是可选的,可用于设置额外的令牌声明。

提示

@TestSecurity@JwtSecurity 可以合并到 meta-annotation 中,如下所示:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    @TestSecurity(user = "userOidc", roles = "viewer")
    @OidcSecurity(introspectionRequired = true,
        introspection = {
            @TokenIntrospection(key = "email", value = "user@gmail.com")
        }
    )
    public @interface TestSecurityMetaAnnotation {

    }
Copy to Clipboard Toggle word wrap

如果在多个测试方法中使用同一组安全设置,这特别有用。

1.3.10. 如何检查日志中的错误

请启用 io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator 级别日志记录,以查看令牌验证或解密错误的更多详情:

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
Copy to Clipboard Toggle word wrap

1.3.11. 主动验证

如果您想在调用公共端点方法时跳过令牌验证,请禁用 主动身份验证

请注意,如果没有令牌验证,则无法通过公共方法访问注入的 JsonWebToken

1.3.12. 如何直接添加 SmallRye JWT

要使用 JWTParser 解析并验证 JsonWebToken,请在以下情况中使用 smallrye-jwt 而不是 quarkus-smallrye-jwt

  • 您可以使用不支持 HTTP 的 Quarkus 扩展,如 Quarkus GRPC
  • 您可以提供一个特定于扩展的 HTTP,它的支持与 quarkus-smallrye-jwtVert.x HTTP 提供的支持冲突,如 Quarkus AWS Lambda

从添加 smallrye-jwt 依赖项开始:

  • 使用 Maven:

    <dependency>
        <groupId>io.smallrye</groupId>
        <artifactId>smallrye-jwt</artifactId>
    </dependency>
    Copy to Clipboard Toggle word wrap
  • 使用 Gradle:

    implementation("io.smallrye:smallrye-jwt")
    Copy to Clipboard Toggle word wrap

然后,更新 application.properties 以获取 smallrye-jwt 提供的所有 CDI 生成者,如下所示:

quarkus.index-dependency.smallrye-jwt.group-id=io.smallrye
quarkus.index-dependency.smallrye-jwt.artifact-id=smallrye-jwt
Copy to Clipboard Toggle word wrap
返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat