Skip to content

Migrate Spring Boot example to Spring Boot 4, Keycloak 26 and Angular 22 - #41

Closed
Wictorgirardi wants to merge 2 commits into
p2-inc:mainfrom
Wictorgirardi:feat/spring-boot-keycloak
Closed

Wictorgirardi wants to merge 2 commits into
p2-inc:mainfrom
Wictorgirardi:feat/spring-boot-keycloak

Conversation

@Wictorgirardi

Copy link
Copy Markdown
Contributor

Depends on #28: this branch includes its commit, so review the last commit only until #28 is merged.

Summary

  • API toolchain:
    • Spring Boot 3.2.5 → 4.1.1 (Spring Security 7) and Gradle 8.7 → 9.7.1.
    • Java 17 → a Java 21 toolchain, resolved by the foojay plugin.
    • The dependencies use Boot 4's modular starters: spring-boot-starter-security, spring-boot-starter-security-oauth2-resource-server and spring-boot-starter-webmvc, plus their -test starters.
  • API security fixes:
    • GET /api/test/anonymous is public. The old config required a token on all of /api/**, including this endpoint.
    • Paths outside /api/**, /error and the protected resource metadata are denied.
    • CORS is configured for the Angular client, from app.cors.allowed-origins.
    • The endpoints return JSON records, and /api/test/user includes the caller's preferred_username.
    • JwtClaimsConverter is a plain converter without the unchecked cast.
    • The TRACE security logging and the redundant jwk-set-uri are removed. The issuer can be overridden with KEYCLOAK_ISSUER_URI.
  • Keycloak:
    • docker-compose.yml runs quay.io/phasetwo/phasetwo-keycloak:26.6 on port 8888.
    • It imports keycloak/demo-realm-realm.json: the public demo-spa client with PKCE, the realm role user, and two users. test / test has the role; noaccess / noaccess doesn't.
    • Before, it ran Keycloak 24 with a JDWP debug agent and an empty realm to set up by hand.
  • Angular client: rebuilt on Angular 22.2 the same way as Migrate Angular example to Angular 22 and angular-oauth2-oidc 22 #40: standalone, zoneless, Vitest, angular-eslint 22.5 with ESLint 10, Tailwind 4, pnpm 10.34 and Node 24.
    • The old client declared Angular 21 with TypeScript 4.1, rxjs 6, tslint and Tailwind 2, so it could not install or build.
    • angular-oauth2-oidc 10 → 22, configured in provideAppInitializer.
    • PKCE is on; it was turned off with disablePKCE. Tokens are kept in session storage instead of local storage, and debug output is off.
    • provideOAuthClient attaches the access token only to requests to the API.
    • Buttons call both endpoints and show the response; the old client never called the API.
    • A functional authGuard protects /protected. The old guard always returned of(true).
    • It uses the shared layout and status strings, so tools/e2e-smoke runs against it.
  • Tests: TestControllerTests sends requests through the security filter chain and checks the public endpoint, 401, 403, 200 and CORS. JwtClaimsConverterTests checks the role mapping. None of them need Keycloak.
  • CI: spring-boot-keycloak.yml runs the shared Gradle workflow for the API and the shared Node workflow for the client.
  • README: rewritten to cover the architecture, the endpoints, the local Keycloak, curl calls, configuration and using your own realm.

Test plan

  • ./gradlew build passes with the Java 21 toolchain. The client's pnpm install --frozen-lockfile && pnpm lint && pnpm test && pnpm build passes on Node 24.
  • curl against the running API:
    • /api/test/anonymous answers 200 without a token;
    • /api/test/user answers 401 without a token and with a malformed one;
    • the CORS preflight is allowed from http://localhost:4200 and rejected with 403 from another origin.
  • Playwright against the local Keycloak:
    • test gets 200 from /api/test/user, and noaccess gets 403;
    • deep links to /protected go through Keycloak and come back;
    • logout ends the Keycloak session.
  • tools/e2e-smoke passes on port 4200 with test / test.
  • No console errors, failed requests, or WARN or ERROR lines in the API and dev-server logs.

Docs drift

blog/2024-05-09-secure-spring-boot.mdx:

  • L38 and L81: Java 17 → 21 and Keycloak 24 → 26.6.
  • L40 and L73–77: Spring Boot 3 → 4.1, with the starters listed above.
  • L52–61: the Initializr metadata has an invalid package name (com.example.spring-boot-keycloak), and the screenshot is outdated.
  • L83–104: the hosted starter instance no longer exists.
    • The local compose file imports demo-realm with test / test and noaccess / noaccess.
    • The client is the public demo-spa (PKCE, http://localhost:4200/*, web origin and post logout +), not a confidential client as in _oidc_client_creation_client_auth.mdx.
  • L26 and L181: angular.io → angular.dev, Angular CLI 22.

templates/frameworks/_springboot.mdx:

  • L3: hosted Keycloak → local compose.
  • L9–19: the YAML uses ${KEYCLOAK_ISSUER_URI:…}, drops jwk-set-uri and adds app.cors.allowed-origins.
  • L32 and L105: the package path is src/main/java/com/example/springbootkeycloak.
  • L34–62: SecurityConfig has CORS, public /error and GET /api/test/anonymous, and anyRequest().denyAll(), without @EnableWebSecurity or constructor injection.
  • L70–97: JwtClaimsConverter is not a @Component and has no unchecked cast.
  • L114–152: TestController uses @GetMapping and returns JSON records. /anonymous is public; the post says both endpoints need a token.
  • L154–165: the password-grant curl no longer works, since the client is public and direct grants are off. The README shows how to take a token from the Angular client.
  • L186–237: NgModule, APP_INITIALIZER, HttpClientModule, local storage and disablePKCE: true → a standalone app.config.ts with provideOAuthClient and provideAppInitializer, PKCE, and session storage.
  • L241: npm run start → pnpm start.
  • L245–291: user.component with *ngIf and a guard that always passed → home with @if, API buttons, and a functional authGuard on /protected.

@Wictorgirardi

Copy link
Copy Markdown
Contributor Author

Combined into #46 with the other Spring Boot example. The changes are the same; only the commit SHAs differ.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant