diff --git a/endpoints-api-modified.yaml b/endpoints-api-modified.yaml
index 152a8d8..d932bbd 100644
--- a/endpoints-api-modified.yaml
+++ b/endpoints-api-modified.yaml
@@ -183,24 +183,24 @@ paths:
application/trust-mark+jwt:
schema:
type: string
- 400:
- description: Bad Request
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- 404:
- description: Not Found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- 500:
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
+# 400:
+# description: Bad Request
+# content:
+# application/json:
+# schema:
+# $ref: '#/components/schemas/ErrorResponse'
+# 404:
+# description: Not Found
+# content:
+# application/json:
+# schema:
+# $ref: '#/components/schemas/ErrorResponse'
+# 500:
+# description: Server Error
+# content:
+# application/json:
+# schema:
+# $ref: '#/components/schemas/ErrorResponse'
/{name}/trust_mark_listing:
get:
@@ -233,24 +233,24 @@ paths:
type: array
items:
type: string
- 400:
- description: Bad Request
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- 404:
- description: Not Found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- 500:
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
+# 400:
+# description: Bad Request
+# content:
+# application/json:
+# schema:
+# $ref: '#/components/schemas/ErrorResponse'
+# 404:
+# description: Not Found
+# content:
+# application/json:
+# schema:
+# $ref: '#/components/schemas/ErrorResponse'
+# 500:
+# description: Server Error
+# content:
+# application/json:
+# schema:
+# $ref: '#/components/schemas/ErrorResponse'
/{name}/trust_mark_status:
post:
diff --git a/pom.xml b/pom.xml
index e36cbca..b528a50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -193,173 +193,175 @@
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
- eudiw-wallet-issuer-poc
-
- true
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- ${java.version}
-
-
- org.projectlombok
- lombok
- ${lombok.version}
-
-
-
-
-
-
-
- org.openapitools
- openapi-generator-maven-plugin
- ${openapi-generator.version}
-
-
-
- generate
-
-
- ${project.basedir}/endpoints-api-modified.yaml
- java
-
- resttemplate
- false
- true
- false
-
- se.digg.eudiw
- se.digg.eudiw.client
- se.digg.eudiw.model
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- ${maven-surefire-plugin.version}
-
-
- org.apache.maven.plugins
- maven-failsafe-plugin
- ${maven-failsafe-plugin.version}
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- ${maven-javadoc-plugin.version}
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
- ${maven-source-plugin.version}
-
-
- attach-sources
-
- jar
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-deploy-plugin
- ${maven-deploy-plugin.version}
-
- local::file:./target/staging-deploy
- false
-
-
-
-
-
- org.jreleaser
- jreleaser-maven-plugin
- ${jreleaser-maven-plugin.version}
-
- ${project.basedir}/jreleaser.yml
-
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- ${maven-enforcer-plugin.version}
-
-
- enforce-versions
-
- enforce
-
-
-
-
-
- [3.8.0,)
- Maven 3.8.0 or higher is required
-
-
-
-
- [21,)
- Java 21 or higher is required
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
-
-
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ eudiw-wallet-issuer-poc
+
+ true
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+ ${openapi-generator.version}
+
+
+
+ generate
+
+
+ ${project.basedir}/endpoints-api-modified.yaml
+ java
+
+ resttemplate
+ false
+ true
+ false
+
+ false
+ false
+ se.digg.eudiw
+ se.digg.eudiw.client
+ se.digg.eudiw.model
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${maven-failsafe-plugin.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ ${maven-deploy-plugin.version}
+
+ local::file:./target/staging-deploy
+ false
+
+
+
+
+
+ org.jreleaser
+ jreleaser-maven-plugin
+ ${jreleaser-maven-plugin.version}
+
+ ${project.basedir}/jreleaser.yml
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ ${maven-enforcer-plugin.version}
+
+
+ enforce-versions
+
+ enforce
+
+
+
+
+
+ [3.8.0,)
+ Maven 3.8.0 or higher is required
+
+
+
+
+ [21,)
+ Java 21 or higher is required
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/se/digg/eudiw/authorization/EudiwAccessTokenCustomizer.java b/src/main/java/se/digg/eudiw/authorization/EudiwAccessTokenCustomizer.java
new file mode 100644
index 0000000..a3da8b1
--- /dev/null
+++ b/src/main/java/se/digg/eudiw/authorization/EudiwAccessTokenCustomizer.java
@@ -0,0 +1,62 @@
+package se.digg.eudiw.authorization;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
+import se.digg.eudiw.authentication.SwedenConnectPrincipal;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class EudiwAccessTokenCustomizer implements OAuth2TokenCustomizer {
+ public EudiwAccessTokenCustomizer() {
+ }
+
+ @Override
+ public void customize(OAuth2TokenClaimsContext context) {
+ Authentication principal = context.getPrincipal();
+ if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
+ Set authorities = principal.getAuthorities().stream().map(GrantedAuthority::getAuthority)
+ .collect(Collectors.toSet());
+
+ context.getClaims().claim("authorities", authorities);
+
+ // TODO det mesta borde flyttas till idToken eller userinfo någonting
+ OAuth2TokenClaimsSet.Builder claims = context.getClaims();
+ if (principal.getPrincipal() instanceof SwedenConnectPrincipal) {
+ SwedenConnectPrincipal p = (SwedenConnectPrincipal) principal.getPrincipal();
+ if (p.getSubjAttributes().getPersonalNumber() != null) claims.claim("personalNumber", p.getSubjAttributes().getPersonalNumber());
+ if (p.getSubjAttributes().getName() != null) claims.claim("name", p.getSubjAttributes().getName());
+ if (p.getSubjAttributes().getGivenName() != null) claims.claim("givenName", p.getSubjAttributes().getGivenName());
+ if (p.getSubjAttributes().getSurname() != null) claims.claim("surname", p.getSubjAttributes().getSurname());
+ if (p.getSubjAttributes().getCoordinationNumber() != null) claims.claim("coordinationNumber", p.getSubjAttributes().getCoordinationNumber());
+ if (p.getSubjAttributes().getPrid() != null) claims.claim("prid", p.getSubjAttributes().getPrid());
+ if (p.getSubjAttributes().getPridPersistence() != null) claims.claim("pridPersistence", p.getSubjAttributes().getPridPersistence());
+ if (p.getSubjAttributes().getEidasPersonIdentifier() != null) claims.claim("eidasPersonIdentifier", p.getSubjAttributes().getEidasPersonIdentifier());
+ if (p.getSubjAttributes().getPersonalNumberBinding() != null) claims.claim("personalNumberBinding", p.getSubjAttributes().getPersonalNumberBinding());
+ if (p.getSubjAttributes().getOrgNumber() != null) claims.claim("orgNumber", p.getSubjAttributes().getOrgNumber());
+ if (p.getSubjAttributes().getOrgAffiliation() != null) claims.claim("orgAffiliation", p.getSubjAttributes().getOrgAffiliation());
+ if (p.getSubjAttributes().getOrgName() != null) claims.claim("orgName", p.getSubjAttributes().getOrgName());
+ if (p.getSubjAttributes().getOrgUnit() != null) claims.claim("orgUnit", p.getSubjAttributes().getOrgUnit());
+ if (p.getSubjAttributes().getUserCertificate() != null) claims.claim("userCertificate", p.getSubjAttributes().getUserCertificate());
+ if (p.getSubjAttributes().getUserSignature() != null) claims.claim("userSignature", p.getSubjAttributes().getUserSignature());
+ if (p.getSubjAttributes().getDeviceIp() != null) claims.claim("deviceIp", p.getSubjAttributes().getDeviceIp());
+ if (p.getSubjAttributes().getAuthnEvidence() != null) claims.claim("authnEvidence", p.getSubjAttributes().getAuthnEvidence());
+ if (p.getSubjAttributes().getCountry() != null) claims.claim("country", p.getSubjAttributes().getCountry());
+ if (p.getSubjAttributes().getBirthName() != null) claims.claim("birthName", p.getSubjAttributes().getBirthName());
+ if (p.getSubjAttributes().getPlaceOfbirth() != null) claims.claim("placeOfbirth", p.getSubjAttributes().getPlaceOfbirth());
+ if (p.getSubjAttributes().getAge() != null) claims.claim("age", p.getSubjAttributes().getAge());
+ if (p.getSubjAttributes().getBirthDate() != null) claims.claim("birthDate", p.getSubjAttributes().getBirthDate());
+
+ claims.claim("client_id", context.getRegisteredClient().getClientId());
+
+ }
+
+ }
+ }
+}
diff --git a/src/main/java/se/digg/eudiw/authorization/EudiwJwtGenerator.java b/src/main/java/se/digg/eudiw/authorization/EudiwJwtGenerator.java
new file mode 100644
index 0000000..ca10444
--- /dev/null
+++ b/src/main/java/se/digg/eudiw/authorization/EudiwJwtGenerator.java
@@ -0,0 +1,151 @@
+package se.digg.eudiw.authorization;
+
+import org.springframework.lang.Nullable;
+import org.springframework.security.core.session.SessionInformation;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
+import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.*;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.UUID;
+import org.springframework.security.oauth2.jwt.JwsHeader;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
+import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
+import se.digg.eudiw.authentication.SwedenConnectPrincipal;
+
+// TODO vi borde kunna konfigurera endast en jwtCustomizer men av någon anledning har det krånglat...
+
+public final class EudiwJwtGenerator implements OAuth2TokenGenerator {
+ private final JwtEncoder jwtEncoder;
+ private OAuth2TokenCustomizer jwtCustomizer;
+
+ public EudiwJwtGenerator(JwtEncoder jwtEncoder) {
+ Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
+ this.jwtEncoder = jwtEncoder;
+ }
+
+ @Nullable
+ public Jwt generate(OAuth2TokenContext context) {
+ if (context.getTokenType() != null && (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || "id_token".equals(context.getTokenType().getValue()))) {
+ if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && !OAuth2TokenFormat.SELF_CONTAINED.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {
+ return null;
+ } else {
+ String issuer = null;
+ if (context.getAuthorizationServerContext() != null) {
+ issuer = context.getAuthorizationServerContext().getIssuer();
+ }
+
+ RegisteredClient registeredClient = context.getRegisteredClient();
+ Instant issuedAt = Instant.now();
+ JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256;
+ Instant expiresAt;
+ if ("id_token".equals(context.getTokenType().getValue())) {
+ expiresAt = issuedAt.plus(30L, ChronoUnit.MINUTES);
+ if (registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm() != null) {
+ jwsAlgorithm = registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm();
+ }
+ } else {
+ expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());
+ }
+
+ JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
+ if (StringUtils.hasText(issuer)) {
+ claimsBuilder.issuer(issuer);
+ }
+
+ if (context.getPrincipal().getPrincipal() instanceof SwedenConnectPrincipal) {
+ SwedenConnectPrincipal swedenConnectPrincipal = (SwedenConnectPrincipal)context.getPrincipal().getPrincipal();
+ claimsBuilder.claim("personalNumber", swedenConnectPrincipal.getSubjAttributes().getPersonalNumber());
+ claimsBuilder.claim("surname", swedenConnectPrincipal.getSubjAttributes().getSurname());
+ claimsBuilder.claim("givenName", swedenConnectPrincipal.getSubjAttributes().getGivenName());
+ claimsBuilder.claim("birthDate", swedenConnectPrincipal.getSubjAttributes().getBirthDate());
+ }
+
+ claimsBuilder.claim("clientId", registeredClient.getClientId());
+
+ claimsBuilder.subject(context.getPrincipal().getName()).audience(Collections.singletonList(registeredClient.getClientId())).issuedAt(issuedAt).expiresAt(expiresAt).id(UUID.randomUUID().toString());
+ if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
+ claimsBuilder.notBefore(issuedAt);
+ if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
+ claimsBuilder.claim("scope", context.getAuthorizedScopes());
+ }
+ } else if ("id_token".equals(context.getTokenType().getValue())) {
+ claimsBuilder.claim("azp", registeredClient.getClientId());
+ if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType())) {
+ OAuth2AuthorizationRequest authorizationRequest = (OAuth2AuthorizationRequest)context.getAuthorization().getAttribute(OAuth2AuthorizationRequest.class.getName());
+ String nonce = (String)authorizationRequest.getAdditionalParameters().get("nonce");
+ if (StringUtils.hasText(nonce)) {
+ claimsBuilder.claim("nonce", nonce);
+ }
+
+ SessionInformation sessionInformation = (SessionInformation)context.get(SessionInformation.class);
+ if (sessionInformation != null) {
+ claimsBuilder.claim("sid", sessionInformation.getSessionId());
+ claimsBuilder.claim("auth_time", sessionInformation.getLastRequest());
+ }
+ } else if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) {
+ OidcIdToken currentIdToken = (OidcIdToken)context.getAuthorization().getToken(OidcIdToken.class).getToken();
+ if (currentIdToken.hasClaim("sid")) {
+ claimsBuilder.claim("sid", currentIdToken.getClaim("sid"));
+ }
+
+ if (currentIdToken.hasClaim("auth_time")) {
+ claimsBuilder.claim("auth_time", currentIdToken.getClaim("auth_time"));
+ }
+ }
+ }
+
+ JwsHeader.Builder jwsHeaderBuilder = JwsHeader.with(jwsAlgorithm);
+ if (this.jwtCustomizer != null) {
+ JwtEncodingContext.Builder jwtContextBuilder = (JwtEncodingContext.Builder)((JwtEncodingContext.Builder)((JwtEncodingContext.Builder)((JwtEncodingContext.Builder)((JwtEncodingContext.Builder)((JwtEncodingContext.Builder)JwtEncodingContext.with(jwsHeaderBuilder, claimsBuilder).registeredClient(context.getRegisteredClient())).principal(context.getPrincipal())).authorizationServerContext(context.getAuthorizationServerContext())).authorizedScopes(context.getAuthorizedScopes())).tokenType(context.getTokenType())).authorizationGrantType(context.getAuthorizationGrantType());
+ if (context.getAuthorization() != null) {
+ jwtContextBuilder.authorization(context.getAuthorization());
+ }
+
+ if (context.getAuthorizationGrant() != null) {
+ jwtContextBuilder.authorizationGrant(context.getAuthorizationGrant());
+ }
+
+ if ("id_token".equals(context.getTokenType().getValue())) {
+ SessionInformation sessionInformation = (SessionInformation)context.get(SessionInformation.class);
+ if (sessionInformation != null) {
+ jwtContextBuilder.put(SessionInformation.class, sessionInformation);
+ }
+ }
+
+ JwtEncodingContext jwtContext = jwtContextBuilder.build();
+ this.jwtCustomizer.customize(jwtContext);
+ }
+
+ JwsHeader jwsHeader = jwsHeaderBuilder.build();
+ JwtClaimsSet claims = claimsBuilder.build();
+ Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims));
+ return jwt;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public void setJwtCustomizer(OAuth2TokenCustomizer jwtCustomizer) {
+ Assert.notNull(jwtCustomizer, "jwtCustomizer cannot be null");
+ this.jwtCustomizer = jwtCustomizer;
+ }
+}
diff --git a/src/main/java/se/digg/eudiw/authorization/FederatedRegisteredClientRepository.java b/src/main/java/se/digg/eudiw/authorization/FederatedRegisteredClientRepository.java
deleted file mode 100644
index 91c0595..0000000
--- a/src/main/java/se/digg/eudiw/authorization/FederatedRegisteredClientRepository.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package se.digg.eudiw.authorization;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.stereotype.Component;
-import se.digg.eudiw.config.EudiwConfig;
-import se.digg.eudiw.service.OpenIdFederationService;
-import se.digg.wallet.metadata.WalletOAuthClientMetadata;
-
-@Component
-public class FederatedRegisteredClientRepository implements RegisteredClientRepository {
-
- private final String walletProviderAnchor;
- private final OpenIdFederationService openIdFederationService;
-
-
- FederatedRegisteredClientRepository(@Autowired EudiwConfig eudiwConfig, @Autowired OpenIdFederationService openIdFederationService) {
- this.walletProviderAnchor = eudiwConfig.getOpenidFederation().walletProviderAnchor();
- this.openIdFederationService = openIdFederationService;
- }
-
- @Override
- public void save(RegisteredClient registeredClient) {
-
- }
-
- @Override
- public RegisteredClient findById(String id) {
- return null;
- }
-
- @Override
- public RegisteredClient findByClientId(String clientId) {
- WalletOAuthClientMetadata walletOAuthClientMetadata = openIdFederationService.resolveWallet(clientId);
- return null;
- }
-}
diff --git a/src/main/java/se/digg/eudiw/authorization/OidFederatedRegisteredClientRepository.java b/src/main/java/se/digg/eudiw/authorization/OidFederatedRegisteredClientRepository.java
new file mode 100644
index 0000000..ef0e4ed
--- /dev/null
+++ b/src/main/java/se/digg/eudiw/authorization/OidFederatedRegisteredClientRepository.java
@@ -0,0 +1,122 @@
+package se.digg.eudiw.authorization;
+
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
+import org.springframework.util.Assert;
+import se.digg.eudiw.config.EudiwConfig;
+import se.digg.eudiw.service.OpenIdFederationService;
+import se.digg.wallet.metadata.WalletOAuthClientMetadata;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class OidFederatedRegisteredClientRepository implements RegisteredClientRepository {
+
+ private final EudiwConfig config;
+ private final OpenIdFederationService openIdFederationService;
+ private final Map idRegistrationMap;
+ private final Map clientIdRegistrationMap;
+
+ public OidFederatedRegisteredClientRepository(EudiwConfig config, OpenIdFederationService openIdFederationService ) {
+ this.config = config;
+ this.openIdFederationService = openIdFederationService;
+ ConcurrentHashMap idRegistrationMapResult = new ConcurrentHashMap<>();
+ ConcurrentHashMap clientIdRegistrationMapResult = new ConcurrentHashMap<>();
+ this.idRegistrationMap = idRegistrationMapResult;
+ this.clientIdRegistrationMap = clientIdRegistrationMapResult;
+ }
+
+ @Override
+ public void save(RegisteredClient registeredClient) {
+ // no need to save - the client registration is done in federation
+ }
+
+ @Override
+ public RegisteredClient findById(String id) {
+ Assert.hasText(id, "id cannot be empty");
+ return (RegisteredClient)this.idRegistrationMap.get(id);
+ }
+
+ @Override
+ public RegisteredClient findByClientId(String clientId) {
+ Assert.hasText(clientId, "clientId cannot be empty");
+ RegisteredClient client = (RegisteredClient)this.clientIdRegistrationMap.get(clientId);
+ if (client == null) {
+ return buildRegisteredClient(clientId);
+ }
+ return client;
+ }
+
+
+ private RegisteredClient buildRegisteredClient(String clientId) {
+
+ WalletOAuthClientMetadata metadata = openIdFederationService.resolveWallet(clientId);
+
+ MessageDigest digest = null;
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+
+ byte[] shasum = digest.digest(clientId.getBytes(StandardCharsets.UTF_8));
+ String id = new String(shasum, StandardCharsets.UTF_8);
+
+ RegisteredClient.Builder registeredClientBuilder = RegisteredClient.withId(id)
+ .clientId(clientId)
+ .clientAuthenticationMethods(s -> {
+ s.add(ClientAuthenticationMethod.NONE);
+ })
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
+ .authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:pre-authorized_code"));
+
+
+ registeredClientBuilder = registeredClientBuilder.redirectUri(String.format("%s/login/oauth2/code/messaging-client-pkce", config.getIssuerBaseUrl()))
+ .redirectUri(String.format("%s/callback", config.getIssuerBaseUrl()))
+ .redirectUri(String.format("%s/auth-flow", config.getIssuerBaseUrl()))
+ .redirectUri(String.format("%s/callback-demo-auth", config.getIssuerBaseUrl()))
+ .redirectUri(String.format("%s/credentials", config.getIssuerBaseUrl()))
+ .redirectUri(String.format("%s/callback-demo-pre-auth", config.getIssuerBaseUrl()))
+ .redirectUri("com.example.eudiwdemo:/oauthredirect")
+ .redirectUri(config.getIssuerBaseUrl());
+
+ for (String uri : config.getRedirectUris()) {
+ registeredClientBuilder = registeredClientBuilder.redirectUri(uri);
+ }
+
+ registeredClientBuilder = registeredClientBuilder.scope("identitycredential.read")
+ .scope("VerifiablePortableDocumentA1")
+ .scope(OidcScopes.OPENID)
+ .scope(OidcScopes.PROFILE)
+ .clientSettings(ClientSettings.builder()
+ .requireAuthorizationConsent(true)
+ .requireProofKey(true) //Only PKCE is supported
+ .build())
+ .tokenSettings(TokenSettings.builder()
+ .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) // Generate JWT token
+ .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
+ .accessTokenTimeToLive(Duration.ofSeconds(30 * 60))
+ .refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))
+ .reuseRefreshTokens(true)
+ .build())
+ .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build());
+
+
+ RegisteredClient client = registeredClientBuilder.build();
+
+
+ return client;
+ }
+}
diff --git a/src/main/java/se/digg/eudiw/authorization/PreAuthCodeGrantAuthenticationProvider.java b/src/main/java/se/digg/eudiw/authorization/PreAuthCodeGrantAuthenticationProvider.java
index c9225c6..9eb3cec 100644
--- a/src/main/java/se/digg/eudiw/authorization/PreAuthCodeGrantAuthenticationProvider.java
+++ b/src/main/java/se/digg/eudiw/authorization/PreAuthCodeGrantAuthenticationProvider.java
@@ -11,8 +11,6 @@
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -20,7 +18,6 @@
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
-import se.digg.eudiw.context.EudiwSessionSecurityContextRepository;
public class PreAuthCodeGrantAuthenticationProvider implements AuthenticationProvider {
diff --git a/src/main/java/se/digg/eudiw/config/OAuth2ServerConfig.java b/src/main/java/se/digg/eudiw/config/OAuth2ServerConfig.java
index 2f2b861..1525657 100644
--- a/src/main/java/se/digg/eudiw/config/OAuth2ServerConfig.java
+++ b/src/main/java/se/digg/eudiw/config/OAuth2ServerConfig.java
@@ -1,53 +1,30 @@
package se.digg.eudiw.config;
-import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
-import java.time.Duration;
-import java.util.List;
import java.util.UUID;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.proc.SecurityContext;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationEventPublisher;
-import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.session.SessionRegistry;
-import org.springframework.security.core.session.SessionRegistryImpl;
-import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
-import org.springframework.security.oauth2.server.authorization.authentication.*;
import org.springframework.security.oauth2.server.authorization.token.*;
-import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
-import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.*;
-import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
-import org.springframework.security.web.session.HttpSessionEventPublisher;
-import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import se.digg.eudiw.authentication.*;
-import se.digg.eudiw.authorization.OAuth2ParAuthorizationCodeRequestAuthenticationConverter;
-import se.digg.eudiw.authorization.PreAuthCodeGrantAuthenticationConverter;
-import se.digg.eudiw.authorization.PreAuthCodeGrantAuthenticationProvider;
+import se.digg.eudiw.authorization.*;
import se.digg.eudiw.context.EudiwSessionSecurityContextRepository;
import org.springframework.beans.factory.annotation.Autowired;
@@ -61,36 +38,28 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.oauth2.core.oidc.OidcScopes;
-import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
-import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
-import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
-import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.web.SecurityFilterChain;
import com.nimbusds.jose.jwk.source.JWKSource;
+import se.digg.eudiw.service.OpenIdFederationService;
import se.digg.eudiw.service.ParCacheService;
@Configuration
@EnableWebSecurity
public class OAuth2ServerConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(OAuth2ServerConfig.class);
+
@Autowired
private SwedenConnectAuthenticationProvider authProvider;
@Autowired
EudiwSessionSecurityContextRepository contextRepository;
- //@Autowired
- //private ClientRegistrationRepository clientRegistrationRepository;
@Autowired
private EudiwConfig config;
@@ -126,6 +95,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.registeredClientRepository(registeredClientRepository)
+ .tokenGenerator(tokenGenerator())
.oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint
@@ -133,7 +103,6 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
.authenticationProvider(authProvider)
//.authorizationRequestConverters(converters -> converters.addFirst(new ParAuthenticationConverter(parCacheService)))
.errorResponseHandler((req, res, error) -> {
- System.out.println("HJKSHKHDJHSAKDHKSHDJKHAJDHAKSHAKHDKAHSDJ******");
res.getWriter().write("FOOBAR!");
res.setStatus(HttpStatus.OK.value());
})
@@ -223,10 +192,10 @@ public IdProxyRequestBuilder idProxyRequestBuilder() {
}
@Bean
- public RegisteredClientRepository registeredClientRepository() {
-
-
+ public RegisteredClientRepository registeredClientRepository(OpenIdFederationService openIdFederationService) {
+ return new OidFederatedRegisteredClientRepository(config, openIdFederationService);
+/*
RegisteredClient.Builder registeredClientBuilder = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(config.getClientId())
.clientAuthenticationMethods(s -> {
@@ -268,6 +237,8 @@ public RegisteredClientRepository registeredClientRepository() {
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build());
RegisteredClient registeredClient = registeredClientBuilder.build();
return new InMemoryRegisteredClientRepository(registeredClient);
+
+ */
}
public JwtDecoder jwtDecoder(JWKSource jwkSource) {
@@ -291,49 +262,6 @@ public JWKSource jwkSource() {
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
- @Bean
- public OAuth2TokenCustomizer accessTokenCustomizer() {
- return (context) -> {
- Authentication principal = context.getPrincipal();
- if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
- Set authorities = principal.getAuthorities().stream().map(GrantedAuthority::getAuthority)
- .collect(Collectors.toSet());
-
- context.getClaims().claim("authorities", authorities);
-
- // TODO det mesta borde flyttas till idToken eller userinfo någonting
- JwtClaimsSet.Builder claims = context.getClaims();
- if (principal.getPrincipal() instanceof SwedenConnectPrincipal) {
- SwedenConnectPrincipal p = (SwedenConnectPrincipal) principal.getPrincipal();
- if (p.getSubjAttributes().getPersonalNumber() != null) claims.claim("personalNumber", p.getSubjAttributes().getPersonalNumber());
- if (p.getSubjAttributes().getName() != null) claims.claim("name", p.getSubjAttributes().getName());
- if (p.getSubjAttributes().getGivenName() != null) claims.claim("givenName", p.getSubjAttributes().getGivenName());
- if (p.getSubjAttributes().getSurname() != null) claims.claim("surname", p.getSubjAttributes().getSurname());
- if (p.getSubjAttributes().getCoordinationNumber() != null) claims.claim("coordinationNumber", p.getSubjAttributes().getCoordinationNumber());
- if (p.getSubjAttributes().getPrid() != null) claims.claim("prid", p.getSubjAttributes().getPrid());
- if (p.getSubjAttributes().getPridPersistence() != null) claims.claim("pridPersistence", p.getSubjAttributes().getPridPersistence());
- if (p.getSubjAttributes().getEidasPersonIdentifier() != null) claims.claim("eidasPersonIdentifier", p.getSubjAttributes().getEidasPersonIdentifier());
- if (p.getSubjAttributes().getPersonalNumberBinding() != null) claims.claim("personalNumberBinding", p.getSubjAttributes().getPersonalNumberBinding());
- if (p.getSubjAttributes().getOrgNumber() != null) claims.claim("orgNumber", p.getSubjAttributes().getOrgNumber());
- if (p.getSubjAttributes().getOrgAffiliation() != null) claims.claim("orgAffiliation", p.getSubjAttributes().getOrgAffiliation());
- if (p.getSubjAttributes().getOrgName() != null) claims.claim("orgName", p.getSubjAttributes().getOrgName());
- if (p.getSubjAttributes().getOrgUnit() != null) claims.claim("orgUnit", p.getSubjAttributes().getOrgUnit());
- if (p.getSubjAttributes().getUserCertificate() != null) claims.claim("userCertificate", p.getSubjAttributes().getUserCertificate());
- if (p.getSubjAttributes().getUserSignature() != null) claims.claim("userSignature", p.getSubjAttributes().getUserSignature());
- if (p.getSubjAttributes().getDeviceIp() != null) claims.claim("deviceIp", p.getSubjAttributes().getDeviceIp());
- if (p.getSubjAttributes().getAuthnEvidence() != null) claims.claim("authnEvidence", p.getSubjAttributes().getAuthnEvidence());
- if (p.getSubjAttributes().getCountry() != null) claims.claim("country", p.getSubjAttributes().getCountry());
- if (p.getSubjAttributes().getBirthName() != null) claims.claim("birthName", p.getSubjAttributes().getBirthName());
- if (p.getSubjAttributes().getPlaceOfbirth() != null) claims.claim("placeOfbirth", p.getSubjAttributes().getPlaceOfbirth());
- if (p.getSubjAttributes().getAge() != null) claims.claim("age", p.getSubjAttributes().getAge());
- if (p.getSubjAttributes().getBirthDate() != null) claims.claim("birthDate", p.getSubjAttributes().getBirthDate());
- }
-
- }
- };
- }
-
-
static class Jwks {
private Jwks() {
@@ -371,7 +299,7 @@ static KeyPair generateRsaKey() {
@Bean
public OAuth2TokenGenerator> tokenGenerator() {
JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource());
- JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
+ EudiwJwtGenerator jwtGenerator = new EudiwJwtGenerator(jwtEncoder);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
diff --git a/src/main/java/se/digg/eudiw/controllers/CredentialController.java b/src/main/java/se/digg/eudiw/controllers/CredentialController.java
index b219dcf..6fed76f 100644
--- a/src/main/java/se/digg/eudiw/controllers/CredentialController.java
+++ b/src/main/java/se/digg/eudiw/controllers/CredentialController.java
@@ -5,6 +5,9 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.nimbusds.jose.jwk.JWK;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -28,6 +31,9 @@
@RestController
public class CredentialController {
+
+ private static final Logger logger = LoggerFactory.getLogger(CredentialController.class);
+
private final EudiwConfig eudiwConfig;
private final SignerConfig signerConfig;
private final OpenIdFederationService openIdFederationService;
@@ -68,7 +74,8 @@ String credential(@AuthenticationPrincipal Jwt jwt, @RequestBody CredentialParam
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof Jwt) {
- WalletOAuthClientMetadata walletOAuthClientMetadata = openIdFederationService.resolveWallet("1234567891");
+ String clientId = jwt.getClaim("clientId");
+ WalletOAuthClientMetadata walletOAuthClientMetadata = openIdFederationService.resolveWallet(clientId);
Optional jwk;
if (walletOAuthClientMetadata != null) {
jwk = walletOAuthClientMetadata.getJwkSet().getKeys().stream().findFirst();
@@ -80,11 +87,17 @@ String credential(@AuthenticationPrincipal Jwt jwt, @RequestBody CredentialParam
// TODO - get PID data from ID token and authentic source (t.ex. skatteverket)
PidBuilder builder = new PidBuilder(eudiwConfig.getIssuer(), signerConfig)
.withExp(eudiwConfig.getExpHours())
- .withVcType("https://attestations.eudiw.se/se_pid")
- .addSelectiveDisclosure("given_name", "FOO" + jwt.getClaim("givenName"))
- .addSelectiveDisclosure("last_name", "FOO" + jwt.getClaim("surname"))
- .addSelectiveDisclosure("birthdate", "FOO" + jwt.getClaim("birthDate"))
- .addSelectiveDisclosure("address", new Address("123 Main St", "Anytown", "Anystate", "US"));
+ .withVcType("https://attestations.eudiw.se/se_pid");
+
+
+ if (!StringUtils.isBlank(jwt.getClaim("givenName")))
+ builder.addSelectiveDisclosure("given_name", jwt.getClaim("givenName"));
+ if (!StringUtils.isBlank(jwt.getClaim("surname")))
+ builder.addSelectiveDisclosure("last_name", jwt.getClaim("surname"));
+ if (!StringUtils.isBlank(jwt.getClaim("birthDate")))
+ builder.addSelectiveDisclosure("birthdate", jwt.getClaim("birthDate"));
+
+ builder.addSelectiveDisclosure("address", new Address("123 Main St", "Anytown", "Anystate", "US"));
jwk.ifPresent(value -> builder.withCnf(Map.of("jwk", value.toPublicJWK().toJSONObject())));
diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml
index ddce88d..0163d1e 100644
--- a/src/main/resources/application-dev.yaml
+++ b/src/main/resources/application-dev.yaml
@@ -42,7 +42,8 @@ eudiw:
authHost: https://local.dev.swedenconnect.se:9090
credentialHost: https://local.dev.swedenconnect.se:9090
expHours: 24
- clientId: eudiw-client
+ clientId: 1234567891
+ #eudiw-client
openidFederation:
baseUrl: https://local.dev.swedenconnect.se:9040/oidfed
trustMarkId: https://local.dev.swedenconnect.se/trust-mark-id/pid-issuer