Migrate the Spring Boot examples: resource server with Angular, and SAML - #46
Merged
Merged
Conversation
pnzrr
self-requested a review
October 5, 2026 16:35
pnzrr
force-pushed
the
feat/spring-boot-examples
branch
from
October 5, 2026 16:53
dd7daf2 to
2088fad
Compare
pnzrr
approved these changes
Oct 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #28: the branch includes its commit.
This PR replaces #41 and #42: the two Spring Boot 4.1 examples. Each one runs its own Keycloak with Docker Compose, because their tutorials use their own realms.
Each example has its own commits and touches only its own folder and workflow. The commits follow the order of the table, so the PR is easiest to review commit by commit. The section for each example is the description of the PR it replaces.
frameworks/spring-boot-keycloaksaml2/idp-initiatedSpring Boot + Angular
Replaces #41.
Summary
spring-boot-starter-security,spring-boot-starter-security-oauth2-resource-serverandspring-boot-starter-webmvc, plus their-teststarters.GET /api/test/anonymousis public. The old config required a token on all of/api/**, including this endpoint./api/**,/errorand the protected resource metadata are denied.app.cors.allowed-origins./api/test/userincludes the caller'spreferred_username.JwtClaimsConverteris a plain converter without the unchecked cast.jwk-set-uriare removed. The issuer can be overridden withKEYCLOAK_ISSUER_URI.docker-compose.ymlrunsquay.io/phasetwo/phasetwo-keycloak:26.6on port 8888.keycloak/demo-realm-realm.json: the publicdemo-spaclient with PKCE, the realm roleuser, and two users.test/testhas the role;noaccess/noaccessdoesn't.provideAppInitializer.disablePKCE. Tokens are kept in session storage instead of local storage, and debug output is off.provideOAuthClientattaches the access token only to requests to the API.authGuardprotects/protected. The old guard always returnedof(true).tools/e2e-smokeruns against it.TestControllerTestssends requests through the security filter chain and checks the public endpoint, 401, 403, 200 and CORS.JwtClaimsConverterTestschecks the role mapping. None of them need Keycloak.spring-boot-keycloak.ymlruns the shared Gradle workflow for the API and the shared Node workflow for the client.Test plan
./gradlew buildpasses with the Java 21 toolchain. The client'spnpm install --frozen-lockfile && pnpm lint && pnpm test && pnpm buildpasses on Node 24./api/test/anonymousanswers 200 without a token;/api/test/useranswers 401 without a token and with a malformed one;http://localhost:4200and rejected with 403 from another origin.testgets 200 from/api/test/user, andnoaccessgets 403;/protectedgo through Keycloak and come back;tools/e2e-smokepasses on port 4200 withtest/test.Docs drift
blog/2024-05-09-secure-spring-boot.mdx:com.example.spring-boot-keycloak), and the screenshot is outdated.demo-realmwithtest/testandnoaccess/noaccess.demo-spa(PKCE,http://localhost:4200/*, web origin and post logout+), not a confidential client as in_oidc_client_creation_client_auth.mdx.templates/frameworks/_springboot.mdx:${KEYCLOAK_ISSUER_URI:…}, dropsjwk-set-uriand addsapp.cors.allowed-origins.src/main/java/com/example/springbootkeycloak.SecurityConfighas CORS, public/errorandGET /api/test/anonymous, andanyRequest().denyAll(), without@EnableWebSecurityor constructor injection.JwtClaimsConverteris not a@Componentand has no unchecked cast.TestControlleruses@GetMappingand returns JSON records./anonymousis public; the post says both endpoints need a token.NgModule,APP_INITIALIZER,HttpClientModule, local storage anddisablePKCE: true→ a standaloneapp.config.tswithprovideOAuthClientandprovideAppInitializer, PKCE, and session storage.npm run start→pnpm start.user.componentwith*ngIfand a guard that always passed →homewith@if, API buttons, and a functionalauthGuardon/protected.SAML IdP-initiated SSO (Spring Boot)
Replaces #42.
Summary
spring-boot-starter-security-saml2in place of the barespring-security-saml2-service-provider.src/main/resources/credentials/are deleted, along withkeycloak/saml-client.json, which embedded a client private key.scripts/generate-sp-credentials.shcreates the key pair in a gitignoredcredentials/folder. Both old keys stay in the git history, so the README says never to trust them.docker-compose.ymlrunsquay.io/phasetwo/phasetwo-keycloak:26.6on port 8080, without the/authpath, like the tutorial.keycloak/test-realm-export.json, which replaces a 2,200-line export that referenced a real Okta tenant. The new realm has the SAML clientokta-client, the usertest/test, and a disabledokta-brokeridentity provider with placeholder values.-exportsuffix because Keycloak imports a file named<name>-realm.jsonas the realm<name>and refusedtest-realm.json.okta-app→keycloak, and the invalididp-entity-idproperty is removed.SecurityConfigurationaddssaml2Logoutandsaml2Metadata, so the SP publishes its metadata at/saml2/metadata.Saml2AssertionAuthentication, which replaces the deprecatedSaml2AuthenticatedPrincipal.contextLoadstest needed a running Keycloak, so it couldn't pass in CI.saml2-idp-initiated.ymlruns the shared Gradle workflow.Test plan
./gradlew buildpasses without credentials or Keycloak (5 tests), with the Java 21 toolchain.docker compose up -d --waitimports the realm, and the admin API shows the expected client, mappers, identity provider and user.InResponseTo) and signed, with Destination = ACS and Audience = SP entity ID.test,email,firstNameandlastName.InResponseTo).LogoutRequestgets a signed successLogoutResponse.invalid_signature.