The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
返回指南目录

使用 JWT RBAC

本指南旨在阐述如何在你的Quarkus应用程序中使用 SmallRye JWT来验证 JSON Web Token,并将它们呈现为MicroProfile JWT 的 org.eclipse.microprofile.jwt.JsonWebToken ,然后使用不记名令牌验证(Bearer Token Authorization)和 基于角色的访问控制来保证对Quarkus HTTP端点的安全访问。

Quarkus OpenID Connect quarkus-oidc 扩展也支持不记名令牌验证(Bearer Token Authorization),并使用 smallrye-jwt 把不记名令牌(bearer token)呈现为 JsonWebToken , 请阅读 使用 OpenID Connect 来保护服务应用程序 指南来了解更多信息。如果Quarkus应用程序需要使用OIDC授权码模式(Authorization Code Flow)来对用户进行认证,就必须使用OpenID Connect扩展,请阅读《 使用OpenID Connect保护Web应用程序 》指南了解更多信息。

先决条件

完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • 安装JDK 11以上版本并正确配置了 JAVA_HOME

  • Apache Maven 3.8.6

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

快速入门

解决方案

我们建议你按照下面几节的说明,一步一步地创建应用程序。不过,你也可以直接跳到已经完成的例子所在的地方。

克隆 Git 仓库。 git clone https://github.com/quarkusio/quarkus-quickstarts.git ,或者下载一个 存档

该解决方案位于 security-jwt-quickstart 目录中。

创建Maven项目

首先,我们需要创建一个新的项目。用以下命令创建一个新项目。

CLI
quarkus create app org.acme:security-jwt-quickstart \
    --extension='resteasy-reactive-jackson,smallrye-jwt,smallrye-jwt-build' \
    --no-code
cd security-jwt-quickstart

创建Grade项目,请添加 --gradle 或者 --gradle-kotlin-dsl 参数。

关于如何安装并使用Quarkus CLI的更多信息,请参考Quarkus CLI指南

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.14.2.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-jwt-quickstart \
    -Dextensions='resteasy-reactive-jackson,smallrye-jwt,smallrye-jwt-build' \
    -DnoCode
cd security-jwt-quickstart

创建Grade项目,请添加 -DbuildTool=gradle 或者 -DbuildTool=gradle-kotlin-dsl 参数。

该命令生成Maven项目并导入 smallrye-jwt 扩展,其中包括MicroProfile JWT RBAC支持。

如果你已经配置了你的Quarkus项目,你可以通过在你的项目基础目录下运行以下命令,将 smallrye-jwt 扩展到你的项目。

CLI
quarkus extension add 'smallrye-jwt,smallrye-jwt-build'
Maven
./mvnw quarkus:add-extension -Dextensions='smallrye-jwt,smallrye-jwt-build'
Gradle
./gradlew addExtension --extensions='smallrye-jwt,smallrye-jwt-build'

这将在你的build文件中添加以下内容:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-smallrye-jwt")
implementation("io.quarkus:quarkus-smallrye-jwt-build")

检查JAX-RS资源

src/main/java/org/acme/security/jwt/TokenSecuredResource.java 里,创建一个REST端点,内容如下:

REST Endpoint V1
package org.acme.security.jwt;

import java.security.Principal;

import javax.annotation.security.PermitAll;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.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.Principal接口的扩展,提供了对与当前认证令牌authenticated token相关声明的访问。
2 @PermitAll是JSR 250的一个通用安全注解,它表明给定的端点可以被任何调用者访问,无论是否经过验证。
3 这里我们注入JAX-RS SecurityContext来检查调用的安全状态,并使用 getResponseString() 函数来得到响应字符串。
4 在这里,我们通过检查请求用户/呼叫者 Principal 是否是null值,如果是则不安全。
5 这里我们检查Principal和JsonWebToken是否有相同的名字,因为JsonWebToken代表了当前的Principal。
6 这里我们得到了Principal的名字。
7 当我们建立的回复时使用调用者的名字,和 isSecure()getAuthenticationScheme() 这两个 SecurityContext 的状态,以及是否注入了非空的 JsonWebToken

