Menu

Quarkus Topic

Part 1: Basic Role-Based Security

Quarkus Security provides built-in authentication and authorization mechanisms.

Dependencies

xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>

Basic Configuration

properties

# Basic file-based authentication (for development/testing)
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true

# Define users and roles
quarkus.security.users.embedded.users.alice=password
quarkus.security.users.embedded.users.bob=password
quarkus.security.users.embedded.users.admin=admin123

quarkus.security.users.embedded.roles.alice=USER,VIEWER
quarkus.security.users.embedded.roles.bob=USER,EDITOR  
quarkus.security.users.embedded.roles.admin=USER,ADMIN

Using Security Annotations

java

@Path("/api/books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {

    @GET
    @Path("/public")
    @PermitAll  // No authentication required
    public String publicEndpoint() {
        return "This is public data";
    }

    @GET
    @Path("/user-only")
    @RolesAllowed("USER")  // Any authenticated user with USER role
    public String userEndpoint() {
        return "Data for authenticated users";
    }

    @POST
    @Path("/create")
    @RolesAllowed("EDITOR")  // Only users with EDITOR role
    public String createBook(Book book) {
        return "Book created by editor";
    }

    @PUT
    @Path("/admin-only")
    @RolesAllowed("ADMIN")  // Only ADMIN role
    public String adminEndpoint() {
        return "Admin-only operation";
    }

    @DELETE
    @Path("/{id}")
    @RolesAllowed({"ADMIN", "EDITOR"})  // Multiple roles allowed
    public String deleteBook(@PathParam("id") Long id) {
        return "Book deleted";
    }
}

Programmatic Security Access

java

@ApplicationScoped
public class SecurityService {
    
    @Inject
    SecurityIdentity securityIdentity;

    public String getCurrentUserInfo() {
        if (securityIdentity.isAnonymous()) {
            return "Anonymous user";
        }

        String username = securityIdentity.getPrincipal().getName();
        Set<String> roles = securityIdentity.getRoles();
        
        return String.format("User: %s, Roles: %s", username, roles);
    }

    public boolean hasRole(String role) {
        return securityIdentity.hasRole(role);
    }

    public void checkPermission(String resource) {
        if (!securityIdentity.hasPermission(new ResourcePermission(resource, "read"))) {
            throw new SecurityException("Access denied to resource: " + resource);
        }
    }
}

Part 2: JWT Authentication (smallrye-jwt)

JSON Web Tokens are perfect for stateless microservices authentication.

Dependencies

xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>

JWT Configuration

properties

# JWT Configuration
quarkus.smallrye-jwt.enabled=true
quarkus.smallrye-jwt.auth-mechanism=MP-JWT
quarkus.smallrye-jwt.public-key-location=publicKey.pem
quarkus.smallrye-jwt.roles-claim=groups

# Token issuer validation
mp.jwt.verify.issuer=https://your-auth-server.com
mp.jwt.verify.publickey.location=publicKey.pem

JWT-Endpoint with Token Injection

java

@Path("/api/jwt")
@Authenticated  // All endpoints require authentication
public class JwtResource {

    @Inject
    JsonWebToken jwt;  // Injected JWT token

    @GET
    @Path("/user-info")
    @RolesAllowed("USER")
    public JsonObject getUserInfo() {
        return Json.createObjectBuilder()
            .add("username", jwt.getName())
            .add("subject", jwt.getSubject())
            .add("issuer", jwt.getIssuer())
            .add("groups", Json.createArrayBuilder(jwt.getGroups()))
            .add("token-id", jwt.getTokenID())
            .add("issued-at", jwt.getIssuedAtTime().toString())
            .add("expiration", jwt.getExpirationTime().toString())
            .build();
    }

    @GET
    @Path("/admin-data")
    @RolesAllowed("ADMIN")
    public String adminData() {
        return "Sensitive admin data accessible only with ADMIN role";
    }

    @GET
    @Path("/custom-claim")
    @RolesAllowed("USER")
    public String getCustomClaim() {
        // Access custom claims from JWT
        String department = jwt.getClaim("department");
        String email = jwt.getClaim("email");
        
        return String.format("Department: %s, Email: %s", department, email);
    }
}

JWT Token Generator (for testing)

java

@ApplicationScoped
public class JwtTokenService {
    
    public String generateUserToken(String username, Set<String> roles) {
        return generateToken(username, roles, 3600); // 1 hour
    }
    
    public String generateAdminToken(String username) {
        return generateToken(username, Set.of("USER", "ADMIN"), 3600);
    }
    
    private String generateToken(String username, Set<String> roles, long duration) {
        try {
            return Jwt.issuer("https://your-quarkus-app.com")
                .upn(username)
                .groups(roles)
                .claim("department", "Engineering")
                .claim("email", username + "@company.com")
                .expiresIn(duration)
                .sign();
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate JWT", e);
        }
    }
}

Part 3: OAuth2/OpenID Connect (quarkus-oidc)

For enterprise applications integrating with identity providers like Google, Auth0, etc.

Dependencies

xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc</artifactId>
</dependency>

OIDC Configuration

properties

# OIDC Configuration for Auth0
quarkus.oidc.auth-server-url=https://your-domain.auth0.com/
quarkus.oidc.client-id=your-client-id
quarkus.oidc.credentials.secret=your-client-secret
quarkus.oidc.application-type=web-app

# Token verification
quarkus.oidc.token.issuer=${quarkus.oidc.auth-server-url}
quarkus.oidc.token.audience=your-api-audience

# Session management
quarkus.oidc.authentication.cookie-path=/
quarkus.oidc.authentication.restore-path-after-redirect=false

OAuth2-Protected Endpoints

java

@Path("/api/oauth")
public class OAuth2Resource {

    @GET
    @Path("/public")
    @PermitAll
    public String publicInfo() {
        return "Public information";
    }

    @GET
    @Path("/profile")
    @RolesAllowed("USER")
    public JsonObject getUserProfile(@Context SecurityContext securityContext) {
        // SecurityContext contains OAuth2 user information
        return Json.createObjectBuilder()
            .add("message", "Protected user profile")
            .add("user", securityContext.getUserPrincipal().getName())
            .build();
    }

    @GET
    @Path("/admin")
    @RolesAllowed("ADMIN")
    public String adminArea() {
        return "Admin dashboard";
    }
}

OAuth2 Client for Resource Server

java

@Path("/api/resource-client")
@ApplicationScoped
public class OAuth2ClientResource {

    @Inject
    @RestClient
    ExternalServiceClient externalServiceClient;

    @GET
    @Path("/external-data")
    @RolesAllowed("USER")
    public String fetchExternalData() {
        // This will automatically include OAuth2 token
        return externalServiceClient.getProtectedData();
    }
}

// REST Client interface with OAuth2
@RegisterRestClient
@RegisterOAuth2RequestFilter
public interface ExternalServiceClient {
    
    @GET
    @Path("/api/external/protected")
    String getProtectedData();
}

Part 4: Integrating with Keycloak

Keycloak is a popular open-source identity and access management solution.

Dependencies

xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-keycloak-authorization</artifactId>
</dependency>

Keycloak Configuration

properties

# Keycloak OIDC Configuration
quarkus.oidc.auth-server-url=http://localhost:8080/auth/realms/quarkus-realm
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=your-client-secret
quarkus.oidc.application-type=service

# Keycloak Authorization
quarkus.keycloak.policy-enforcer.enable=true
quarkus.keycloak.policy-enforcer.paths.1.name=Admin Resource
quarkus.keycloak.policy-enforcer.paths.1.path=/api/admin/.*
quarkus.keycloak.policy-enforcer.paths.1.claims.role=admin

# CORS for web apps
quarkus.http.cors=true
quarkus.http.cors.origins=http://localhost:3000

Keycloak-Protected Resources

java

@Path("/api/keycloak")
public class KeycloakResource {

    @GET
    @Path("/public")
    @PermitAll
    public String publicEndpoint() {
        return "Public data for all users";
    }

    @GET
    @Path("/user-profile")
    @RolesAllowed("user")  // Keycloak realm role
    public JsonObject getUserProfile(@Inject SecurityIdentity identity) {
        return Json.createObjectBuilder()
            .add("username", identity.getPrincipal().getName())
            .add("roles", String.join(", ", identity.getRoles()))
            .build();
    }

    @GET
    @Path("/admin-reports")
    @RolesAllowed("admin")  // Keycloak admin role
    public String adminReports() {
        return "Confidential admin reports";
    }

    @GET
    @Path("/department-data")
    @RolesAllowed("department-manager")  // Custom role
    public String departmentData() {
        return "Department-specific data";
    }
}

Fine-Grained Authorization with Keycloak

java

@Path("/api/secure")
public class FineGrainedSecurityResource {

    @Inject
    SecurityIdentity securityIdentity;

    // Resource-based access control
    @GET
    @Path("/projects/{projectId}")
    @RolesAllowed("user")
    public String getProject(@PathParam("projectId") String projectId) {
        // Check if user has access to this specific project
        if (!hasProjectAccess(projectId)) {
            throw new AccessDeniedException("No access to project: " + projectId);
        }
        return "Project data: " + projectId;
    }

    private boolean hasProjectAccess(String projectId) {
        // Implement resource-specific access logic
        // Could check against user attributes, group membership, etc.
        return securityIdentity.getAttributes().containsKey("project_" + projectId);
    }
}

Part 5: Advanced Security Features

Custom Security Filters

java

@Provider
@Priority(Priorities.AUTHENTICATION)
public class ApiKeyAuthenticationFilter implements ContainerRequestFilter {
    
    private static final String API_KEY_HEADER = "X-API-Key";
    
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String apiKey = requestContext.getHeaderString(API_KEY_HEADER);
        
        if (apiKey != null && isValidApiKey(apiKey)) {
            // Create a custom security identity for API key authentication
            QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
            builder.setPrincipal(new SimplePrincipal("api-client"));
            builder.addRole("API_CLIENT");
            builder.addCredential(new ApiKeyCredential(apiKey));
            
            SecurityIdentity securityIdentity = builder.build();
            
            // Set the security context
            requestContext.setSecurityContext(new SecurityContext() {
                @Override
                public Principal getUserPrincipal() {
                    return securityIdentity.getPrincipal();
                }
                
                @Override
                public boolean isUserInRole(String role) {
                    return securityIdentity.hasRole(role);
                }
                
                @Override
                public boolean isSecure() {
                    return requestContext.getSecurityContext().isSecure();
                }
                
                @Override
                public String getAuthenticationScheme() {
                    return "API_KEY";
                }
            });
        }
    }
    
    private boolean isValidApiKey(String apiKey) {
        // Validate API key against database or configuration
        return "valid-api-key-123".equals(apiKey);
    }
}

Security Events and Auditing

java

@ApplicationScoped
public class SecurityEventListener {
    
    private static final Logger LOG = Logger.getLogger(SecurityEventListener.class);
    
    public void onAuthenticationSuccess(@Observes AuthenticationSuccessEvent event) {
        LOG.infof("User %s authenticated successfully from IP: %s", 
            event.getSecurityIdentity().getPrincipal().getName(),
            getClientIP());
    }
    
    public void onAuthenticationFailure(@Observes AuthenticationFailedEvent event) {
        LOG.warnf("Authentication failed for user: %s, reason: %s",
            event.getAuthenticationRequest().getName(),
            event.getFailure().getMessage());
    }
    
    public void onAuthorizationSuccess(@Observes AuthorizationSuccessEvent event) {
        LOG.infof("Authorization granted for %s to access %s",
            event.getSecurityIdentity().getPrincipal().getName(),
            event.getResource().getName());
    }
    
    public void onAuthorizationFailure(@Observes AuthorizationFailureEvent event) {
        LOG.warnf("Authorization denied for %s to access %s",
            event.getSecurityIdentity().getPrincipal().getName(),
            event.getResource().getName());
    }
    
    private String getClientIP() {
        // Extract client IP from request
        return "unknown"; // Implement IP extraction
    }
}

Security Configuration Class

java

@ApplicationScoped
public class SecurityConfig {
    
    @ConfigProperty(name = "app.security.jwt.secret")
    String jwtSecret;
    
    @ConfigProperty(name = "app.security.cors.allowed-origins")
    String allowedOrigins;
    
    // Programmatic security configuration
    public void configureSecurity() {
        // Additional security setup if needed
    }
    
    @Produces
    @ApplicationScoped
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Testing Security

Security Test Dependencies

xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-security</artifactId>
    <scope>test</scope>
</dependency>

Security Test Examples

java

@QuarkusTest
public class SecurityTest {

    @Test
    @TestSecurity(user = "alice", roles = "USER")
    public void testUserAccess() {
        given()
            .when().get("/api/books/user-only")
            .then()
            .statusCode(200);
    }

    @Test
    @TestSecurity(user = "bob", roles = "EDITOR") 
    public void testEditorAccess() {
        given()
            .when().post("/api/books/create")
            .then()
            .statusCode(200);
    }

    @Test
    @TestSecurity(user = "eve", roles = "VIEWER")
    public void testUnauthorizedAccess() {
        given()
            .when().post("/api/books/create")
            .then()
            .statusCode(403); // Forbidden
    }

    @Test
    public void testPublicEndpoint() {
        given()
            .when().get("/api/books/public")
            .then()
            .statusCode(200);
    }
}

Key Takeaways

  1. Layered Security: Use different authentication methods based on requirements

  2. JWT for Microservices: Perfect for stateless, scalable architectures

  3. OIDC for Enterprise: Integrates with existing identity providers

  4. Keycloak for Full IAM: Complete identity and access management solution

  5. Programmatic Control: Combine annotations with programmatic security for fine-grained control

 

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?