在 Javalin 中创建安全的 REST API
在 Javalin 中创建安全的 REST API
依赖项
首先,我们需要创建一个带有一些依赖项的 Maven 项目:(→ 教程)
<dependencies>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin-bundle</artifactId>
<version>6.6.0</version>
</dependency>
</dependencies>
创建控制器
我们需要一些值得保护的东西。假设我们有一个非常重要的 API,用于操作用户数据库。我们创建一个控制器对象,其中包含一些虚拟数据和 CRUD 操作:
import io.javalin.http.Context;
import java.util.*;
public class UserController {
public record User(String name, String email) {}
private static final Map<String, User> users;
static {
var tempMap = Map.of(
randomId(), new User("Alice", "alice@alice.kt"),
randomId(), new User("Bob", "bob@bob.kt"),
randomId(), new User("Carol", "carol@carol.kt"),
randomId(), new User("Dave", "dave@dave.kt")
);
users = new HashMap<>(tempMap);
}
public static void getAllUserIds(Context ctx) {
ctx.json(users.keySet());
}
public static void createUser(Context ctx) {
users.put(randomId(), ctx.bodyAsClass(User.class));
}
public static void getUser(Context ctx) {
ctx.json(users.get(ctx.pathParam("userId")));
}
public static void updateUser(Context ctx) {
users.put(ctx.pathParam("userId"), ctx.bodyAsClass(User.class));
}
public static void deleteUser(Context ctx) {
users.remove(ctx.pathParam("userId"));
}
private static String randomId() {
return UUID.randomUUID().toString();
}
}
创建角色
现在我们已经有了功能,我们需要为系统定义一组角色。这可以通过实现RouteRole接口来实现io.javalin.security.RouteRole
。我们将定义三个角色,一个代表“任何人”,一个代表读取用户数据的权限,一个代表写入用户数据的权限。
import io.javalin.security.RouteRole;
enum Role implements RouteRole { ANYONE, USER_READ, USER_WRITE }
设置 API
现在我们有了角色,我们可以实现我们的端点:
import io.javalin.Javalin;
import static io.javalin.apibuilder.ApiBuilder.*;
public class Main {
public static void main(String[] args) {
Javalin app = Javalin.create(config -> {
config.router.mount(router -> {
router.beforeMatched(Auth::handleAccess);
}).apiBuilder(() -> {
get("/", ctx -> ctx.redirect("/users"), Role.ANYONE);
path("users", () -> {
get(UserController::getAllUserIds, Role.ANYONE);
post(UserController::createUser, Role.USER_WRITE);
path("{userId}", () -> {
get(UserController::getUser, Role.USER_READ);
patch(UserController::updateUser, Role.USER_WRITE);
delete(UserController::deleteUser, Role.USER_WRITE);
});
});
});
}).start(7070);
}
}
现在每个端点都被赋予了一个角色:
- ANYONE能getAllUserIds
- USER_READ能getUser
- USER_WRITE可以createUser,updateUser并且deleteUser
现在,剩下的就是实现访问管理(Auth::handleAccess)。
实现授权
我们的访问管理器的规则很简单:
- 当端点有时ApiRole.ANYONE,所有请求将被处理
- 当端点有另一个角色集并且请求具有匹配的凭据时,该请求将被处理
- 否则,我们将停止请求并401 Unauthorized返回给客户端
这很好地转化为代码:
public static void handleAccess(Context ctx) {
var permittedRoles = ctx.routeRoles();
if (permittedRoles.contains(Role.ANYONE)) {
return; // anyone can access
}
if (userRoles(ctx).stream().anyMatch(permittedRoles::contains)) {
return; // user has role required to access
}
ctx.header(Header.WWW_AUTHENTICATE, "Basic");
throw new UnauthorizedResponse();
}
从上下文中提取用户角色
Javalin 中没有内置的ctx.userRoles
或userRoles(ctx)
,所以我们需要实现一些东西。首先,我们需要一个用户表。我们将创建一个表,map(Pair<String, String>, Set<Role>)
其中键是明文形式的用户名+密码(请不要在实际服务中这样做),值是用户角色:
record Pair(String a, String b) {}
private static final Map<Pair, List<Role>> userRolesMap = Map.of(
new Pair("alice", "weak-1234"), List.of(Role.USER_READ),
new Pair("bob", "weak-123456"), List.of(Role.USER_READ, Role.USER_WRITE)
);
现在我们有了用户表,我们需要对请求进行身份验证。我们从Basic-auth-header中获取用户名+密码 ,并将它们用作以下键userRoleMap:
public static List<Role> getUserRoles(Context ctx) {
return Optional.ofNullable(ctx.basicAuthCredentials())
.map(credentials -> userRolesMap.getOrDefault(new Pair(credentials.getUsername(), credentials.getPassword()), List.of()))
.orElse(List.of());
}
使用基本身份验证时,凭证将以纯文本形式传输(尽管经过 base64 编码)。 如果您在实际服务中使用基本身份验证,请务必启用 SSL。
结论
就这样!现在您拥有一个包含三个角色的安全 REST API。
作者:Jeebiz 创建时间:2025-05-04 00:18
最后编辑:Jeebiz 更新时间:2025-05-04 00:55
最后编辑:Jeebiz 更新时间:2025-05-04 00:55