运行应用程序

现在我们准备运行我们的应用程序。使用:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

而你应该看到类似的输出:

quarkus:dev 输出
[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, resteasy-reactive, resteasy-reactive-jackson, security, smallrye-context-propagation, smallrye-jwt, vertx, vertx-web]

现在,REST端点正在运行,我们可以使用curl这样的命令行工具来访问它:

用curl命令访问/secured/permit-all
$ curl http://127.0.0.1:8080/secured/permit-all; echo
hello + anonymous, isHttps: false, authScheme: null, hasJWT: false

我们在请求中没有提供任何JWT,所以我们不会期望有任何安全状态被终端看到,而响应也与此一致:

  • 用户名称是匿名的

  • isHttps为false,因为没有使用https

  • authScheme为null

  • hasJWT为false

使用Ctrl-C来停止Quarkus服务器。

所以,现在让我们真正让一些东西变得安全。看看下面的新端点方法 helloRolesAllowed

REST Endpoint V2
package org.acme.security.jwt;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.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 在这里,我们注入 JsonWebToken
2 这个新的endpoint将位于/secured/roles-allowed
3 @RolesAllowed是一个JSR 250通用安全注解,表明如果调用者分配了 "用户 "或 "管理员 "角色,那么他们可以访问给定的端点。
4 在这里,我们以与 hello 方法相同的方式建立回复,但也通过直接调用注入的 JsonWebToken ,添加JWT birthdate 要求的值。

在对你的 TokenSecuredResource 进行这一补充后,重新运行 ./mvnw compile quarkus:dev 命令,然后尝试 curl -v http://127.0.0.1:8080/secured/roles-allowed; echo 来访问新的端点。你的输出应该是:

用curl 命令访问/secured/roles-allowed
$ 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
Not authorized

很好,我们在请求中没有提供任何JWT,所以我们应该不能够访问这个端点,而我们确实没有。相反,我们收到了一个HTTP 401 Unauthorized错误。我们需要获得并传入一个有效的JWT来访问该端点。这有两个步骤,1)用如何验证JWT的信息配置我们的SmallRye JWT扩展;2)用适当的声明(claim)生成一个匹配的JWT。

配置SmallRye JWT扩展安全信息

创建一个 security-jwt-quickstart/src/main/resources/application.properties ,内容如下:

TokenSecuredResource的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 我们设置公钥位置以指向classpath publicKey.pem所在位置。我们将在B部分中 添加公钥
2 我们将发行者issuer设置为URL字符串 https://example.com/issuer .
3 我们将公钥作为一种资源纳入本地可执行文件。

添加公钥

这个 JWT规范 定义了人们可以使用的JWTs的各种安全级别。MicroProfile JWT RBAC规范要求JWTs用RSA-256签名算法进行签名。这反过来又需要一个RSA公钥对。在REST终端服务器端,你需要配置RSA公钥的位置,用来验证与请求一起发送的JWT。之前配置的 mp.jwt.verify.publickey.location=publicKey.pem ,意思是公钥要能在classpath上作为 publicKey.pem 文件能找到。为了达到这个目的,请将以下内容复制到 security-jwt-quickstart/src/main/resources/publicKey.pem 文件中。

RSA公钥PEM格式内容
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq
Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR
TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e
UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9
AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn
sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x
nQIDAQAB
-----END PUBLIC KEY-----

生成一个JWT

通常情况下,人们会从身份管理服务器(如 Keycloak )那里获得JWT,但在这个快速入门中,我们将使用 smallrye-jwt 提供的JWT生成API来生成我们自己的JWT(更多信息 见用SmallRye JWT生成JWT令牌 )。

从以下列表中拷贝代码,并将其放入 security-jwt-quickstart/src/test/java/org/acme/security/jwt/GenerateToken.java

GenerateToken主驱动类
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 声明(claim)是JWT的发行者。这需要与服务器端的 mp.jwt.verify.issuer 符合。一遍使令牌被接受为有效的。
2 这个 upn 声明(claim)被MicroProfile JWT RBAC规范定义为首选声明,用做为被container security APIs所能见的 Principal
3 这个 group 声明(claim)提供了与JWT bearer相关的group和最高级别的role。
4 这个 birthday 声明(claim),它可以被认为是一个敏感的声明,所以你可能要考虑对这些声明进行加密, 见用SmallRye JWT生成JWT令牌

注意为了使这段代码工作,我们需要RSA私钥的内容,它与我们在TokenSecuredResource应用程序中的公钥相对应。将以下PEM内容放入 security-jwt-quickstart/src/test/resources/privateKey.pem

RSA私钥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 属性来指向这个私人签名钥匙。

用OpenSSL生成密钥

也可以使用OpenSSL命令行工具生成一个公钥和私钥对。

用于生成密钥的openssl命令
openssl genrsa -out rsaPrivateKey.pem 2048
openssl rsa -pubout -in rsaPrivateKey.pem -out publicKey.pem

生成私钥后需要一个额外的步骤,以便将其转换为PKCS#8格式。

openssl转换私钥的命令
openssl pkcs8 -topk8 -nocrypt -inform pem -in rsaPrivateKey.pem -outform pem -out privateKey.pem

你可以使用生成的这对密钥,而不是本快速入门中使用的密钥。

现在我们可以生成一个JWT,然后与 TokenSecuredResource 端点一起使用。要做到这一点,运行以下命令:

JWT生成输出样本
$ 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签名。

最后,安全访问/secured/roles-allowed

现在让我们用它来向/secured/roles-allowed端点发出一个安全请求。确保你的Quarkus服务器仍然运行在开发模式下,然后运行以下命令,确保使用你在上一步中生成的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命令带上所需的JWT来访问/secured/roles-allowed
$ 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)的认证方案

  • 一个非空的JsonWebToken

  • birthdate声明(claim)的值

使用JsonWebToken和声明(claim)注入

现在我们可以生成一个JWT来访问我们的安全REST端点,让我们看看我们还能用 JsonWebToken 接口和JWT请求做什么。 org.eclipse.microprofile.jwt.JsonWebToken 接口扩展了 java.security.Principal 接口,事实上,它是我们之前使用的 javax.ws.rs.core.SecurityContext#getUserPrincipal() 调用返回的对象的类型。这意味着不使用CDI但可以访问REST容器 SecurityContext 的代码,可以通过转换 SecurityContext#getUserPrincipal() 来得到调用者 JsonWebToken 的接口。

这个 JsonWebToken 接口定义了用于访问底层JWT中的声明(claims)的方法。它为MicroProfile JWT RBAC规范所要求的共同声明(common claims)以及JWT中可能存在的任何的声明提供访问器。

所有的JWT声明(claims)也可以被注入。让我们用另一个端点/secured/roles-allowed-admin来扩展我们的 TokenSecuredResource ,它使用注入的 birthdate (而不是从 JsonWebToken 得来):

package org.acme.security.jwt;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.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 的声明(claim)作为 String 注入 - 这就是为什么现在需要 @RequestScoped 范围。
3 在这里,我们使用注入的 birthday 声明(claim)来建立最终的回复。

现在再次生成令牌并运行:

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

打包并运行应用程序

像往常一样,该应用程序可以用以下方式打包:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

执行程序使用 java -jar target/quarkus-app/quarkus-run.jar

Runner jar的例子
$ java -jar target/quarkus-app/quarkus-run.jar
2019-03-28 14:27:48,839 INFO  [io.quarkus] (main) Quarkus 2.14.2.Final started in 0.796s. Listening on: http://[::]:8080
2019-03-28 14:27:48,841 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, resteasy-reactive-jackson, security, smallrye-jwt]

