Вход и роли
Сейчас любой может создать или удалить пост. Нужно, чтобы читать могли все, писать — вошедшие пользователи, а удалять — только администратор. Для этого есть два понятия. Аутентификация — узнать, кто пришёл (проверить логин и пароль). Авторизация — решить, можно ли ему это действие. В Javalin маршрутам назначают роли, а проверку пишут сами в обработчике beforeMatched.
Роли
Роль — любое перечисление, которое реализует интерфейс RouteRole:
// src/main/java/com/example/blog/Role.java
package com.example.blog;
import io.javalin.security.RouteRole;
public enum Role implements RouteRole {
ANYONE, USER, ADMIN
}Роли перечисляются последними аргументами при объявлении маршрута:
// src/main/java/com/example/blog/Main.java
path("/posts", () -> {
get(posts::getAll, Role.ANYONE);
post(posts::create, Role.USER, Role.ADMIN);
path("/{id}", () -> {
get(posts::getOne, Role.ANYONE);
delete(posts::delete, Role.ADMIN);
});
});
get("/blog", posts::page, Role.ANYONE);Проверка
Сами роли ничего не запрещают — это лишь метки. Проверку выполняет класс Auth. Пользователь передаёт логин и пароль по схеме Basic: в заголовке Authorization, который curl формирует ключом -u.
// src/main/java/com/example/blog/Auth.java
public class Auth {
private record User(String password, Role role) {
}
private static final Map<String, User> USERS = Map.of(
"anna", new User("changeme", Role.ADMIN),
"oleg", new User("changeme", Role.USER)
);
public static void check(Context ctx) {
Set<RouteRole> allowed = ctx.routeRoles();
if (allowed.isEmpty() || allowed.contains(Role.ANYONE)) {
return;
}
Role role = roleOf(ctx);
if (role == null) {
ctx.header("WWW-Authenticate", "Basic");
throw new UnauthorizedResponse();
}
if (!allowed.contains(role)) {
throw new ForbiddenResponse();
}
}
private static Role roleOf(Context ctx) {
BasicAuthCredentials credentials = ctx.basicAuthCredentials(); // io.javalin.security
if (credentials == null) {
return null;
}
User user = USERS.get(credentials.getUsername());
if (user == null || !user.password().equals(credentials.getPassword())) {
return null;
}
return user.role();
}
}ctx.routeRoles() возвращает роли найденного маршрута. Нет входа — 401 Unauthorized, вошёл, но роль не подходит — 403 Forbidden. Пользователи для простоты лежат в словаре; в настоящем проекте их хранят в базе, а пароли — только в виде хеша.
Подключите проверку в настройках:
// src/main/java/com/example/blog/Main.java
config.routes.beforeMatched(Auth::check);Проверка с curl
curl -i -X POST http://localhost:8080/posts -d '{"title":"x","body":"xxxxxx","author":"Олег"}'
curl -i -u oleg:changeme -X DELETE http://localhost:8080/posts/2
curl -i -u anna:changeme -X DELETE http://localhost:8080/posts/2HTTP/1.1 401 Unauthorized
Content-Type: text/plain
WWW-Authenticate: Basic
Unauthorized
HTTP/1.1 403 Forbidden
Content-Type: text/plain
Forbidden
HTTP/1.1 204 No Content
Content-Type: text/plain
Без пароля создать пост нельзя, Олег не может удалять, а Анна-администратор может. GET /posts по-прежнему открыт всем. Basic-авторизация передаёт пароль почти открытым текстом, поэтому на настоящем сервере её используют только через HTTPS.
Частая ошибка
Если забыть строку config.routes.beforeMatched(Auth::check), маршруты с ролями остаются открытыми: Javalin сохраняет роли, но сам их не проверяет. Маршрут с Role.ADMIN ответит 200 любому, и никакой ошибки в логе не будет. После подключения защиты всегда проверяйте запрос без -u — он должен получить 401.