Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
353 changes: 217 additions & 136 deletions docs/src/main/asciidoc/security-openid-connect-client-reference.adoc

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
### Bean Scopes

- `@ApplicationScoped` — one instance per application (most common for services).
- `@RequestScoped` — one instance per HTTP request.
- `@Singleton` — like `@ApplicationScoped` but NOT proxied. Use only when proxy overhead matters.
- `@Dependent` — new instance per injection point. Avoid unless specifically needed.

### Dependency Injection

- Use `@Inject` on fields (package-private preferred) or constructor parameters.
- `@Inject` is not required when there is exactly one constructor (Simplified Constructor Injection).
- `@Inject` is also not required when a qualifier annotation is used (e.g., `@Channel("my-channel") Emitter<String> emitter`).
- Use `@Produces` methods to create beans that need custom initialization.

### Events

- Fire events with `Event<T>` injection and `event.fire(payload)`.
- Observe with `void onEvent(@Observes MyEvent event)`.
- Use `@ObservesAsync` for async event handling.

### Lifecycle and Startup

- `@Startup` on a bean class forces eager initialization at startup.
- Use `@PostConstruct` and `@PreDestroy` for lifecycle hooks.

### Interceptors

- Define interceptor binding with `@InterceptorBinding`.
- Implement interceptor with `@Interceptor` and `@AroundInvoke`.
- Lifecycle interceptors are also available: `@PostConstruct`, `@PreDestroy`, and `@AroundConstruct`.
- Register with `@Priority(value)`.

### Build-Time Discovery

- ArC discovers beans at build time (not runtime). Unscoped classes are NOT beans.
- Add a scope annotation to make a class a CDI bean. Note: some extensions add scope automatically (e.g. `@Scheduled`, `@WebSocket`).
- `Instance<T>` lookup is detected automatically. Use `@Unremovable` if a bean is only accessed via `CDI.current()` or ArC-specific APIs like `Arc.container()`.

### Testing

- Use `@InjectMock` to replace beans in `@QuarkusTest`.
- For `@Singleton` beans, use Mockito's `@InjectSpy` or configure the mock manually, since `@InjectMock` requires a client proxy (normal-scoped beans only).
- Use `@QuarkusComponentTest` for lightweight CDI-only tests (no full app startup).

### Common Pitfalls

- Do NOT use `new MyService()` — always let CDI inject beans.
- `@Singleton` beans are NOT proxied — there is no client proxy indirection, which gives slightly better performance.
- Quarkus supports Simplified Constructor Injection: a no-args constructor is generated automatically if missing, and `@Inject` is not required when there is exactly one constructor.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
artifact: ${project.groupId}:${project.artifactId}:${project.version}
name: "ArC"
description: "Build-time oriented CDI Lite implementation for Jakarta Contexts and Dependency Injection"
metadata:
short-name: "CDI"
keywords:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
### Entity Mapping

- Annotate entities with `@Entity` from `jakarta.persistence`.
- Use `@Id` with `@GeneratedValue` for primary keys.
- Use `@Column`, `@Table`, `@ManyToOne`, `@OneToMany`, etc. for mapping.
- Consider using Panache (`quarkus-hibernate-orm-panache`) for simplified data access.

### Session Usage

- Prefer `Session` to `EntityManager`, unless specifically instructed to follow standards.
- Use `StatelessSession` for simple CRUD scenarios, and `Session` for more involved business methods. Avoid mixing the two in a single transaction.
- Inject `Session` or `StatelessSession` with `@Inject`.
- Use `session.persist()`, `session.find()`, `session.remove()` or `statelessSession.insert()`, `statelessSession.get()`, `statelessSession.update()`, `statelessSession.delete()` for CRUD.
- Favor DTOs for REST endpoints, especially if the REST API is more limited than the domain model (only some attributes exposed in most endpoints).
- Use `session.merge()` only if a REST endpoint is accepting a serialized entity as input.
- Use `session.createSelectionQuery()` or `@NamedQuery` for JPQL queries. If Jakarta Data is in the classpath, use that in priority for static queries — especially if they are used from multiple places.
- Use DTO projections in simple read endpoints: `session.createSelectionQuery("select ...", MyDTO.class)`.
- Use entity graphs in more involved read endpoints to ensure all relevant attributes are read eagerly.

### Transactions

- Annotate service methods with `@Transactional` (from `jakarta.transaction`).
- Place `@Transactional` on the service/boundary layer, NOT on entities or repositories.

### Dev Services

- When a JDBC driver extension is on the classpath, Quarkus auto-starts a database in dev/test mode.
- In test/dev mode, if not using dev services, set `quarkus.hibernate-orm.schema-management.strategy=drop-and-create` for schema generation. If using dev services, this is set automatically by Quarkus. Note: the legacy property `quarkus.hibernate-orm.database.generation` still works but is deprecated — always use `schema-management.strategy`.
- For production, use Flyway or Liquibase for migrations.

### Testing

- Use `@QuarkusTest` — Dev Services provides a test database automatically.
- Use `@TestTransaction` to auto-rollback database changes after each test.
- Inject `Session`/`StatelessSession` in tests for direct database assertions.

### Common Pitfalls

- Do NOT use `@Transactional` on private methods — CDI proxies cannot intercept them.
- Hibernate Reactive and Hibernate ORM can coexist in the same project, but do NOT mix them in the same method — they use separate persistence contexts.
- Always add a JDBC driver extension when using Hibernate ORM, a Vert.x SQL client extension when using Hibernate Reactive.
- Define `quarkus.datasource.db-kind` for the default datasource if there are multiple JDBC drivers or Vert.x SQL client extensions in the classpath.
- Always define `quarkus.datasource."my-datasource".db-kind` for named datasources; this ensures the datasource is detected by Quarkus at build time.
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
---
artifact: ${project.groupId}:${project.artifactId}:${project.version}
name: "Hibernate ORM"
description: "Object-relational mapping with JPA/Hibernate for relational database access"
metadata:
short-name: "JPA"
keywords:
- "hibernate-orm"
- "jpa"
- "hibernate"
guide: "https://quarkus.io/guides/hibernate-orm"
quickstart: "hibernate-orm-quickstart"
categories:
- "data"
status: "stable"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.quarkus.hibernate.reactive.transaction;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.concurrent.atomic.AtomicInteger;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import org.hibernate.reactive.mutiny.Mutiny;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.vertx.RunOnVertxContext;
import io.quarkus.test.vertx.UniAsserter;
import io.smallrye.mutiny.Uni;

public class HibernateReactiveTransactionalRetryTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(jar -> jar
.addClasses(Hero.class)
.addAsResource("initialTransactionRetryData.sql", "import.sql"))
.withConfigurationResource("application-reactive-transaction.properties");

@Inject
Mutiny.SessionFactory sessionFactory;

@Inject
Mutiny.Session session;

private final AtomicInteger callCounter = new AtomicInteger();

@Test
@RunOnVertxContext
public void testTransactionalRetry(UniAsserter asserter) {
Long hero1Id = 50L;
Long hero2Id = 51L;
callCounter.set(0);

// Test that:
// 1. First call fails -> DB changes to hero1 are rolled back
// 2. Method is retried (second call)
// 3. Second call succeeds -> DB changes to hero2 are committed
asserter.assertThat(
() -> updateHeroWithRetry(hero1Id, hero2Id)
.onFailure().retry().atMost(1),
h -> {
// Verify the successful update returned hero2
assertThat(h.id).isEqualTo(hero2Id);
assertThat(h.name).isEqualTo("updatedHero2");
// Verify the method was called twice (first failed, second succeeded)
assertThat(callCounter.get()).isEqualTo(2);
});

// Verify that hero1 still has its original name (first call was rolled back)
asserter.assertThat(
() -> findHero(hero1Id),
h -> assertThat(h.name).isEqualTo("hero1"));

// Verify that hero2 has the updated name (second call was committed)
asserter.assertThat(
() -> findHero(hero2Id),
h -> assertThat(h.name).isEqualTo("updatedHero2"));
}

@Transactional
public Uni<Hero> updateHeroWithRetry(Long hero1Id, Long hero2Id) {
int invocation = callCounter.incrementAndGet();

if (invocation == 1) {
// First call: update hero1, then fail
return session.find(Hero.class, hero1Id)
.map(hero -> {
hero.setName("shouldBeRolledBack");
return hero;
})
.onItem().invoke(() -> {
throw new RuntimeException("Simulated failure on first call");
});
} else {
// Second call: update hero2, then succeed
return session.find(Hero.class, hero2Id)
.map(hero -> {
hero.setName("updatedHero2");
return hero;
});
}
}

