使用 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项目
首先,我们需要一个新的项目。使用以下命令创建一个新的项目:
该命令生成Maven项目并导入 smallrye-jwt 扩展,其中包括MicroProfile JWT RBAC支持。
如果你已经配置了你的Quarkus项目,你可以通过在你的项目基础目录下运行以下命令,将 smallrye-jwt 扩展到你的项目。
quarkus extension add 'smallrye-jwt,smallrye-jwt-build'
./mvnw quarkus:add-extension -Dextensions='smallrye-jwt,smallrye-jwt-build'
./gradlew addExtension --extensions='smallrye-jwt,smallrye-jwt-build'
这将在您的构建文件中添加以下内容:
<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")
检查JAX-RS资源
在 src/main/java/org/acme/security/jwt/TokenSecuredResource.java 里,创建一个REST端点,内容如下:
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 。 |
运行应用程序
现在我们准备运行我们的应用程序。使用:
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, resteasy-reactive, resteasy-reactive-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,所以我们不会期望有任何安全状态被终端看到,而响应也与此一致:
-
用户名称是匿名的
-
isHttps为false,因为没有使用https
-
authScheme为null
-
hasJWT为false
使用Ctrl-C来停止Quarkus服务器。
所以,现在让我们真正让一些东西变得安全。看看下面的新端点方法 helloRolesAllowed :
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 -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 ,内容如下:
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 文件中。
-----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 :
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 :
-----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命令
生成私钥后需要一个额外的步骤,以便将其转换为PKCS#8格式。 openssl转换私钥的命令
你可以使用生成的这对密钥,而不是本快速入门中使用的密钥。 |
现在我们可以生成一个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签名。
最后,安全访问/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 -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
打包并运行该应用程序
像往常一样,该应用程序能够使用以下方式进行打包:
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 2.16.12.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]
你也可以通过以下命令生成本地可执行文件:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.package.type=native
[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.location 。 smallrye.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.publickey 或 mp.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 DevServices for Keycloak
mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
Note that the tokens issued by Keycloak have an iss (issuer) claim set to the realm endpoint address.
If your Quarkus application is running in a docker container then it may share a network interface with a Keycloak docker container launched by DevServices for Keycloak, with the Quarkus application and Keycloak communicating with each other via an internal shared docker network.
In such cases, use the following configuration instead:
# 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}
本地公钥
你可以使用与 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注解
添加以下依赖关系:
<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 注释是可选的,可以用来设置额外的标记要求。
|
This is particularly useful if the same set of security settings needs to be used in multiple test methods. |
如何检查日志中的错误
请启用 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
主动认证
If you’d like to skip the token verification when the public endpoint methods are invoked then please disable the proactive authentication.
请注意,如果没有进行令牌验证,你就不能在公共方法中使用注入的 JsonWebToken 。
如何直接添加SmallRye JWT
如果你使用的Quarkus扩展不支持 HTTP (例如: Quarkus GRPC ),或者他们自己的扩展使用了特定的 HTTP ,与 quarkus-smallrye-jwt 和 Vert.x HTTP 提供的支持相冲突(例如: Quarkus Amazon Lambda ),并且你想 用JWTParser解析和验证JsonWebToken ,那么请直接使用 smallrye-jwt ,而不是 quarkus-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
配置参考
Quarkus配置
Configuration property fixed at build time - All other configuration properties are overridable at runtime
类型 |
默认 |
|
|---|---|---|
The MP-JWT configuration object Environment variable: Show more |
boolean |
|
The name of the Environment variable: Show more |
string |
|
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: Show more |
boolean |
|
MicroProfile JWT配置
| Property Name | 默认 | 描述 |
|---|---|---|
|
|
这个 |
|
|
配置属性允许指定公钥的外部或内部位置。该值可以是一个相对路径或一个URL。如果该值指向一个基于HTTPS的JWK集,那么为了让它在本地模式下工作, |
|
|
签名算法。将其设置为 |
|
|
配置属性允许指定私人解密密钥的外部或内部位置。 |
|
|
配置属性指定了服务器将接受为有效的JWT的 |
|
|
逗号分隔的列表,其中列出了一个令牌的 |
|
|
如果使用另一个header(如 |
|
|
包含令牌的cookie的名称。只有当 |
额外的SmallRye JWT配置
SmallRye JWT提供了更多的属性,可以用来定制令牌的处理:
| Property Name | 默认 | 描述 |
|---|---|---|
|
|
验证密钥的位置,可以指向公钥和秘钥。秘密密钥只能是JWK格式。注意,如果设置了这个属性, |
|
签名算法。此属性只能用于设置所需的对称算法,如 |
|
|
|
将此属性设置为特定的密钥格式,如 |
|
|
放宽密钥验证,将此属性设置为 |
|
|
如果此属性被启用,那么签名的令牌必须包含 |
|
|
如果使用另一个header,如 |
|
|
包含token的cookie的名称。该属性只有在 |
|
|
如果将此属性设置为 |
|
|
逗号分隔的列表,其中包含一个可替代的单一或多个schemes,例如, |
|
|
密钥标识符。如果它被设置,那么验证JWK密钥以及每个JWT标记必须有一个匹配的 |
|
|
JWT可能被发布使用的最大秒数。实际上,JWT的到期日和签发日期之间的差异不得超过这个值。将此属性设置为非正值,可以取消对令牌具有有效的 |
|
|
如果一个应用程序依赖于 |
|
|
包含subject名称的声明的路径。它从顶级的JSON对象开始,可以包含多个段,每个段只代表一个JSON对象名称,例如: |
|
|
当当前令牌没有可用的标准或自定义 |
|
|
到包含组的声明的路径。它从顶级的JSON对象开始,可以包含多个段,每个段只代表一个JSON对象的名称,例如: |
|
|
分隔符,用于分割一个可能包含多个组值的字符串。只有当 |
|
|
当当前令牌没有可用的标准或自定义的组声明时,此属性可用于设置默认组声明值。 |
|
|
JWK缓存刷新时间间隔,单位是分钟。它将被忽略,除非 |
|
|
强制刷新JWK缓存的时间间隔,以分钟为单位,用于限制强制刷新尝试的频率,这可能发生在令牌验证失败时,因为缓存中没有能与当前令牌带 |
|
|
过期时间,以秒为单位。默认情况下,如果当前时间不超过令牌过期时间1分钟,过期的令牌仍将被接受。 |
|
|
逗号分隔的列表,列出了令牌 |
|
|
一个token必须包含逗号分隔的声明(claims)列表。 |
|
|
配置属性允许指定私人解密密钥的外部或内部位置。此属性已被废弃 - 请使用 |
|
|
解密算法。 |
|
|
以字符串形式提供的解密密钥。 |
|
|
解密密钥标识符。如果它被设置,那么解密JWK密钥以及每个JWT标记必须有一个匹配的 |
|
|
如果需要通过 |
|
|
信任所有的主机名。如果钥匙必须通过 |
|
|
受信任的主机名的集合。如果钥匙必须通过 |
|
|
HTTP代理主机。 |
|
|
HTTP代理端口。 |
|
|
如果 |
|
如果 |
|
|
Keystore密码。如果有 |
|
|
这个属性必须被设置,以确定一个公共验证密钥,如果 |
|
|
如果 |
|
|
如果 |