QuarkusSeniorCoding
Как реализовать безопасность в Quarkus с помощью @RolesAllowed и JWT?
Quarkus защищает эндпоинты через @RolesAllowed с JWT-токенами, валидируемыми либо локально (quarkus-smallrye-jwt + PEM-ключ), либо через OIDC-провайдер (quarkus-oidc). Роли берутся из клейма groups, JsonWebToken инжектируется через CDI для доступа к claims.
Механизм безопасности в Quarkus
Quarkus реализует безопасность через расширения quarkus-smallrye-jwt (локальная валидация JWT) и quarkus-oidc (OIDC/Keycloak). @RolesAllowed — стандартная Jakarta EE-аннотация, работающая с обоими вариантами.
Зависимости
<!-- Вариант 1: локальная валидация JWT -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<!-- Вариант 2: OIDC (Keycloak, Auth0 и др.) -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
Конфигурация SmallRye JWT
# application.properties
mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem
mp.jwt.verify.issuer=https://auth.example.com
quarkus.smallrye-jwt.blocking-authentication=true
Защита эндпоинтов через @RolesAllowed
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.DenyAll;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/api/orders")
@RequestScoped
public class OrderResource {
@Inject
JsonWebToken jwt; // инжекция распарсенного токена
@GET
@PermitAll
public List<Order> listPublic() {
return orderService.findPublic();
}
@GET
@Path("/my")
@RolesAllowed({"user", "admin"})
public List<Order> myOrders() {
String userId = jwt.getSubject();
return orderService.findByUser(userId);
}
@DELETE
@Path("/{id}")
@RolesAllowed("admin")
public Response delete(@PathParam("id") Long id) {
orderService.delete(id);
return Response.noContent().build();
}
@GET
@Path("/internal")
@DenyAll // всегда 403, даже с токеном
public Response internal() {
return Response.ok().build();
}
}
Создание и подпись JWT (для тестов / сервис-аккаунтов)
// В тестах используйте io.smallrye.jwt.build.Jwt
String token = Jwt.issuer("https://auth.example.com")
.subject("user-123")
.groups(Set.of("user", "admin"))
.expiresIn(Duration.ofHours(1))
.sign(); // подписывает приватным ключом из test-resources
Извлечение Claims
@Inject
@Claim("email")
String userEmail;
@Inject
@Claim(ClaimConstants.GROUPS)
Set<String> roles;
// Или напрямую через JsonWebToken:
String customClaim = jwt.getClaim("org_id");
Политики на уровне конфигурации (без аннотаций)
quarkus.http.auth.permission.admin-only.paths=/api/admin/*
quarkus.http.auth.permission.admin-only.policy=role-policy-admin
quarkus.http.auth.policy.role-policy-admin.roles-allowed=admin
quarkus.http.auth.permission.authenticated.paths=/api/*
quarkus.http.auth.permission.authenticated.policy=authenticated
Тестирование с @TestSecurity
@QuarkusTest
public class OrderResourceTest {
@Test
@TestSecurity(user = "alice", roles = {"admin"})
public void adminCanDelete() {
given()
.when().delete("/api/orders/1")
.then().statusCode(204);
}
@Test
public void anonymousIsUnauthorized() {
given()
.when().get("/api/orders/my")
.then().statusCode(401);
}
}
Подводные камни
- Роли в JWT берутся из клейма
groups(MicroProfile JWT spec) — Keycloak по умолчанию кладёт их вrealm_access.roles; нужна кастомная маппинг-логика илиquarkus.oidc.roles.role-claim-path=realm_access/roles. @RolesAllowedна уровне класса применяется ко всем методам, но метод-аннотация переопределяет её — порядок приоритетов неочевиден.- Без
@RequestScopedили@ApplicationScopedинжекцияJsonWebTokenможет вернуть пустой токен в не-JAX-RS контексте. - В native-сборке PEM-ключ должен быть в
resources, а не загружаться по HTTP-URL — это ломает холодный старт при недоступности JWKS-эндпоинта. - Истечение токена (
exp) проверяется автоматически, но clock skew между сервисами вызывает ложные 401 — настройтеmp.jwt.token.ageили синхронизацию NTP. - Алгоритм подписи по умолчанию RS256; если выдаётся HS256-токен, SmallRye JWT отклонит его — явно укажите
quarkus.smallrye-jwt.token.signature-algorithm=HS256. @PermitAllна методе не отменяет политикуauthenticated, заданную черезapplication.properties— конфигурационные политики имеют приоритет.- Не смешивать
quarkus-smallrye-jwtиquarkus-oidcв одном приложении без явного указанияquarkus.http.auth.proactive=false— возможны конфликты механизмов аутентификации.
Common mistakes
- Путать термин «rolesallowed jwt» с соседним механизмом Quarkus.
- Не называть границу lifecycle, transaction, thread или request для «rolesallowed jwt».
- Игнорировать production-эффекты «rolesallowed jwt»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «rolesallowed jwt» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «rolesallowed jwt».
- Уточнить, какие настройки или API меняют «rolesallowed jwt» в реальном сервисе.