@Transactional
public Uni<Hero> findHero(Long heroId) {
return session.find(Hero.class, heroId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
insert into Hero (id, name) values (50, 'hero1');
insert into Hero (id, name) values (51, 'hero2');
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
### Bean Validation Annotations

- Use constraints from `jakarta.validation.constraints`/`org.hibernate.validator.constraints` packages, e.g. `@NotNull`, `@NotBlank`, `@NotEmpty`, `@Size`, `@Min`, `@Max`, `@Email`, `@Pattern` on class fields and getters or on method parameters and return values of CDI beans.
- Use `@Valid` for cascading validation: on bean properties or method's return value/parameters represented by a complex type with constraints.
- Place constraints on REST endpoint parameters for automatic 400 responses.

### REST Integration

- Constraint violations on REST endpoint **parameters** automatically return HTTP 400 with validation details.
- Annotate request body DTOs with constraints, and use `@Valid` on the endpoint parameter.
- Return value validation and service method validation result in HTTP 500, not 400 — handle `ConstraintViolationException` explicitly if you need custom error responses for those.

### Custom Validators

- For simple constraints that are only applicable to the current type consider creating a simple boolean getter constrained with `@AssertTrue`: `@AssertTrue(message="{message.key.for.this.check}") public boolean is[NameOfTheCheck]() { ... }`
- For reusable constraints:
* Create a constraint annotation with `@Target({ METHOD, FIELD, ... }) @Retention(RUNTIME) @Constraint(validatedBy = {})`.
* Implement `ConstraintValidator<MyAnnotation, TargetType>`.
* Add a FQCN of the implemented constraint validator to the service file `src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator`

### Method Validation

- Annotate CDI bean method parameters and return values with constraints.
- Method validation is automatic for CDI beans — no extra config needed.

### Testing

- Test validation by sending invalid input via REST Assured and asserting 400 status.
- For unit testing validators, use `Validator` injection and `validator.validate(object)`.

### Common Pitfalls

- Do NOT forget `@Valid` on nested object parameters — Without it, constraints on nested object fields are silently ignored.
- Automatic method parameter/return value validation only works on CDI-managed beans — any method calls on plain `new` objects are not validated.
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
---
artifact: ${project.groupId}:${project.artifactId}:${project.version}
name: "Hibernate Validator"
description: "Bean validation using Hibernate Validator and Jakarta Validation annotations"
metadata:
short-name: "bean validation"
keywords:
- "hibernate-validator"
- "bean-validation"
- "validation"
guide: "https://quarkus.io/guides/validation"
quickstart: "validation-quickstart"
categories:
- "web"
- "data"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
### Application Types

- `service` (default) — bearer token validation for APIs. Tokens validated but no login flow.
- `web-app` — authorization code flow with login redirect. For server-rendered apps.
- `hybrid` — supports both. Set `quarkus.oidc.application-type`.

### Configuration

- Set `quarkus.oidc.auth-server-url` to the OIDC provider (e.g. Keycloak realm URL).
- For `service`: set auth-server-url only. Token validation is automatic.
- For `web-app`: also set `quarkus.oidc.client-id` and `quarkus.oidc.credentials.secret`.

### Token Access

- Inject the **access** token with `@Inject JsonWebToken jwt` (no qualifier needed).
- Inject the **ID** token with `@Inject @IdToken JsonWebToken idToken`.
- Access claims with `idToken.getClaim("email")` or `idToken.getName()`.
- Use `@RolesAllowed` with roles from the token for authorization.

### Keycloak Dev Services

- When Keycloak is not configured, Dev Services starts a Keycloak container automatically.
- A default realm with test users is created. Check Dev UI for credentials.

### Testing

- Add `quarkus-test-security-oidc` as a test dependency for OIDC-specific test annotations.
- Use `@TestSecurity(user = "alice", roles = "user")` for simple auth simulation.
- For custom token claims, combine `@TestSecurity` with `@OidcSecurity(claims = @Claim(key = "email", value = "alice@example.com"))`.
- For integration tests with real tokens, use `OidcTestClient`.

### Common Pitfalls

- Do NOT hardcode auth-server-url — use `%prod.` prefix and let Dev Services handle dev/test.
- Token validation requires the OIDC server to be reachable — handle startup failures gracefully.
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
---
artifact: ${project.groupId}:${project.artifactId}:${project.version}
name: "OpenID Connect"
description: "Secure applications with OpenID Connect and OAuth 2.0 using bearer tokens and authorization code flow"
metadata:
keywords:
- "oauth2"
- "openid-connect"
- "oidc"
guide: "https://quarkus.io/guides/security-openid-connect"
quickstart: "security-openid-connect-quickstart"
categories:
- "security"
status: "stable"
Expand Down
Loading
Loading