你也可以用以下方法生成本地可执行文件:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native
Native Executable的例子
[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, resteasy-reactive, resteasy-reactive-jackson, security, smallrye-jwt]

探索解决方案

位于 security-jwt-quickstart 目录中的解决方案库包含了我们在本快速入门指南中研究过的所有版本,以及一些额外的端点,用以说明将 JsonWebToken 及其声明注入到那些使用CDI APIs的子资源。我们建议你查看快速入门解决方案并探索 security-jwt-quickstart 目录,以了解更多关于SmallRye JWT扩展功能的信息。

参考指南

支持的注射范围

这里 @ApplicationScoped , @Singleton@RequestScoped 外层Bean注入作用域在注入 org.eclipse.microprofile.jwt.JsonWebToken 时都是被支持的, @RequestScoped 作用域对 JsonWebToken 强制执行,以确保当前令牌被呈现。

然而,当单个令牌要求被注入为简单的类型,例如 String ,必须使用 @RequestScoped

package org.acme.security.jwt;

import javax.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 来访问各个声明(claims),在这种情况下,设置 @RequestScoped 是没有必要的。

请参阅 MP JWT CDI注入要求 以了解更多细节。

支持的公钥格式

公钥可以采用以下任何一种格式,按优先顺序排列:

  • 公钥密码学标准#8(PKCS#8) PEM

  • JSON网络密钥(JWK)

  • JSON网络密钥集(JWKS)

  • JSON Web Key (JWK) Base64 URL 编码

  • JSON网络密钥集(JWKS)Base64 URL编码

处理验证密钥

如果你需要使用非对称RSA或椭圆曲线(EC)密钥来验证令牌签名,那么请使用 mp.jwt.verify.publickey.location 属性来参考本地或远程验证密钥。

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

如果你需要使用对称密匙验证令牌签名,那么必须使用 JSON Web Key (JWK)或 JSON Web Key Set (JWK Set)格式来呈现这个密匙,例如:

{
 "keys": [
   {
     "kty":"oct",
     "kid":"secretKey",
     "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
   }
 ]
}

这个密匙JWK也需要用 smallrye.jwt.verify.key.locationsmallrye.jwt.verify.algorithm 应该设置为 HS256 / HS384 / HS512

用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);

你也可以用它来定制令牌的验证或解密方式。例如,可以提供一个本地 SecretKey

import javax.crypto.SecretKey;
import javax.ws.rs.GET;
import javax.ws.rs.core.NewCookie;
import javax.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 {
  @Inject JWTParser parser;
  private String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMko";

  @GET
  @Produces("text/plain")
  public Response getUserName(@CookieParam("jwt") String jwtCookie) {
    Response response = null;
    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();
    }
  }
}

也请参见 如何直接添加SmallRye JWT 部分,关于在没有 quarkus-smallrye-jwt 提供的 HTTP 支持的情况下使用 JWTParser

令牌解密

如果你的应用程序需要接受带有加密的声明(claim)或带有加密的内部签名声明的令牌,那么你所要做的就是设置 smallrye.jwt.decrypt.key.location ,指向解密密钥的位置。

如果这是唯一被设置的密钥属性,那么传入的令牌预计将只包含加密的claims。如果 mp.jwt.verify.publickeymp.jwt.verify.publickey.location 验证属性也被设置,那么传入的令牌预计将包含加密的内签令牌。

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

定制工厂

io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipalFactory 默认用于解析和验证JWT令牌,并将其转换为 JsonWebToken。它使用 MP JWT smallrye-jwt 这些在 Configuration 中列出的属性来验证和定制JWT令牌。

如果你需要提供你自己的工厂,例如,避免再次验证已经被防火墙验证过的令牌,那么你可以通过提供 META-INF/services/io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory 资源来使用 ServiceLoader 机制,或者干脆像这样有一个 Alternative CDI bean 实现:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.annotation.Priority;
import javax.enterprise.context.ApplicationScoped;
import javax.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());
        }
    }
}

