google-site-verification: google61fe8ba583a51912.html
Quarkus Security in Java: Complete Implementation Guide

Introduction to Quarkus Security

Quarkus provides a comprehensive security framework that integrates with standards like JWT, OAuth2, OpenID Connect, and MicroProfile JWT. It offers both traditional and reactive security approaches with minimal configuration.

Key Quarkus Security Features

  • Built-in Authentication & Authorization
  • JWT & OAuth2/OpenID Connect Support
  • Role-Based Access Control (RBAC)
  • MicroProfile JWT Integration
  • Reactive Security
  • Elytron Security
  • Custom Security Extensions

Dependencies Setup

Maven Configuration

<properties> <quarkus.version>3.2.0.Final</quarkus.version> </properties> <dependencies> <!-- Quarkus Security --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-security</artifactId> </dependency> <!-- JWT Security --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-oidc</artifactId> </dependency> <!-- MicroProfile JWT --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-jwt</artifactId> </dependency> <!-- Elytron Security --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-elytron-security</artifactId> </dependency> <!-- Reactive Routes Security --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-vertx-http</artifactId> </dependency> <!-- Database Security --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-security-jpa</artifactId> </dependency> <!-- Test Security --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-test-security</artifactId> <scope>test</scope> </dependency> <!-- RESTEasy JSON-B --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-jsonb</artifactId> </dependency> </dependencies>

Basic Security Configuration

Application Properties

# Application Configuration quarkus.http.port=8080 # Basic Security quarkus.security.enabled=true # JWT Configuration quarkus.oidc.auth-server-url=https://your-oidc-provider.com quarkus.oidc.client-id=your-client-id quarkus.oidc.credentials.secret=your-client-secret # MP JWT Configuration mp.jwt.verify.publickey.location=publicKey.pem mp.jwt.verify.issuer=https://your-issuer.com # Security Roles quarkus.http.auth.permission.authenticated.paths=/* quarkus.http.auth.permission.authenticated.policy=authenticated # CORS Configuration quarkus.http.cors=true quarkus.http.cors.origins=*

JWT-Based Security Implementation

JWT Token Generator

package com.quarkus.security.jwt; import io.smallrye.jwt.build.Jwt; import org.eclipse.microprofile.jwt.Claims; import java.time.Instant; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class JWTGenerator { public static String generateUserToken(String username, Set<String> roles) { return Jwt.issuer("https://quarkus-app.com") .upn(username) .subject(username) .groups(roles) .claim(Claims.birthdate.name(), "2000-01-01") .expiresAt(Instant.now().plusSeconds(3600)) .sign(); } public static String generateAdminToken() { Set<String> roles = new HashSet<>(Arrays.asList("admin", "user")); return generateUserToken("[email protected]", roles); } public static String generateUserToken() { Set<String> roles = new HashSet<>(Arrays.asList("user")); return generateUserToken("[email protected]", roles); } }

JWT Protected Resource

