将 JWT 与 Javalin 应用程序结合使用

您将学到什么
在本教程中,我们将介绍该扩展及其功能,然后展示其基本用法,最后深入讲解如何使用该扩展提供的一些组件。本教程假设您了解 JWT 是什么。如果您不了解,可以查看这篇文章,它提供了简单但详尽的介绍。

依赖项
目前没有 Maven 依赖项可以直接获取扩展;您需要拉取源代码。很快就会有依赖项准备好,本教程也会相应更新。

扩展本身依赖于Auth0 Java JWT 库。

扩展
注意:建议您首先熟悉 Auth0 Java JWT。 扩展本身很小,它提供了三件事:

Javalin Context 的辅助函数使使用 JWT 更加容易,包括:从授权标头中提取令牌、向 cookie 添加/从 cookie 获取令牌以及将解码的 JWT 对象添加到上下文以供将来的处理程序使用

解码助手负责提取、验证和添加解码对象到上下文中

访问管理器

不要求使用扩展的所有部分,您可以只使用特定情况所需的部分。

初步步骤
任何使用该扩展的功能,都需要一个 JWT 提供程序(因为找不到更合适的词来形容)。提供程序是一种使用 JWT 的便捷方式,它封装了一个生成器和一个验证器。其中,生成器实现了函数式接口 JWTGeneratr,而验证器则是标准的 Auth0 JWTVerifier。

在创建提供程序之前,我们首先需要具备:一个用户类、一个生成器和一个验证器。在本教程中,我们假设以下类作为我们的用户类:

class MockUser {
String name;
String level;

MockUser(String name, String level) {
    this.name = name;
    this.level = level;
}

}
现在我们可以按如下方式创建我们的 JWT 提供程序:

//1.
Algorithm algorithm = Algorithm.HMAC256(“very_secret”);

//2.
JWTGenerator generator = (user, alg) -> {
JWTCreator.Builder token = JWT.create()
.withClaim(“name”, user.name)
.withClaim(“level”, user.level);
return token.sign(alg);
};

//3.
JWTVerifier verifier = JWT.require(algorithm).build();

//4.
JWTProvider provider = JWTProvider(algorithm, generator, verifier);
1) 首先,我们初始化要使用的算法。在本例中,我们选择了 HMAC256,但您也可以尝试其他变体。提示:如果您将应用程序拆分为多个服务,并且只有一个服务负责发放令牌,那么可以考虑使用非对称算法,例如 RSA。

2) 第二步,我们创建 JWT 生成器。它实现了一个函数,该函数接受一个对象和算法,生成一个带有一组声明的令牌,并返回已签名的令牌。

3) 第三步,我们使用 Auth0 提供的构建器创建一个验证器。在本例中,我们只提供了算法,但根据您的具体情况,还可以添加更多算法。

4) 我们最终创建了我们的提供程序,我们将在本教程的其余部分中使用它。

基本示例
现在一切准备就绪,终于可以在应用程序中使用该提供程序了。我们将创建一个只有两个路由的简单应用程序:/generate和/validate。

//
// .. create your Javalin app …
//
Handler generateHandler = context -> {
MockUser mockUser = new MockUser(“Mocky McMockface”, “user”);
String token = provider.generateToken(mockUser);
context.json(new JWTResponse(token));
};

Handler validateHandler = context -> {
Optional decodedJWT = JavalinJWT.getTokenFromHeader(context)
.flatMap(provider::validateToken);

if (!decodedJWT.isPresent()) {
    context.status(401).result("Missing or invalid token");
}
else {
    context.result("Hi " + decodedJWT.get().getClaim("name").asString());
}

};

app.get(“/generate”, generateHandler);
app.get(“/validate”, validateHandler);
在generateHandler中,我们使用提供程序为模拟用户生成令牌。然后,我们将该令牌包装在一个响应对象中,该响应对象的 JSON 格式为 {“jwt”: “…”}。在实际示例中,该对象可能还包含续订令牌、到期日期等信息。

在validateHandler中,我们使用提供的辅助函数之一从授权标头中提取令牌,然后使用提供程序对其进行验证。授权标头值必须具有Bearer方案。空选项表示缺少令牌值或令牌无效。

现在,如果您访问 /generate,您将获得已创建用户的 JWT。然后,您需要将该令牌放入使用“Bearer”方案的授权标头中,并向 /validate 发出请求。

一些需要检查的链接:

授权标头
承载方案
高级示例
上一个示例展示了扩展的基本功能,但您可能已经注意到实现中存在两个不切实际的地方:您需要为每个需要 JWT 的处理程序提取和验证 JWT,并且需要在每个处理程序内部执行访问控制。在本例中,我们将展示如何使用解码处理程序和访问管理器解决这两个问题。

解码处理程序负责解码和验证 JWT,然后将解码后的对象添加到上下文中以供将来的处理程序使用。解码处理程序有两种:一种用于从授权标头读取令牌,另一种用于从 Cookie 读取令牌。您可以选择您喜欢的任意一种。解码处理程序只需使用辅助函数创建即可,如下所示:

Handler decodeHandler = JavalinJWT.createHeaderDecodeHandler(provider);
注意:人们常误以为 JWT 是 Cookie 的替代品;实际上,它是会话的替代品,并且 Cookie 可用于承载 JWT

并且应该作为前置处理程序添加,无论是全局的还是特定路径的。在本例中,我们将其设置为全局的:

app.before(decodeHandler);
然后,我们将使用访问管理器来处理访问管理。访问管理器需要 JWT 声明的名称,该声明声明了用户的级别、用户级别和角色之间的映射,以及在没有可用令牌时的默认角色。为了便于本示例,以下列出了可用的角色及其映射:

enum Roles implements Role {
ANYONE,
USER,
ADMIN
}

Map<String, Role> rolesMapping = new HashMap<String, Role>() {{
put(“user”, Roles.USER);
put(“admin”, Roles.ADMIN);
}};
访问管理器的设置很简单,如下:

JWTAccessManager accessManager = new JWTAccessManager(“level”, rolesMapping, Roles.ANYONE);
app.accessManager(accessManager);
请注意,用户的级别声明必须与生成器中指定的级别相匹配。

现在我们已经设置好了解码处理程序和访问管理器,我们可以继续并充分利用它们。

Handler generateHandler = context -> {
MockUser mockUser = new MockUser(“Mocky McMockface”, “user”);
String token = provider.generateToken(mockUser);
context.json(new JWTResponse(token));
};

Handler validateHandler = context -> {
DecodedJWT decodedJWT = JavalinJWT.getDecodedFromContext(context);
context.result(“Hi “ + decodedJWT.getClaim(“name”).asString());
};

app.get(“/generate”, generateHandler, roles(Roles.ANYONE));
app.get(“/validate”, validateHandler, roles(Roles.USER, Roles.ADMIN));
app.get(“/adminslounge”, validateHandler, roles(Roles.ADMIN));
虽然 generateHandler 与上一个示例保持不变,但validateHandler 现在更加简洁、更专注。您不再需要在处理程序中进行用户授权。为了突出这一点,示例显示/validate和/adminlounge 具有相同的处理程序,但访问角色不同。

作者:Jeebiz  创建时间:2025-05-04 00:42
最后编辑:Jeebiz  更新时间:2025-05-04 00:55