Encodera

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

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

Оглавление

Jakarta Security

Удалять записи блога должна только Анна-администратор, а смотреть свой профиль — любой вошедший автор. Jakarta Security решает две задачи. Аутентификация — узнать, кто пришёл: проверить имя и пароль. Авторизация — решить, что ему можно: обычно по ролям, например admin и author.

Способ входа и роли

// src/main/java/com/example/blog/SecurityConfig.java
package com.example.blog;
 
import jakarta.annotation.security.DeclareRoles;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
 
@BasicAuthenticationMechanismDefinition(realmName = "blog")
@DeclareRoles({"admin", "author"})
@ApplicationScoped
public class SecurityConfig {
}

@BasicAuthenticationMechanismDefinition включает HTTP Basic: клиент отправляет имя и пароль в заголовке Authorization с каждым запросом. @DeclareRoles перечисляет роли приложения.

Хранилище пользователей

IdentityStore проверяет имя и пароль и сообщает группы пользователя. Группы с теми же именами, что и роли, становятся ролями.

// src/main/java/com/example/blog/BlogIdentityStore.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.security.enterprise.credential.UsernamePasswordCredential;
import jakarta.security.enterprise.identitystore.CredentialValidationResult;
import jakarta.security.enterprise.identitystore.IdentityStore;
import java.util.Map;
import java.util.Set;
 
@ApplicationScoped
public class BlogIdentityStore implements IdentityStore {
 
    private record User(String password, Set<String> groups) {
    }
 
    // только для учебного примера: в реальном приложении пользователи лежат в базе
    private final Map<String, User> users = Map.of(
            "anna", new User("changeme", Set.of("admin", "author")),
            "oleg", new User("changeme", Set.of("author")));
 
    public CredentialValidationResult validate(UsernamePasswordCredential credential) {
        User user = users.get(credential.getCaller());
        if (user == null || !credential.compareTo(credential.getCaller(), user.password())) {
            return CredentialValidationResult.INVALID_RESULT;
        }
        return new CredentialValidationResult(credential.getCaller(), user.groups());
    }
}

Сервер находит бин, реализующий IdentityStore, и вызывает метод validate для каждого входа. В настоящем приложении пароли хранят в базе в виде хешей — для этого есть готовое хранилище @DatabaseIdentityStoreDefinition.

Защита методов

Удаление записи разрешим только роли admin:

// src/main/java/com/example/blog/PostResource.java (фрагмент)
@DELETE
@Path("/{id}")
@RolesAllowed("admin")
public void delete(@PathParam("id") long id) {
    repository.delete(id);
}

Аннотация @RolesAllowed из пакета jakarta.annotation.security. Методы без неё остаются открытыми.

Кто вошёл

SecurityContext из Jakarta Security рассказывает о текущем пользователе:

// src/main/java/com/example/blog/MeResource.java
package com.example.blog;
 
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.security.enterprise.SecurityContext;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
 
@Path("/me")
@Produces(MediaType.APPLICATION_JSON)
public class MeResource {
 
    public record Me(String name, boolean admin) {
    }
 
    @Inject
    SecurityContext security;
 
    @GET
    @RolesAllowed({"admin", "author"})
    public Me me() {
        return new Me(security.getCallerPrincipal().getName(),
                security.isCallerInRole("admin"));
    }
}

Проверка

Флаг curl -u имя:пароль отправляет заголовок Basic:

curl -i http://localhost:8080/blog/api/me
curl -u anna:changeme http://localhost:8080/blog/api/me
curl -u oleg:changeme http://localhost:8080/blog/api/me
curl -i -u oleg:changeme -X DELETE http://localhost:8080/blog/api/posts/2
curl -i -u anna:changeme -X DELETE http://localhost:8080/blog/api/posts/2
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="blog"

{"admin":true,"name":"anna"}
{"admin":false,"name":"oleg"}
HTTP/1.1 403 Forbidden
HTTP/1.1 204 No Content
КодЗначение
401 UnauthorizedНе представился или пароль неверный
403 ForbiddenПользователь известен, но роли не хватает

Олег вошёл успешно, но удалять записи ему нельзя — отсюда 403. Анне можно.

Частая ошибка

Включить Basic на обычном HTTP в рабочей системе. Заголовок Authorization: Basic YW5uYTpjaGFuZ2VtZQ== — это лишь имя и пароль в кодировке Base64, а не шифрование: любой, кто видит трафик, прочитает пароль. Basic допустим только поверх HTTPS — Open Liberty уже слушает порт 8443.

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

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