Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Вход и роли

Сейчас любой может создать или удалить пост. Нужно, чтобы читать могли все, писать — вошедшие пользователи, а удалять — только администратор. Для этого есть два понятия. Аутентификация — узнать, кто пришёл (проверить логин и пароль). Авторизация — решить, можно ли ему это действие. В 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/2
HTTP/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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.