使用 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 extension also supports Bearer Token Authorization and uses smallrye-jwt to represent the bearer tokens as JsonWebToken .
For more information, read the OIDC Bearer token authentication guide.
OpenID Connect extension has to be used if the Quarkus application needs to authenticate the users using OIDC Authorization Code Flow.
For more information, see OIDC code flow mechanism for protecting web applications
|
先决条件
完成这个指南,你需要:
-
大概15分钟
-
编辑器
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.8
-
如果你愿意的话,还可以选择使用Quarkus CLI
-
如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)
快速入门
解决方案
我们建议您按照下面几节的说明,一步一步地创建应用程序。不过,您可以直接跳到已完成的例子。
克隆 Git 仓库。 git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或者下载一个 存档 。
The solution is located in the security-jwt-quickstart
directory.
创建Maven项目
First, create a new project with the following command:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=security-jwt-quickstart"
该命令生成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'
这将在你的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")
Examine the Jakarta REST resource
在 src/main/java/org/acme/security/jwt/TokenSecuredResource.java
里,创建一个REST端点,内容如下:
package org.acme.security.jwt;
import jakarta.annotation.security.PermitAll;
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.Principal接口的扩展,提供了对与当前认证令牌authenticated token相关声明的访问。 |
2 | @PermitAll is a Jakarta common security annotation that indicates that the given endpoint is accessible by any caller, authenticated or not. |
3 | Here we inject the Jakarta REST SecurityContext to inspect the security state of the call and use a getResponseString() function to populate a response string. |
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, 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,所以我们不会期望有任何安全状态被终端看到,而响应也与此一致:
-
用户名称是匿名的
-
isHttps为false,因为没有使用https
-
authScheme为null
-
hasJWT为false
使用Ctrl-C来停止Quarkus服务器。
所以,现在让我们真正让一些东西变得安全。看看下面的新端点方法 helloRolesAllowed
:
package org.acme.security.jwt;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
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
@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 is a Jakarta common security annotation that indicates that the given endpoint is accessible by a caller if
they have either a "User" or "Admin" role assigned. |
4 | 在这里,我们以与 hello 方法相同的方式建立回复,但也通过直接调用注入的 JsonWebToken ,添加JWT birthdate 要求的值。 |
After you make this addition to your TokenSecuredResource
, rerun the ./mvnw compile quarkus:dev
command, and then try curl -v http://127.0.0.1:8080/secured/roles-allowed; echo
to attempt to access the new endpoint.
Your output should be as follows:
$ 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 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 | We are setting public key location to point to a classpath publicKey.pem location. We will add this key in part 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
Often one obtains a JWT from an identity manager like Keycloak, but for this quickstart we will generate our own using the JWT generation API provided by smallrye-jwt
.
For more information, see 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 声明(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)注入
Now that we can generate a JWT to access our secured REST endpoints, let’s see what more we can do with the JsonWebToken
interface and the JWT claims. The org.eclipse.microprofile.jwt.JsonWebToken
interface extends the java.security.Principal
interface, and is in fact the type of the object that is returned by the jakarta.ws.rs.core.SecurityContext#getUserPrincipal()
call we
used previously. This means that code that does not use CDI but does have access to the REST container SecurityContext
can get
hold of the caller JsonWebToken
interface by casting the SecurityContext#getUserPrincipal()
.
这个 JsonWebToken
接口定义了用于访问底层JWT中的声明(claims)的方法。它为MicroProfile JWT RBAC规范所要求的共同声明(common claims)以及JWT中可能存在的任何的声明提供访问器。
所有的JWT声明(claims)也可以被注入。让我们用另一个端点/secured/roles-allowed-admin来扩展我们的 TokenSecuredResource
,它使用注入的 birthdate
(而不是从 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 (1)
public class TokenSecuredResource {
@Inject
JsonWebToken jwt; (2)
@Inject
@Claim(standard = Claims.birthdate)
String birthdate; (3)
@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; (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 | RequestScoped scope is required to support an injection of the birthday claim as String . |
2 | 这里我们注入JsonWebToken。 |
3 | 在这里,我们把 birthday 的声明(claim)作为 String 注入 - 这就是为什么现在需要 @RequestScoped 范围。 |
4 | 在这里,我们使用注入的 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 3.15.1 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]
探索解决方案
The solution repository located in the security-jwt-quickstart
directory contains all the versions we have worked through in this quickstart guide as well as some additional endpoints that illustrate subresources with injection of 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
directory to learn more about the SmallRye JWT extension features.
参考指南
支持的注射范围
这里 @ApplicationScoped
, @Singleton
和 @RequestScoped
外层Bean注入作用域在注入 org.eclipse.microprofile.jwt.JsonWebToken
时都是被支持的, @RequestScoped
作用域对 JsonWebToken
强制执行,以确保当前令牌被呈现。
然而,当单个令牌要求被注入为简单的类型,例如 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
来访问各个声明(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
:
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();
}
}
}
Please also see the How to Add SmallRye JWT directly section about using JWTParser
without the HTTP
support provided by quarkus-smallrye-jwt
.
令牌解密
If your application needs to accept the tokens with the encrypted claims or the encrypted inner-signed claims, all you have to do is set
smallrye.jwt.decrypt.key.location
pointing to the decryption key.
If this is the only key property that is set, the incoming token is expected to contain the encrypted claims only.
If either mp.jwt.verify.publickey
or mp.jwt.verify.publickey.location
verification properties are also set then the incoming token is expected to contain the encrypted inner-signed token.
请参阅 使用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 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
extension uses SmallRye JWT library which is currently not reactive.
What it means from the perspective of quarkus-smallrye-jwt
which operates as part of the reactive Quarkus security architecture, is that an IO thread entering the SmallRye JWT verification or decryption code might block in one of the following cases:
-
Default key resolver refreshes
JsonWebKey
set containing the keys which involves a remote call to the OIDC endpoint -
Custom key resolver such as
AWS Application Load Balancer
(ALB
) key resolver, resolves the keys against the AWS ALB key endpoint using the current token’s key identifier header value
In such cases, if the connections are slow, for example, it may take more than 3 seconds to get a response from the key endpoint, the current event loop thread will most likely block.
To prevent it, set quarkus.smallrye-jwt.blocking-authentication=true
.
令牌传播
Please see the Token Propagation section about the Bearer access token propagation to the downstream services.
测试
Wiremock
If you configure mp.jwt.verify.publickey.location
to point to HTTPS or HTTP based JsonWebKey (JWK) set then you can use the same approach as described in the OpenID Connect Bearer Token Integration testing Wiremock
section but only change the application.properties
to use MP JWT configuration properties instead:
# 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
If you work with Keycloak and configure mp.jwt.verify.publickey.location
to point to HTTPS or HTTP based JsonWebKey (JWK) set then you can use the same approach as described in the OpenID Connect Bearer Token Integration testing Keycloak section but only change the application.properties
to use MP JWT configuration properties instead:
# 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, 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}
本地公钥
You can use the same approach as described in the OpenID Connect Bearer Token Integration testing Local Public Key
section but only change the application.properties
to use MP JWT configuration properties instead:
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, disable the proactive authentication.
请注意,如果没有进行令牌验证,你就不能在公共方法中使用注入的 JsonWebToken
。
如何直接添加SmallRye JWT
To parse and verify JsonWebToken with JWTParser, use smallrye-jwt
instead of quarkus-smallrye-jwt
directly for the following situations:
-
You work with Quarkus extensions that do not support
HTTP
, such asQuarkus GRPC
. -
You provide an extension-specific
HTTP
, the support of which conflicts with the support of those offered byquarkus-smallrye-jwt
andVert.x HTTP
, such asQuarkus AWS Lambda
.
Start with adding the smallrye-jwt
dependency:
<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
Configuration property |
类型 |
默认 |
---|---|---|
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 |
|
Always create HTTP 401 challenge, even for requests containing no authentication credentials. JWT authentication mechanism will return HTTP 401 when an authentication challenge is required. However if it is used alongside one of the interactive authentication mechanisms then returning HTTP 401 to the users accessing the application from a browser may not be desired. If you prefer you can request that JWT authentication mechanism does not create a challenge in such cases by setting this property to 'true'. Environment variable: Show more |
boolean |
|
MicroProfile JWT配置
Property Name | 默认 | 描述 |
---|---|---|
|
|
The |
|
|
配置属性允许指定公钥的外部或内部位置。该值可以是一个相对路径或一个URL。如果该值指向一个基于HTTPS的JWK集,那么为了让它在本地模式下工作, |
|
|
签名算法。将其设置为 |
|
|
配置属性允许指定私人解密密钥的外部或内部位置。 |
|
|
配置属性指定了服务器将接受为有效的JWT的 |
|
|
逗号分隔的列表,其中列出了一个令牌的 |
|
|
Clock skew in seconds used during the token expiration and age verification. An expired token is accepted if the current time is within the number of seconds specified by this property after the token expiration time. The default value is 60 seconds. |
|
|
Number of seconds that must not elapse since the token |
|
|
如果使用另一个header(如 |
|
|
包含令牌的cookie的名称。只有当 |
额外的SmallRye JWT配置
SmallRye JWT提供了更多的属性,可以用来定制令牌的处理:
Property Name | 默认 | 描述 |
---|---|---|
|
|
验证密钥的位置,可以指向公钥和秘钥。秘密密钥只能是JWK格式。注意,如果设置了这个属性, |
|
签名算法。此属性只能用于设置所需的对称算法,如 |
|
|
|
将此属性设置为特定的密钥格式,如 |
|
|
By default, PEM, JWK or JWK key sets can be read from the local file system or fetched from URIs as required by MicroProfile JWT specification. Set this property to |
|
|
放宽密钥验证,将此属性设置为 |
|
|
如果此属性被启用,那么签名的令牌必须包含 |
|
|
如果使用另一个header,如 |
|
|
Key cache size. Use this property, as well as |
|
|
Key cache entry time-to-live in minutes. Use this property, as well as |
|
|
Name of the cookie containing a token. This property will be effective only if |
|
|
如果将此属性设置为 |
|
|
逗号分隔的列表,其中包含一个可替代的单一或多个schemes,例如, |
|
|
密钥标识符。如果它被设置,那么验证JWK密钥以及每个JWT标记必须有一个匹配的 |
|
|
JWT可能被发布使用的最大秒数。实际上,JWT的到期日和签发日期之间的差异不得超过这个值。将此属性设置为非正值,可以取消对令牌具有有效的 |
|
|
如果一个应用程序依赖于 |
|
|
包含subject名称的声明的路径。它从顶级的JSON对象开始,可以包含多个段,每个段只代表一个JSON对象名称,例如: |
|
|
当当前令牌没有可用的标准或自定义 |
|
|
到包含组的声明的路径。它从顶级的JSON对象开始,可以包含多个段,每个段只代表一个JSON对象的名称,例如: |
|
|
分隔符,用于分割一个可能包含多个组值的字符串。只有当 |
|
|
当当前令牌没有可用的标准或自定义的组声明时,此属性可用于设置默认组声明值。 |
|
|
JWK缓存刷新时间间隔,单位是分钟。它将被忽略,除非 |
|
|
强制刷新JWK缓存的时间间隔,以分钟为单位,用于限制强制刷新尝试的频率,这可能发生在令牌验证失败时,因为缓存中没有能与当前令牌带 |
|
|
Expiration grace in seconds. By default an expired token will still be accepted if the current time is no more than 1 min after the token expiry time. This property is deprecated. Use |
|
|
逗号分隔的列表,列出了令牌 |
|
|
一个token必须包含逗号分隔的声明(claims)列表。 |
|
|
Config property allows for an external or internal location of Private Decryption Key to be specified. This property is deprecated - use |
|
|
解密算法。 |
|
|
以字符串形式提供的解密密钥。 |
|
|
解密密钥标识符。如果它被设置,那么解密JWK密钥以及每个JWT标记必须有一个匹配的 |
|
|
如果需要通过 |
|
|
信任所有的主机名。如果钥匙必须通过 |
|
|
受信任的主机名的集合。如果钥匙必须通过 |
|
|
HTTP代理主机。 |
|
|
HTTP代理端口。 |
|
|
如果 |
|
如果 |
|
|
Keystore密码。如果有 |
|
|
这个属性必须被设置,以确定一个公共验证密钥,如果 |
|
|
如果 |
|
|
如果 |
|
|
|
Set this property to true to resolve the remote keys at the application startup. |