Quarkus Security

作者 affaan-mef648e01899b无许可证275K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库3天前更新

Buenas prácticas de seguridad en Quarkus para autenticación, autorización, JWT/OIDC, RBAC, validación de entrada, CSRF, gestión de secretos y seguridad de dependencias.

AI 生成的概览

Quarkus 安全最佳实践,涵盖认证、授权、输入校验、密钥管理与依赖扫描。

功能
提供保护 Quarkus 应用的指导与代码示例:JWT/OIDC 与自定义认证、基于角色和编程式授权、Bean Validation、参数化查询、BCrypt 密码哈希、CORS、密钥管理、限流、安全响应头、审计日志以及依赖 CVE 扫描。产出的是参考代码片段与配置属性,而非可直接运行的成品。
适用场景
适用于在 Quarkus 服务中添加或审查认证与授权、校验用户输入、配置 CORS 或安全响应头、管理密钥、添加限流,或扫描依赖中的 CVE 时。
运行要求
不附带脚本或凭据,仅为说明性内容。应用示例需具备 Quarkus 项目及相关扩展(如 SmallRye JWT、OIDC、Hibernate/Panache、Bean Validation);依赖扫描需 Maven 或 Gradle 及 OWASP dependency-check 插件。

Revisión de Seguridad Quarkus

Buenas prácticas para asegurar aplicaciones Quarkus con autenticación, autorización y validación de entrada.

Cuándo Activar

  • Agregar autenticación (JWT, OIDC, Basic Auth)
  • Implementar autorización con @RolesAllowed o SecurityIdentity
  • Validar entrada de usuario (Bean Validation, validadores personalizados)
  • Configurar CORS o cabeceras de seguridad
  • Gestionar secretos (Vault, variables de entorno, fuentes de configuración)
  • Agregar limitación de velocidad o protección contra fuerza bruta
  • Escanear dependencias por CVEs
  • Trabajar con MicroProfile JWT o SmallRye JWT

Autenticación

Autenticación JWT

java
// Recurso protegido con JWT@Path("/api/protected")@Authenticatedpublic class ProtectedResource {
  @Inject  JsonWebToken jwt;
  @Inject  SecurityIdentity securityIdentity;
  @GET  public Response getData() {    String username = jwt.getName();    Set<String> roles = jwt.getGroups();    return Response.ok(Map.of(        "username", username,        "roles", roles,        "principal", securityIdentity.getPrincipal().getName()    )).build();  }}

Configuración (application.properties):

properties
mp.jwt.verify.publickey.location=publicKey.pemmp.jwt.verify.issuer=https://auth.example.com
# OIDCquarkus.oidc.auth-server-url=https://auth.example.com/realms/myrealmquarkus.oidc.client-id=backend-servicequarkus.oidc.credentials.secret=${OIDC_SECRET}

Filtro de Autenticación Personalizado

java
@Provider@Priority(Priorities.AUTHENTICATION)public class CustomAuthFilter implements ContainerRequestFilter {
  @Inject  SecurityIdentity identity;
  @Override  public void filter(ContainerRequestContext requestContext) {    String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
    if (authHeader == null || !authHeader.startsWith("Bearer ")) {      requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());      return;    }
    String token = authHeader.substring(7);    if (!validateToken(token)) {      requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());    }  }
  private boolean validateToken(String token) {    return true;  }}

Autorización

Control de Acceso Basado en Roles

java
@Path("/api/admin")@RolesAllowed("ADMIN")public class AdminResource {
  @GET  @Path("/users")  public List<UserDto> listUsers() {    return userService.findAll();  }
  @DELETE  @Path("/users/{id}")  @RolesAllowed({"ADMIN", "SUPER_ADMIN"})  public Response deleteUser(@PathParam("id") Long id) {    userService.delete(id);    return Response.noContent().build();  }}
@Path("/api/users")public class UserResource {
  @Inject  SecurityIdentity securityIdentity;
  @GET  @Path("/{id}")  @RolesAllowed("USER")  public Response getUser(@PathParam("id") Long id) {    if (!securityIdentity.hasRole("ADMIN") &&        !isOwner(id, securityIdentity.getPrincipal().getName())) {      return Response.status(Response.Status.FORBIDDEN).build();    }    return Response.ok(userService.findById(id)).build();  }}

Seguridad Programática

java
@ApplicationScopedpublic class SecurityService {
  @Inject  SecurityIdentity securityIdentity;
  public boolean canAccessResource(Long resourceId) {    if (securityIdentity.isAnonymous()) {      return false;    }
    if (securityIdentity.hasRole("ADMIN")) {      return true;    }
    String userId = securityIdentity.getPrincipal().getName();    return resourceRepository.isOwner(resourceId, userId);  }}

Validación de Entrada

Bean Validation

java
// MAL: Sin validación@POSTpublic Response createUser(UserDto dto) {  return Response.ok(userService.create(dto)).build();}
// BIEN: DTO validadopublic record CreateUserDto(    @NotBlank @Size(max = 100) String name,    @NotBlank @Email String email,    @NotNull @Min(18) @Max(150) Integer age,    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$") String phone) {}
@POST@Path("/users")public Response createUser(@Valid CreateUserDto dto) {  User user = userService.create(dto);  return Response.status(Response.Status.CREATED).entity(user).build();}

Validadores Personalizados

java
@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = UsernameValidator.class)public @interface ValidUsername {  String message() default "Formato de nombre de usuario inválido";  Class<?>[] groups() default {};  Class<? extends Payload>[] payload() default {};}
public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {  @Override  public boolean isValid(String value, ConstraintValidatorContext context) {    if (value == null) return false;    return value.matches("^[a-zA-Z0-9_-]{3,20}$");  }}

Prevención de Inyección SQL

Panache Active Record (Seguro por Defecto)

java
// BIEN: Consultas parametrizadas con PanacheList<User> users = User.list("email = ?1 and active = ?2", email, true);
Optional<User> user = User.find("username", username).firstResultOptional();
// BIEN: Parámetros nombradosList<User> users = User.list("email = :email and age > :minAge",    Parameters.with("email", email).and("minAge", 18));

Consultas Nativas (Usar Parámetros)

java
// MAL: Concatenación de cadenas@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// BIEN: Consulta nativa parametrizada@Entitypublic class User extends PanacheEntity {  public static List<User> findByEmailNative(String email) {    return getEntityManager()        .createNativeQuery("SELECT * FROM users WHERE email = :email", User.class)        .setParameter("email", email)        .getResultList();  }}

Hash de Contraseñas

java
@ApplicationScopedpublic class PasswordService {
  public String hash(String plainPassword) {    return BcryptUtil.bcryptHash(plainPassword);  }
  public boolean verify(String plainPassword, String hashedPassword) {    return BcryptUtil.matches(plainPassword, hashedPassword);  }}

Configuración de CORS

properties
# application.propertiesquarkus.http.cors=truequarkus.http.cors.origins=https://app.example.com,https://admin.example.comquarkus.http.cors.methods=GET,POST,PUT,DELETEquarkus.http.cors.headers=accept,authorization,content-type,x-requested-withquarkus.http.cors.access-control-allow-credentials=true

Gestión de Secretos

properties
# application.properties - SIN SECRETOS AQUÍ
# Usar variables de entornoquarkus.datasource.username=${DB_USER}quarkus.datasource.password=${DB_PASSWORD}quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
# O usar Vaultquarkus.vault.url=https://vault.example.comquarkus.vault.authentication.kubernetes.role=my-role

Limitación de Velocidad

Nota de Seguridad: Nunca usar X-Forwarded-For directamente — los clientes pueden falsificarlo. Usar la dirección remota real de la solicitud servlet, o una identidad autenticada (clave API, subject del JWT) cuando esté disponible.

java
@ApplicationScopedpublic class RateLimitFilter implements ContainerRequestFilter {  private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
  @Inject  HttpServletRequest servletRequest;
  @Override  public void filter(ContainerRequestContext requestContext) {    String clientId = getClientIdentifier();    RateLimiter limiter = limiters.computeIfAbsent(clientId,        k -> RateLimiter.create(100.0)); // 100 solicitudes por segundo
    if (!limiter.tryAcquire()) {      requestContext.abortWith(          Response.status(429)              .entity(Map.of("error", "Demasiadas solicitudes"))              .build()      );    }  }
  private String getClientIdentifier() {    // Usar la dirección remota provista por el contenedor (no X-Forwarded-For).    // Si está detrás de un proxy confiable, configurar    // quarkus.http.proxy.proxy-address-forwarding=true    // para que getRemoteAddr() retorne la IP real del cliente.    return servletRequest.getRemoteAddr();  }}

Cabeceras de Seguridad

java
@Providerpublic class SecurityHeadersFilter implements ContainerResponseFilter {
  @Override  public void filter(ContainerRequestContext request, ContainerResponseContext response) {    MultivaluedMap<String, Object> headers = response.getHeaders();
    headers.putSingle("X-Frame-Options", "DENY");    headers.putSingle("X-Content-Type-Options", "nosniff");    headers.putSingle("X-XSS-Protection", "1; mode=block");    headers.putSingle("Strict-Transport-Security", "max-age=31536000; includeSubDomains");    // CSP: evitar 'unsafe-inline' para script-src; usar nonces o hashes    headers.putSingle("Content-Security-Policy",        "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");  }}

Logging de Auditoría

java
@ApplicationScopedpublic class AuditService {  private static final Logger LOG = Logger.getLogger(AuditService.class);
  @Inject  SecurityIdentity securityIdentity;
  public void logAccess(String resource, String action) {    String user = securityIdentity.isAnonymous()        ? "anonymous"        : securityIdentity.getPrincipal().getName();
    LOG.infof("AUDIT: user=%s action=%s resource=%s timestamp=%s",        user, action, resource, Instant.now());  }}

Escaneo de Seguridad de Dependencias

bash
# Mavenmvn org.owasp:dependency-check-maven:check
# Gradle./gradlew dependencyCheckAnalyze
# Verificar extensiones de Quarkusquarkus extension list --installable

Buenas Prácticas

  • Siempre usar HTTPS en producción
  • Habilitar JWT u OIDC para autenticación sin estado
  • Usar @RolesAllowed para autorización declarativa
  • Validar toda entrada con Bean Validation
  • Hashear contraseñas con BCrypt (nunca texto plano)
  • Almacenar secretos en Vault o variables de entorno
  • Usar consultas parametrizadas para prevenir inyección SQL
  • Agregar cabeceras de seguridad a todas las respuestas
  • Implementar limitación de velocidad para endpoints públicos
  • Auditar operaciones sensibles
  • Mantener dependencias actualizadas y escanear por CVEs
  • Usar SecurityIdentity para verificaciones programáticas
  • Establecer políticas CORS apropiadas
  • Probar rutas de autenticación y autorización

来源与署名

来源:affaan-m/ecc位于docs/es/skills/quarkus-security提交ef648e0

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架