令牌传播

关于不记名(Bearer)访问令牌向下游服务的传播,请参见 令牌传播 部分。

测试

Wiremock

如果你配置 mp.jwt.verify.publickey.location ,以指向HTTPS或基于HTTP的JsonWebKey(JWK)集,那么你可以使用与 OpenID Connect Bearer Token集成测试 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集成测试 Keycloak 部分中描述的方法,只是需改变 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

本地公钥

你可以使用与 OpenID Connect承载令牌集成测试 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 注解

添加以下依赖关系:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-security-jwt</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
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 注释是可选的,可以用来设置额外的标记要求。

如何检查日志中的错误

请启用 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

主动认证

如果你想在调用公共端点方法时跳过令牌验证,那么请停用 主动验证

请注意,如果没有进行令牌验证,你就不能在公共方法中使用注入的 JsonWebToken

如何直接添加SmallRye JWT

如果你使用的Quarkus扩展不支持 HTTP (例如: Quarkus GRPC ),或者他们自己的扩展使用了特定的 HTTP ,与 quarkus-smallrye-jwtVert.x HTTP 提供的支持相冲突(例如: Quarkus Amazon Lambda ),并且你想 用JWTParser解析和验证JsonWebToken ,那么请直接使用 smallrye-jwt ,而不是 quarkus-smallrye-jwt

添加这个依赖性:

pom.xml
<dependency>
    <groupId>io.smallrye</groupId>
    <artifactId>smallrye-jwt</artifactId>
</dependency>
build.gradle
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

配置参考

Quarkus配置

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

类型

默认

The MP-JWT configuration object

Environment variable: QUARKUS_SMALLRYE_JWT_ENABLED

boolean

true

The name of the java.security.Provider that supports SHA256withRSA signatures

Environment variable: QUARKUS_SMALLRYE_JWT_RSA_SIG_PROVIDER

string

SunRsaSign

Enable this property if fetching the remote keys can be a time-consuming operation. Do not enable it if you use the local keys.

Environment variable: QUARKUS_SMALLRYE_JWT_BLOCKING_AUTHENTICATION

boolean

false

MicroProfile JWT配置

Property Name 默认 描述

mp.jwt.verify.publickey

none

这个 mp.jwt.verify.publickey 配置属性允许将公钥文本本身作为一个字符串提供。公钥将按照 支持的公钥格式 一节中所定义的顺序,从提供的字符串中解析出来。

mp.jwt.verify.publickey.location

none

配置属性允许指定公钥的外部或内部位置。该值可以是一个相对路径或一个URL。如果该值指向一个基于HTTPS的JWK集,那么为了让它在本地模式下工作, quarkus.ssl.native 属性也必须被设置为 true ,更多细节请参见 使用SSL与本地可执行程序

mp.jwt.verify.publickey.algorithm

RS256

签名算法。将其设置为 ES256 ,以支持椭圆曲线签名算法。

mp.jwt.decrypt.key.location

none

配置属性允许指定私人解密密钥的外部或内部位置。

mp.jwt.verify.issuer

none

配置属性指定了服务器将接受为有效的JWT的 iss (签发者)声明的值。

mp.jwt.verify.audiences

none

逗号分隔的列表,其中列出了一个令牌的 aud 声明可能包含的受众。

mp.jwt.token.header

Authorization

如果使用另一个header(如 Cookie )来传递令牌,则设置此属性。

mp.jwt.token.cookie

none

包含令牌的cookie的名称。只有当 mp.jwt.token.header 被设置为 Cookie ,该属性才会有效。

额外的SmallRye JWT配置

SmallRye JWT提供了更多的属性,可以用来定制令牌的处理:

Property Name 默认 描述

smallrye.jwt.verify.key.location

NONE

验证密钥的位置,可以指向公钥和秘钥。秘密密钥只能是JWK格式。注意,如果设置了这个属性,mp.jwt.verify.publickey.location 将被忽略。