package com.quarkus.security.resource; import org.eclipse.microprofile.jwt.Claim; import org.eclipse.microprofile.jwt.Claims; import org.eclipse.microprofile.jwt.JsonWebToken; import javax.annotation.security.RolesAllowed; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.Set; @Path("/secured") @RequestScoped @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class SecuredResource { @Inject JsonWebToken jwt; @Inject @Claim(standard = Claims.upn) String username; @Inject @Claim(standard = Claims.groups) Set<String> groups; @GET @Path("/public") public Response publicEndpoint() { return Response.ok( new Message("Public endpoint - no authentication required") ).build(); } @GET @Path("/user") @RolesAllowed("user") public Response userEndpoint() { return Response.ok( new Message("User endpoint - user role required. Current user: " + username) ).build(); } @GET @Path("/admin") @RolesAllowed("admin") public Response adminEndpoint() { return Response.ok( new Message("Admin endpoint - admin role required. Current user: " + username) ).build(); } @GET @Path("/profile") @RolesAllowed({"user", "admin"}) public Response getUserProfile() { UserProfile profile = new UserProfile( username, jwt.getSubject(), groups, jwt.getExpirationTime(), jwt.getIssuer() ); return Response.ok(profile).build(); } @POST @Path("/update") @RolesAllowed("user") public Response updateUserData(UpdateRequest request) { // Business logic here return Response.ok(new Message("Data updated for user: " + username)).build(); } // DTO classes public static class Message { public String message; public Message(String message) { this.message = message; } } public static class UserProfile { public String username; public String subject; public Set<String> roles; public long expiration; public String issuer; public UserProfile(String username, String subject, Set<String> roles, long expiration, String issuer) { this.username = username; this.subject = subject; this.roles = roles; this.expiration = expiration; this.issuer = issuer; } } public static class UpdateRequest { public String data; } }

Custom Authentication Mechanism

Custom Identity Provider

package com.quarkus.security.identity; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.AuthenticationRequestContext; import io.quarkus.security.identity.IdentityProvider; import io.quarkus.security.runtime.QuarkusSecurityIdentity; import io.smallrye.mutiny.Uni; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import java.util.Set; @ApplicationScoped public class CustomIdentityProvider implements IdentityProvider<CustomAuthenticationRequest> { @Inject UserService userService; @Override public Class<CustomAuthenticationRequest> getRequestType() { return CustomAuthenticationRequest.class; } @Override public Uni<SecurityIdentity> authenticate(CustomAuthenticationRequest request, AuthenticationRequestContext context) { return Uni.createFrom().item(() -> { // Validate API key or custom token User user = userService.validateToken(request.getToken()); if (user != null) { return QuarkusSecurityIdentity.builder() .setPrincipal(user::getUsername) .addRoles(user.getRoles()) .addAttribute("user-id", user.getId()) .addCredential(request.getToken()) .build(); } return null; // Authentication failed }); } } // Custom Authentication Request class CustomAuthenticationRequest implements io.quarkus.security.identity.request.AuthenticationRequest { private final String token; public CustomAuthenticationRequest(String token) { this.token = token; } public String getToken() { return token; } } // User Service @ApplicationScoped class UserService { public User validateToken(String token) { // Implement token validation logic if ("valid-api-key".equals(token)) { return new User("1", "api-user", Set.of("user", "api-client")); } return null; } } class User { private final String id; private final String username; private final Set<String> roles; public User(String id, String username, Set<String> roles) { this.id = id; this.username = username; this.roles = roles; } public String getId() { return id; } public String getUsername() { return username; } public Set<String> getRoles() { return roles; } }

Database-Backed Security

JPA Entity for Users

package com.quarkus.security.entity; import javax.persistence.*; import java.util.Set; @Entity @Table(name = "users") @NamedQuery( name = "User.findByUsername", query = "SELECT u FROM User u WHERE u.username = :username" ) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String username; @Column(nullable = false) private String password; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") private Set<String> roles; @Column(nullable = false) private boolean active = true; // Constructors public User() {} public User(String username, String password, Set<String> roles) { this.username = username; this.password = password; this.roles = roles; } // Getters and Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Set<String> getRoles() { return roles; } public void setRoles(Set<String> roles) { this.roles = roles; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } }

JPA Identity Provider

package com.quarkus.security.jpa; import io.quarkus.elytron.security.common.BcryptUtil; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.hibernate.orm.panache.PanacheRepository; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; import io.quarkus.security.runtime.QuarkusSecurityIdentity; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.transaction.Transactional; import java.util.Optional; @ApplicationScoped public class UserRepository implements PanacheRepository<User> { @Inject EntityManager entityManager; public Optional<User> findByUsername(String username) { return find("username", username).firstResultOptional(); } @Transactional public User createUser(String username, String password, Set<String> roles) { User user = new User(); user.setUsername(username); user.setPassword(BcryptUtil.bcryptHash(password)); user.setRoles(roles); user.setActive(true); persist(user); return user; } public SecurityIdentity validateUser(String username, String password) { Optional<User> userOpt = findByUsername(username); if (userOpt.isPresent()) { User user = userOpt.get(); if (user.isActive() && BcryptUtil.matches(password, user.getPassword())) { return QuarkusSecurityIdentity.builder() .setPrincipal(() -> user.getUsername()) .addRoles(user.getRoles()) .addAttribute("user-id", user.getId()) .build(); } } return null; } }

Reactive Security Implementation

Reactive JWT Authentication

package com.quarkus.security.reactive; import io.quarkus.security.identity.SecurityIdentity; import io.smallrye.mutiny.Uni; import org.eclipse.microprofile.jwt.JsonWebToken; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/reactive") public class ReactiveSecurityResource { @Inject SecurityIdentity securityIdentity; @Inject JsonWebToken jwt; @GET @Path("/user-info") @Produces(MediaType.APPLICATION_JSON) public Uni<UserInfo> getUserInfo() { return Uni.createFrom().item(() -> { String username = securityIdentity.getPrincipal().getName(); Set<String> roles = securityIdentity.getRoles(); return new UserInfo(username, roles, jwt.getExpirationTime()); }); } @GET @Path("/admin-task") public Uni<String> adminTask() { return Uni.createFrom().item(() -> { if (!securityIdentity.hasRole("admin")) { throw new javax.ws.rs.ForbiddenException("Admin role required"); } return "Admin task executed successfully"; }); } public static class UserInfo { public String username; public Set<String> roles; public long tokenExpiry; public UserInfo(String username, Set<String> roles, long tokenExpiry) { this.username = username; this.roles = roles; this.tokenExpiry = tokenExpiry; } } }

Reactive Route Security

package com.quarkus.security.reactive; import io.quarkus.vertx.web.Route; import io.quarkus.vertx.web.RouteBase; import io.quarkus.vertx.web.RouteFilter; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped @RouteBase(path = "/api/v1") public class ReactiveRoutes { @Route(path = "/public", methods = HttpMethod.GET) public void publicRoute(RoutingContext rc) { rc.response() .putHeader("Content-Type", "application/json") .end("{\"message\": \"Public route\"}"); } @Route(path = "/secure", methods = HttpMethod.GET) public void secureRoute(RoutingContext rc) { // This will be secured by Quarkus security rc.response() .putHeader("Content-Type", "application/json") .end("{\"message\": \"Secure route\"}"); } @RouteFilter public void addSecurityHeaders(RoutingContext rc) { rc.response() .putHeader("X-Security-Policy", "strict-origin-when-cross-origin") .putHeader("X-Content-Type-Options", "nosniff"); rc.next(); } }

OAuth2/OpenID Connect Integration

OIDC Configuration

# OIDC Configuration quarkus.oidc.auth-server-url=https://keycloak.example.com/auth/realms/quarkus quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=your-secret quarkus.oidc.application-type=service quarkus.oidc.authentication.redirect-path=/oidc-success quarkus.oidc.authentication.restore-path=/oidc-restore quarkus.oidc.authentication.scopes=openid,profile,email # Role Mapping quarkus.oidc.roles.role-claim-path=realm_access.roles quarkus.http.auth.permission.oidc.paths=/oidc/* quarkus.http.auth.permission.oidc.policy=authenticated

OIDC Protected Resource

package com.quarkus.security.oidc; import org.eclipse.microprofile.jwt.Claim; import org.eclipse.microprofile.jwt.Claims; import javax.annotation.security.RolesAllowed; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.Set; @Path("/oidc") @RequestScoped @Produces(MediaType.APPLICATION_JSON) public class OIDCResource { @Inject @Claim(standard = Claims.email) String email; @Inject @Claim(standard = Claims.preferred_username) String username; @Inject @Claim(standard = Claims.groups) Set<String> groups; @GET @Path("/profile") @RolesAllowed("user") public Response getOIDCProfile() { OIDCProfile profile = new OIDCProfile( username, email, groups ); return Response.ok(profile).build(); } @GET @Path("/admin") @RolesAllowed("admin") public Response adminEndpoint() { return Response.ok(new Message("OIDC Admin access granted for: " + email)).build(); } public static class OIDCProfile { public String username; public String email; public Set<String> roles; public OIDCProfile(String username, String email, Set<String> roles) { this.username = username; this.email = email; this.roles = roles; } } public static class Message { public String message; public Message(String message) { this.message = message; } } }

Security Event Handling

Security Event Listeners

package com.quarkus.security.events; import io.quarkus.security.spi.runtime.SecurityEvent; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import java.time.LocalDateTime; @ApplicationScoped public class SecurityEventListener { public void onAuthenticationSuccess(@Observes SecurityEvent.AuthenticationSuccess event) { System.out.printf("[SECURITY] User %s authenticated successfully at %s%n", event.getSecurityIdentity().getPrincipal().getName(), LocalDateTime.now()); // Log to audit system, send notifications, etc. } public void onAuthenticationFailure(@Observes SecurityEvent.AuthenticationFailed event) { System.out.printf("[SECURITY] Authentication failed for user %s at %s. Reason: %s%n", event.getAuthenticationRequest().getClass().getSimpleName(), LocalDateTime.now(), event.getThrowable().getMessage()); } public void onAuthorizationSuccess(@Observes SecurityEvent.AuthorizationSuccess event) { System.out.printf("[SECURITY] User %s authorized for resource at %s%n", event.getSecurityIdentity().getPrincipal().getName(), LocalDateTime.now()); } public void onAuthorizationFailure(@Observes SecurityEvent.AuthorizationFailure event) { System.out.printf("[SECURITY] Authorization failed for user %s at %s%n", event.getSecurityIdentity().getPrincipal().getName(), LocalDateTime.now()); } }

Custom Security Constraints

Method-Level Security

package com.quarkus.security.constraints; import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @Path("/constraints") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class SecurityConstraintsResource { @GET @Path("/public") @PermitAll public Response publicMethod() { return Response.ok("Public access allowed").build(); } @GET @Path("/user-only") @RolesAllowed("user") public Response userOnly() { return Response.ok("User access allowed").build(); } @GET @Path("/admin-only") @RolesAllowed("admin") public Response adminOnly() { return Response.ok("Admin access allowed").build(); } @GET @Path("/denied") @DenyAll public Response denied() { return Response.ok("This should never be reached").build(); } @GET @Path("/multi-roles") @RolesAllowed({"admin", "supervisor"}) public Response multiRoles() { return Response.ok("Admin or supervisor access allowed").build(); } }

Testing Security

Security Test Configuration

package com.quarkus.security.test; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; import io.quarkus.test.security.jwt.Claim; import io.quarkus.test.security.jwt.JwtSecurity; import io.restassured.RestAssured; import org.junit.jupiter.api.Test; import static org.hamcrest.Matchers.*; @QuarkusTest public class SecurityTest { @Test @TestSecurity(user = "testuser", roles = "user") public void testUserAccess() { RestAssured.given() .when().get("/secured/user") .then() .statusCode(200) .body("message", containsString("testuser")); } @Test @TestSecurity(user = "testadmin", roles = "admin") public void testAdminAccess() { RestAssured.given() .when().get("/secured/admin") .then() .statusCode(200) .body("message", containsString("testadmin")); } @Test @TestSecurity(user = "testuser", roles = "user") public void testUserAccessDeniedToAdminEndpoint() { RestAssured.given() .when().get("/secured/admin") .then() .statusCode(403); } @Test @JwtSecurity(claims = { @Claim(key = "upn", value = "jwtuser"), @Claim(key = "groups", value = "user") }) public void testJwtSecurity() { RestAssured.given() .when().get("/secured/user") .then() .statusCode(200) .body("message", containsString("jwtuser")); } @Test public void testUnauthenticatedAccess() { RestAssured.given() .when().get("/secured/user") .then() .statusCode(401); } }

Advanced Security Configuration

Custom Security Policy

package com.quarkus.security.policy; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy; import javax.enterprise.context.ApplicationScoped; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletableFuture; @ApplicationScoped public class IPWhitelistPolicy implements HttpSecurityPolicy { private final Set<String> allowedIPs = Set.of( "192.168.1.0/24", "10.0.0.1", "127.0.0.1" ); @Override public CompletionStage<CheckResult> checkPermission( io.vertx.ext.web.RoutingContext routingContext, SecurityIdentity identity) { String clientIP = routingContext.request().remoteAddress().host(); if (isIPAllowed(clientIP)) { return CompletableFuture.completedFuture(CheckResult.PERMIT); } else { return CompletableFuture.completedFuture(CheckResult.DENY); } } private boolean isIPAllowed(String ip) { // Implement IP whitelist logic return allowedIPs.stream().anyMatch(allowed -> matchesIP(ip, allowed)); } private boolean matchesIP(String ip, String allowed) { // Simple IP matching logic return ip.equals(allowed) || allowed.endsWith("/24") && ip.startsWith(allowed.substring(0, allowed.length() - 4)); } }

Rate Limiting Security

package com.quarkus.security.ratelimit; import io.quarkus.redis.datasource.RedisDataSource; import io.quarkus.redis.datasource.value.ValueCommands; import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; import java.io.IOException; @Provider @ApplicationScoped public class RateLimitFilter implements ContainerRequestFilter { private final ValueCommands<String, Integer> redisCommands; private static final int MAX_REQUESTS = 100; private static final int TIME_WINDOW = 3600; // 1 hour public RateLimitFilter(RedisDataSource redis) { this.redisCommands = redis.value(Integer.class); } @Override public void filter(ContainerRequestContext requestContext) throws IOException { String clientIP = requestContext.getHeaderString("X-Forwarded-For"); if (clientIP == null) { clientIP = "unknown"; } String key = "rate_limit:" + clientIP; Integer currentCount = redisCommands.get(key); if (currentCount == null) { redisCommands.setex(key, TIME_WINDOW, 1); } else if (currentCount >= MAX_REQUESTS) { requestContext.abortWith( Response.status(429) .entity("Rate limit exceeded") .build() ); } else { redisCommands.incr(key); } } }

Production Security Configuration

Production Security Properties

# Production Security Settings quarkus.security.users.embedded.enabled=false # HTTPS Configuration quarkus.http.ssl-port=8443 quarkus.http.ssl.certificate.files=server.crt quarkus.http.ssl.certificate.key-files=server.key # Security Headers quarkus.http.cors=true quarkus.http.cors.origins=https://yourdomain.com quarkus.http.header.content-security-policy=default-src 'self' quarkus.http.header.strict-transport-security=max-age=31536000 # JWT Production Settings mp.jwt.verify.publickey.location=https://your-issuer.com/.well-known/jwks.json mp.jwt.verify.issuer=https://your-issuer.com # OIDC Production Settings quarkus.oidc.tls.verification=required quarkus.oidc.token.issuer=https://your-oidc-issuer.com # Logging Security Events quarkus.log.category."io.quarkus.security".level=INFO

Security Monitoring and Health Checks

Security Health Check

package com.quarkus.security.health; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.Readiness; import javax.enterprise.context.ApplicationScoped; @Readiness @ApplicationScoped public class SecurityHealthCheck implements HealthCheck { @Override public HealthCheckResponse call() { // Check security components status boolean oidcAvailable = checkOIDCConnection(); boolean databaseAvailable = checkDatabaseConnection(); if (oidcAvailable && databaseAvailable) { return HealthCheckResponse.up("security-components"); } else { return HealthCheckResponse.down("security-components"); } } private boolean checkOIDCConnection() { // Implement OIDC provider connectivity check return true; } private boolean checkDatabaseConnection() { // Implement database connectivity check return true; } }

Conclusion

This comprehensive Quarkus security implementation provides:

  1. JWT-based authentication with MicroProfile JWT
  2. OAuth2/OpenID Connect integration
  3. Database-backed security with JPA
  4. Reactive security for non-blocking operations
  5. Custom security providers and policies
  6. Comprehensive testing strategies
  7. Production-ready configurations

Quarkus security offers a robust, standards-compliant security framework that integrates seamlessly with modern cloud-native applications. The reactive nature of Quarkus ensures that security operations don't block the event loop, maintaining high performance while providing enterprise-grade security features.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper