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
-
Layered Security: Use different authentication methods based on requirements
-
JWT for Microservices: Perfect for stateless, scalable architectures
-
OIDC for Enterprise: Integrates with existing identity providers
-
Keycloak for Full IAM: Complete identity and access management solution
-
Programmatic Control: Combine annotations with programmatic security for fine-grained control
Comments (0)
No comments yet. Be the first to comment!