smallrye.jwt.verify.algorithm

签名算法。此属性只能用于设置所需的对称算法,如 HS256 。对于设置非对称算法,如已被弃用的 ES256 - 请使用 mp.jwt.verify.publickey.algorithm 代替。

smallrye.jwt.verify.key-format

ANY

将此属性设置为特定的密钥格式,如 PEM_KEY , PEM_CERTIFICATE , JWKJWK_BASE64URL ,以优化验证密钥的加载方式。

smallrye.jwt.verify.relax-key-validation

false

放宽密钥验证,将此属性设置为 true ,将允许长度小于2048比特的公共RSA密钥。

smallrye.jwt.verify.certificate-thumbprint

false

如果此属性被启用,那么签名的令牌必须包含 x5tx5t#S256 X509Certificate的thumbprint headers。在这种情况下,验证密钥只能是JWK或PEM证书密钥格式。JWK密钥必须有一个 x5c(Base64编码的X509Certificate)属性设置。

smallrye.jwt.token.header

Authorization

如果使用另一个header,如 Cookie ,来传递令牌,则设置此属性。此属性已被废弃—请使用 mp.jwt.token.header

smallrye.jwt.token.cookie

none

包含token的cookie的名称。该属性只有在 smallrye.jwt.token.header 被设置为 Cookie 时才有效。该属性已被废弃—​请使用 mp.jwt.token.cookie

smallrye.jwt.always-check-authorization

false

如果将此属性设置为 trueAuthorization header也会被检查,即使 smallrye.jwt.token.header 被设置为 Cookie ,但又不存在名称为 smallrye.jwt.token.cookie 的cookie。

smallrye.jwt.token.schemes

Bearer

逗号分隔的列表,其中包含一个可替代的单一或多个schemes,例如, DPoP

smallrye.jwt.token.kid

none

密钥标识符。如果它被设置,那么验证JWK密钥以及每个JWT标记必须有一个匹配的 kid header。

smallrye.jwt.time-to-live

none

JWT可能被发布使用的最大秒数。实际上,JWT的到期日和签发日期之间的差异不得超过这个值。将此属性设置为非正值,可以取消对令牌具有有效的 iat(签发日期)声明的要求。

smallrye.jwt.require.named-principal

true

如果一个应用程序依赖于 java.security.Principal ,返回一个名称,那么一个标记必须有一个 upnpreferred_usernamesub 的声明集。如果应用程序代码没有这些要求来可靠地处理非空的 Principal 名称,设置此属性将导致SmallRye JWT抛出一个异常。

smallrye.jwt.path.sub

none

包含subject名称的声明的路径。它从顶级的JSON对象开始,可以包含多个段,每个段只代表一个JSON对象名称,例如: realms/subject 。如果一个标记没有 sub 声明,但在一个不同的声明里设置了subject,则可以使用这个属性。在命名空间限定的声明上使用双引号。

smallrye.jwt.claims.sub

none

当当前令牌没有可用的标准或自定义 sub 声明时,此属性可用于设置默认的sub声明值。如果没有设置 upnpreferred_usernamesub 声明,该属性可有效地用于自定义 java.security.Principal 名称。

smallrye.jwt.path.groups

none

到包含组的声明的路径。它从顶级的JSON对象开始,可以包含多个段,每个段只代表一个JSON对象的名称,例如: realm/groups 。如果一个标记没有 groups 的声明,但在一个不同的声明中设置了组,就可以使用这个属性。在命名空间限定的声明上使用双引号。

smallrye.jwt.groups-separator

' '

分隔符,用于分割一个可能包含多个组值的字符串。只有当 smallrye.jwt.path.groups 属性指向一个值为字符串的自定义声明时,它才会被使用。默认值是一个单一的空格,因为一个标准的OAuth2 scope 声明可能包含一个空格分隔的序列。

smallrye.jwt.claims.groups

none

当当前令牌没有可用的标准或自定义的组声明时,此属性可用于设置默认组声明值。

smallrye.jwt.jwks.refresh-interval

60

JWK缓存刷新时间间隔,单位是分钟。它将被忽略,除非 mp.jwt.verify.publickey.location 指向基于HTTP或HTTPS URL的JWK设置,并且没有从JWK HTTPS端点返回具有正 max-age 参数值的HTTP Cache-Control 响应header。

smallrye.jwt.jwks.forced-refresh-interval

30

强制刷新JWK缓存的时间间隔,以分钟为单位,用于限制强制刷新尝试的频率,这可能发生在令牌验证失败时,因为缓存中没有能与当前令牌带 kid 属性的JWK密钥,相匹配的 kid header。除非 mp.jwt.verify.publickey.location 指向基于HTTP或HTTPS URL的JWK集,否则它将被忽略。

smallrye.jwt.expiration.grace

60

过期时间,以秒为单位。默认情况下,如果当前时间不超过令牌过期时间1分钟,过期的令牌仍将被接受。

smallrye.jwt.verify.aud

none

逗号分隔的列表,列出了令牌 aud 声明可能包含的受众。该属性已被废弃—请使用 mp.jwt.verify.audiences

smallrye.jwt.required.claims

none

一个token必须包含逗号分隔的声明(claims)列表。

smallrye.jwt.decrypt.key.location

none

配置属性允许指定私人解密密钥的外部或内部位置。此属性已被废弃 - 请使用 mp.jwt.decrypt.key.location

smallrye.jwt.decrypt.algorithm

RSA_OAEP

解密算法。

smallrye.jwt.decrypt.key

none

以字符串形式提供的解密密钥。

smallrye.jwt.token.decryption.kid

none

解密密钥标识符。如果它被设置,那么解密JWK密钥以及每个JWT标记必须有一个匹配的 kid header。

smallrye.jwt.client.tls.certificate.path

none

如果需要通过 HTTPS 获取密钥,则需要配置 TLS 信任证书的路径。

smallrye.jwt.client.tls.trust-all

false

信任所有的主机名。如果钥匙必须通过 HTTPS 获得,并且该属性被设置为 true ,那么所有的主机名都被默认信任。

smallrye.jwt.client.tls.hosts

none

受信任的主机名的集合。如果钥匙必须通过 HTTPS 获得,并且 smallrye.jwt.client.tls.trust-all 被设置为 false ,那么这个属性可以用来配置可信的主机名。

smallrye.jwt.http.proxy.host

none

HTTP代理主机。

smallrye.jwt.http.proxy.port

80

HTTP代理端口。

smallrye.jwt.keystore.type

JKS

如果 mp.jwt.verify.publickey.locationmp.jwt.decrypt.key.location 指向一个 KeyStore 文件,这个属性可以用来定制一个密钥库类型。如果没有设置,那么文件名将被检查以确定密钥库类型,默认为 JKS

smallrye.jwt.keystore.provider

如果 mp.jwt.verify.publickey.locationmp.jwt.decrypt.key.location 指向一个 KeyStore 文件,该属性可用于定制一个 KeyStore 提供者。

smallrye.jwt.keystore.password

Keystore密码。如果有 mp.jwt.verify.publickey.locationmp.jwt.decrypt.key.location ,则此属性必须被设置。

smallrye.jwt.keystore.verify.key.alias

这个属性必须被设置,以确定一个公共验证密钥,如果 mp.jwt.verify.publickey.location 指向 KeyStore 文件,该密钥将从所匹配的证书中的 KeyStore 中提取。

smallrye.jwt.keystore.decrypt.key.alias

如果 mp.jwt.decrypt.key.location 指向 KeyStore 文件,则必须将此属性设置为标识专用解密密钥。

smallrye.jwt.keystore.decrypt.key.password

如果 mp.jwt.decrypt.key.location 指向 KeyStore 文件时,并且 KeyStore 中的专用解密密钥的密码与 smallrye.jwt.keystore.password 不同,则可以设置此属性。