// Sign in with the EU Digital Identity Wallet - Java 17+ (Spring Boot 3, Spring Security 6) // CodeB acts as the verifier: it asks the user's wallet for a presentation, // verifies it, and hands your app a signed OpenID Connect id_token. // // Dependencies (Maven, Spring Boot 3.3 parent): // spring-boot-starter-web, spring-boot-starter-oauth2-client // Save as src/main/java/demo/WalletSignInApplication.java, then // CODEB_CLIENT_ID=... CODEB_CLIENT_SECRET=... mvn spring-boot:run // // Register https:///callback as redirect URI for your client first. package demo; import java.util.List; import java.util.Map; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ClientRegistrations; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class WalletSignInApplication { static final List WALLET_ACRS = List.of("urn:codeb:acr:eudi-wallet", "urn:codeb:acr:eudi-wallet-linked"); static String env(String k, String d) { String v = System.getenv(k); return v == null || v.isEmpty() ? d : v; } public static void main(String[] args) { System.setProperty("server.port", env("PORT", "3000")); SpringApplication.run(WalletSignInApplication.class, args); } @Bean ClientRegistrationRepository clients() { ClientRegistration codeb = ClientRegistrations .fromIssuerLocation(env("CODEB_ISSUER", "https://phone.aloaha.com")) .registrationId("codeb") .clientId(env("CODEB_CLIENT_ID", "")) .clientSecret(env("CODEB_CLIENT_SECRET", "")) .redirectUri("{baseUrl}/callback") .scope("openid", "profile", "email") .build(); return new InMemoryClientRegistrationRepository(codeb); } // After Spring has validated the id_token (signature, issuer, audience, nonce, expiry): require the wallet acr. @Bean OAuth2UserService walletOnly() { OidcUserService delegate = new OidcUserService(); return request -> { OidcUser user = delegate.loadUser(request); String acr = user.getIdToken().getClaimAsString("acr"); if (!WALLET_ACRS.contains(acr)) { throw new OAuth2AuthenticationException(new OAuth2Error("insufficient_user_authentication", "This page needs a sign-in with the EU Digital Identity Wallet (acr=" + acr + ").", null)); } return user; }; } @Bean SecurityFilterChain security(HttpSecurity http, ClientRegistrationRepository repo) throws Exception { DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver(repo, "/oauth2/authorization"); resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce() .andThen(b -> b.additionalParameters(p -> p.put("acr_values", WALLET_ACRS.get(0))))); // straight to the wallet sign-in http.authorizeHttpRequests(a -> a.requestMatchers("/", "/error").permitAll().anyRequest().authenticated()) .oauth2Login(o -> o .authorizationEndpoint(e -> e.authorizationRequestResolver(resolver)) .redirectionEndpoint(r -> r.baseUri("/callback")) .defaultSuccessUrl("/", true) .failureHandler((req, res, ex) -> res.sendError(403, ex.getMessage()))); return http.build(); } @GetMapping("/") String home(@AuthenticationPrincipal OidcUser user) { if (user == null) return "Sign in with your EU Digital Identity Wallet"; return "Signed in as " + user.getSubject() + " (acr=" + user.getIdToken().getClaimAsString("acr") + ")"; } }