diff --git a/api-gateway/src/main/resources/application.yml b/api-gateway/src/main/resources/application.yml index 365add9..26695e6 100644 --- a/api-gateway/src/main/resources/application.yml +++ b/api-gateway/src/main/resources/application.yml @@ -1,14 +1,17 @@ server: port: 8989 shutdown: graceful + spring: application: name: api-gateway + cloud: gateway: discovery: locator: enabled: true + globalcors: cors-configurations: '[/**]': @@ -16,6 +19,7 @@ spring: allowed-methods: "*" allowed-headers: "*" allow-credentials: false + routes: - id: catalog-service uri: ${CATALOG_SERVICE_URL:http://localhost:8081} @@ -23,28 +27,34 @@ spring: - Path=/catalog/** filters: - RewritePath=/catalog/?(?.*), /${segment} + - id: orders-service uri: ${ORDER_SERVICE_URL:http://localhost:8082} predicates: - Path=/orders/** filters: - RewritePath=/orders/?(?.*), /${segment} + - id: openapi uri: http://localhost:${server.port} predicates: - Path=/v3/api-docs/** filters: - RewritePath=/v3/api-docs/?(?.*), /${segment}/v3/api-docs + default-filters: - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Methods Access-Control-Allow-Headers RETAIN_FIRST + mvc: problemdetails: enabled: true + management: endpoints: web: exposure: include: "*" + springdoc: swagger-ui: use-root-path: true diff --git a/catalog-service/src/main/resources/application.properties b/catalog-service/src/main/resources/application.properties index 9da9245..5214b13 100644 --- a/catalog-service/src/main/resources/application.properties +++ b/catalog-service/src/main/resources/application.properties @@ -1,11 +1,15 @@ +# Application Configuration spring.application.name=catalog-service +# Server Configuration server.port=8081 server.shutdown=graceful +# Management Endpoints management.endpoints.web.exposure.include=* management.info.git.mode=full +# Database Configuration spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:15432/postgres} spring.datasource.username=${DB_USERNAME:postgres} spring.datasource.password=${DB_PASSWORD:postgres} @@ -13,4 +17,5 @@ spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=true spring.jpa.open-in-view=false +# Catalog Service Configuration catalog.page-size=10 diff --git a/deployment/compose.yml b/deployment/compose.yml index c0b0308..9cbafca 100644 --- a/deployment/compose.yml +++ b/deployment/compose.yml @@ -1,5 +1,6 @@ name: 'bookstore' services: + # For catalog-service to work, add 127.0.0.1 catalog-service to /etc/hosts catalog-service: image: azdanov/bookstore-catalog-service container_name: catalog-service @@ -20,6 +21,8 @@ services: resources: limits: memory: 700m + + # For order-service to work, add 127.0.0.1 order-service to /etc/hosts order-service: image: azdanov/bookstore-order-service container_name: order-service @@ -35,6 +38,7 @@ services: - RABBITMQ_PORT=5672 - RABBITMQ_USERNAME=guest - RABBITMQ_PASSWORD=guest + - OAUTH2_SERVER_URL=http://keycloak:9191 ports: - "8082:8082" restart: unless-stopped @@ -47,6 +51,7 @@ services: resources: limits: memory: 700m + notification-service: image: azdanov/bookstore-notification-service container_name: notification-service @@ -77,6 +82,8 @@ services: resources: limits: memory: 700m + + # For api-gateway to work, add 127.0.0.1 api-gateway to /etc/hosts api-gateway: image: azdanov/bookstore-api-gateway container_name: api-gateway @@ -93,6 +100,7 @@ services: resources: limits: memory: 700m + webapp: image: azdanov/bookstore-webapp container_name: webapp @@ -101,6 +109,7 @@ services: environment: - SPRING_PROFILES_ACTIVE=docker - BOOKSTORE_API_GATEWAY_URL=http://api-gateway:8989 + - OAUTH2_SERVER_URL=http://keycloak:9191 ports: - "8080:8080" restart: unless-stopped @@ -108,6 +117,7 @@ services: resources: limits: memory: 700m + catalog-db: image: postgres:16-alpine container_name: catalog-db @@ -128,6 +138,7 @@ services: resources: limits: memory: 500m + orders-db: image: postgres:16-alpine container_name: orders-db @@ -148,6 +159,7 @@ services: resources: limits: memory: 500m + bookstore-rabbitmq: image: rabbitmq:3.12.11-management container_name: bookstore-rabbitmq @@ -168,6 +180,7 @@ services: resources: limits: memory: 500m + notifications-db: image: postgres:16-alpine container_name: notifications-db @@ -188,6 +201,7 @@ services: resources: limits: memory: 500m + mailpit: image: axllent/mailpit container_name: mailpit @@ -196,3 +210,27 @@ services: ports: - 8025:8025 - 1025:1025 + deploy: + resources: + limits: + memory: 100m + + # For Keycloak to work, add 127.0.0.1 keycloak to /etc/hosts + keycloak: + image: quay.io/keycloak/keycloak:24.0.4 + profiles: + - infra + command: [ 'start-dev', '--import-realm', '--http-port=9191' ] + container_name: keycloak + hostname: keycloak + volumes: + - ./keycloak:/opt/keycloak/data/import + environment: + - KEYCLOAK_ADMIN=admin + - KEYCLOAK_ADMIN_PASSWORD=admin + ports: + - "9191:9191" + deploy: + resources: + limits: + memory: 2gb diff --git a/deployment/keycloak/bookstore-realm.json b/deployment/keycloak/bookstore-realm.json new file mode 100644 index 0000000..a4c8b90 --- /dev/null +++ b/deployment/keycloak/bookstore-realm.json @@ -0,0 +1,1927 @@ +{ + "id" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "realm" : "bookstore", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "43273753-bab6-4292-a25f-9092d3cb9331", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "attributes" : { } + }, { + "id" : "92ff70a5-7f6e-4f3e-a0b3-5f41a86d1824", + "name" : "default-roles-bookstore", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "attributes" : { } + }, { + "id" : "f6f78c38-43c5-47b3-a18c-1123340fe4f2", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "6d798e56-6d05-498a-8dbb-c1b895579391", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "cdaecae4-fee6-4dab-a7db-38f1abc6f32e", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "fc063e96-aa8d-434a-848b-807afe0926a7", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "c2f19f50-ca4a-4190-8956-81bc10e24356", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "f788fc1b-f470-49f7-8d3e-a398dc56e172", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "1409542d-0123-4626-bfd2-351a69194335", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "b2fbda88-f9b1-4c70-866b-fc52bbca7aed", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "dbe27a6e-5f7a-40b4-b469-e8a9d527cbd3", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "47ae5c38-c3b2-4fc0-87bd-e6b9f5fabe8d", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "1e459d5f-7e2c-4e40-b93a-65a84120bf1e", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "8872c0d6-5c5e-445d-8894-01682e7f05e2", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "862015d5-29d6-4024-a54e-81fddc69555c", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "30cd142d-0033-40f9-85f5-0706561da1fa", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "ef3207ec-bfd9-43c1-ae5e-23fd4ced270d", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "985e3850-84ea-494d-ae4d-e9d1972bed4d", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "16800317-78b4-4f9a-b73c-e128a287ee9f", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "18a42192-7d8e-461c-aafc-6b155834df19", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "e7aa784e-48de-421b-970e-420f694b8724", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "f7b281d9-7a6f-4451-aaca-000918e6f292", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-realms", "query-users", "manage-events", "view-authorization", "view-realm", "impersonation", "view-identity-providers", "query-clients", "manage-identity-providers", "manage-clients", "query-groups", "view-clients", "view-users", "view-events", "manage-realm", "manage-authorization", "manage-users", "create-client" ] + } + }, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "bookstore-webapp" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "02f1701d-813b-4770-a7cb-adcb74b5c724", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "336bf23b-ace7-408d-8d28-6a9d46e05ab8", + "attributes" : { } + } ], + "account" : [ { + "id" : "0c4ecd80-08bf-4212-ae6d-2f70740c8c1f", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "3e124c1a-3d27-405f-a1b4-aeab3a14b3b3", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "90bf93b6-f4fc-4f6b-b5c3-0bcc50045791", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "89a2f5b1-1141-4ec4-a6ee-36dcbaa59795", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "5ee3cb01-3fab-47b3-8996-2c1dbe0f9897", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "ff46c2f7-e863-4a53-a3f4-4274e70d237d", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "0eea8cd2-1ef4-4505-b12d-16603160a754", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "c0eebf2d-0287-4359-bc19-5ea21cf0256f", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "92ff70a5-7f6e-4f3e-a0b3-5f41a86d1824", + "name" : "default-roles-bookstore", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "users" : [ { + "id" : "b6d13f59-de3f-4cea-85a4-814b5f187efc", + "username" : "anton", + "firstName" : "Anton", + "lastName" : "Zdanov", + "email" : "anton@zdanov.dev", + "emailVerified" : true, + "createdTimestamp" : 1713932815276, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "8da0d5c7-eddc-4e98-968c-e2a769c335a2", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1715530098371, + "secretData" : "{\"value\":\"2NjmRd09vNLmNOsxMlf2hLbuedwWIHaEXcS/9h3tkCNEqGrX72pvihp8jJXql6bBG4H2GiQoTZ7orERh3fui6Q==\",\"salt\":\"YY10c+SzEMVrCErJGe+xdQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-bookstore" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "362b5bb3-0e0a-4ee6-9b59-53e2ca1adeac", + "username" : "azdanov", + "firstName" : "Anton", + "lastName" : "Zdanov", + "email" : "anton@azdanov.dev", + "emailVerified" : true, + "createdTimestamp" : 1713932760828, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "f228b1f9-4426-49f2-a294-a4b11c6500cb", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1715530110509, + "secretData" : "{\"value\":\"mrZFtlyCmedeDQHG4ycqQx1s0Y6WSKdXzir0Vr37bDfnNlEfYlESUe20EbKUIocRL+BkZ+vyGUMZNleL/O/8MA==\",\"salt\":\"tQEoDRZNWG/OdYXo84WIXA==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-bookstore" ], + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/bookstore/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/bookstore/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "eb5db48a-a3f6-447a-90a2-9c21b21630b9", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/bookstore/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/bookstore/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "eee6ed78-86a6-42b7-9780-8bee93bc52b6", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "388746c3-146a-4608-8140-ea63b8a0afe8", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "cae03f1a-94cd-448c-90d0-259e87c0ab59", + "clientId" : "bookstore-webapp", + "name" : "Bookstore", + "description" : "", + "rootUrl" : "http://localhost:8080", + "adminUrl" : "http://localhost:8080", + "baseUrl" : "http://localhost:8080", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "P1sibsIrELBhmvK18BOzw1bUl96DcP2z", + "redirectUris" : [ "http://localhost:8989/webjars/swagger-ui/oauth2-redirect.html", "http://localhost:8080/login/oauth2/code/bookstore-webapp" ], + "webOrigins" : [ "http://localhost:8080", "http://localhost:8989" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1713932702", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "http://localhost:8080", + "oauth2.device.authorization.grant.enabled" : "false", + "display.on.consent.screen" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "f606bfbb-610c-4969-a174-789fa7b208a2", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "0f3a5547-8a62-42d7-8840-ebcb49abf69e", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "414aaef3-820b-4d9c-a931-c388f23b87fd", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "336bf23b-ace7-408d-8d28-6a9d46e05ab8", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "ddaefabd-c000-4a8b-98ea-e5eb6c992a1d", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/bookstore/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/bookstore/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "3f0a4f78-0f5b-4d6b-90f8-7eb6b8c4da11", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "cfb1a11f-2ffe-4b31-90b0-c410ca1e3beb", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "315d0814-5a8a-44f6-8610-66bd2c526c7d", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "1cfede7a-5db5-4e38-8be9-c57bfa285385", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String" + } + }, { + "id" : "45594d8a-ec78-44c9-95a4-9bd36aba4423", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "f8f4b27b-edb9-4136-ab45-de6f90ac12dc", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "21c9df62-f4e0-43d8-8d91-076d65286548", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "0961ee05-4b2f-4f5b-870e-39654c7bd950", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "d8052c93-a2f3-433e-91e1-d4fd0ebe11bb", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "97643e26-2d63-4699-aec9-b53bd303a9ac", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "bf5e0bcb-fc3d-42b0-99e0-8e5dbd228da4", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "414252cb-43fc-4ba2-9240-9bd87ac6eebb", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "14627a60-1a8e-4a2f-bc6d-b50f8457fef7", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "fcd6b286-667f-45cb-bb34-61d5be62728a", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "d441c11d-c5db-4993-b382-8c7ef720724c", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "4daac834-93bc-4143-8644-3242547f224b", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "9e51a5f9-fc70-4a14-8534-734810ad2452", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "84a0ac3a-a458-438a-8e12-3e85d8ab1740", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "7d5531c7-4a47-4b6c-a42e-8a82e04bb69a", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "0f347420-c1e5-44a5-baa3-89f1ed17b9c0", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "5abd8354-ec66-4f23-b447-6424d6d669c0", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "9c346e30-d1b2-4297-8a59-7ec2862fe7f1", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "ea8d58b6-ccdd-4c68-98f2-e57a3c8f097c", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "4f5b206a-cde1-4503-be00-f1f85333c7fb", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "0e45624b-fc58-4d07-9b09-4d6b8603e3f8", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "11ec387e-48e6-4478-8dfa-93f4d406a00a", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "8b4d55ff-99bf-4e8d-afc7-74d41934b9f3", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "79cf6096-fce1-44ea-9438-ca25e81b9005", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "6762c33a-e313-4c63-af6e-66c87581ccc1", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "aa13a0bc-7d99-44a8-9a89-6399f59bd8dd", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "4d0f7201-0064-4dea-a828-12bb5aa6c0ec", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "dd3815ca-93b9-409d-9e05-f0213eae52a0", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "f7d6b611-0c1e-4fa4-a7f6-979310a388e8", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "e5bbe4cd-c6e3-40eb-a8e6-b9de521f7c90", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "23e96991-df81-44e7-9a17-7e4db7972480", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + }, { + "id" : "a124c30e-bf74-452f-a65e-cf33ef29dda5", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "358f6efb-3a42-473d-b50f-bfeb240f6127", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "534166af-5b1b-4944-8a72-fab7d1900f3a", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "238a46bc-e29c-489d-8ce6-1392d45ad57f", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper" ] + } + }, { + "id" : "964f7766-4f2c-4107-9f5a-f6afc964cb23", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "1bbb3377-76a9-47f1-96e4-83b864f765e3", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "5de966b0-da21-425e-806f-1b065a37d946", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper" ] + } + }, { + "id" : "5f8f9fff-2279-4840-ae9f-83e3b36fd9c9", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "73e652e0-a743-40bf-ad71-3e0abd1d2e9e", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "a833d514-aee3-46fc-981b-b15ef00fef35", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "4f0f5137-4c9f-4682-87c7-0841ec903a8c", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "1fd29724-b5e9-451f-9f2a-58aa511af3ef", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "c1a8b75c-70f4-4ddd-a5ab-db5c3faea0ab" ], + "secret" : [ "9AGobYatVwpMiZpMVuHwCWrnrjI4EkjGtGspeyJ7jXcFvHbsI8DDQAbh3jnj_H1SJSyI4XO5w5y8hUecpLO3Ox0S7LryI_yS-3S_x7doRofS21zZqSN9FGCZpZlT2cGpOV2aO2Idj_3MAmJhPyB7tPaPk1xJS-tFeWprkYMgZTs" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "96db5738-ed32-4461-abab-ab0712de2361", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEogIBAAKCAQEAl5AvYbXZoa+dt3HKr/wa2QQaV1lraVEbrhKcKaD4mFz3Kau47XLKLBXBIkMhD9LKCx0Jyu7tZy1gLzXKx7m5ygwtD/jvOrnFZ3PYiDCrgHO83NhxYTzKdBSiupWt7X7Ss0keclonGE7L01ZS5ytU15IuofqxrYEO2nbp1mlpUAzjy2ZIX+IEAA8VCHK5dvlmZgiZYic6N5+WkHGqu5DgKkVPyT+h3jhzYpshwmqXW+KtgKrQ2J6PrS/K2apBBq3HHM4s5X//QgEP8ehcTr2eEkqwuaRuVPgjaAdqkAw6VTInE9hiZRhHPHDKvQsA2ze3JTucm+fA9Ng4yvPTxLPowwIDAQABAoIBAARJCl3r41xj98ShDgcNzMCIEiqYF6ZhqbOxSQMQTxcXwdFGzrLABSFb+Br85Olqn1XTG1zGhqXdLZQIJFSjVrJ0x61k73asm/1B1ER9g8vc+eEQqZwDOQIxNyypYMKPTGvPTHIVRHe2eqLeFPf+TKbPtkvUJ8PtGztoE1KBpn2h5IdSqsBp9VsuCUSU67hgJ7C10HQSqm7LAKbioz661MK+h+7o5RbopM6YGKIQeqdWJbTiNs7DtvZwHOm/mRIDC222NCFPNMNuUoTGNDwlJ2P5YNN4svWWHPn2snamaV/zK4z7IGxVfVmG7waN4Tg4B8spbrQrLNSrA6eAj572Z0kCgYEAxlNyYS1W/JaFCI+tVBgbbKifkeiyBNJYQ6iCUSehJezBICK0Tq8C5YYYD35hJrRuDeBacbUBhO6ic/jUDvkzz3jVMnag5Ye5gwXJl20GEXMuWvJOjkSNsBM2dROxmUR5HqkgapGoF3Dv84GWgt685eiD3Gg2V08/rszi0QOXw60CgYEAw6NxwF1pNoD4pgOXEpM+rKsarE7jzOM3GSxIBukqyi/MfEPtAArpVQwM6wH7zT41noRHkokVsDtu5ZxhNC3J5douDSa1XioI1drARS3JTBX0kwB1c/ztERfMEIQboJnVsyeexKUrJzogMctJa1JgkPgGGlVrXlcB6AETewOybC8CgYAexen0jC14Kf7gcjLJ3qe6t3tzD3E38E+DyAzYfNazZMYer2Pc5akEZkcreEeo0sxU2OAPzq86qjCI4C4/WImigrRKDUAv/VxaqTY7ic4sPPwPXpOz3Y1CqeiESBcBmi9VA5clEg7L/FkvJodtLRSQdACp3X25FHHkNKRpHC6bTQKBgBCpAY3dJpGSqBoTIjFdwq+Xnpzf1vxiuRV2tOeJwbAYI/TNIAip5Tbt4Me+kn/m9U/7edDbxbINTaNWDt0PrSrayqMqEbXkJH55s3fwrzzAfeRq3JW1C0cyXJlndqbVkuI/1IBwhF1B0O4MnsL57+lIhdSGQ69z/2CPfuYx6WbXAoGAWE1ikNs6ujz7aTnaPwPK9bCxO8tEsk5qm34R4otIq+GIeaCjia5zCbXJPXpENJPBQgsCH1fHxT/Nt4CZaECE+3Ibg8AuNXnfsVMv8QbnDS3JtQI5RT8n0cnnZd+8c5ioI9QAgR2Hv0hD4cFtfjbzs6QUNSOBH+VEmiox1PYMeYs=" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICoTCCAYkCBgGPDlWStjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlib29rc3RvcmUwHhcNMjQwNDI0MDQxOTAwWhcNMzQwNDI0MDQyMDQwWjAUMRIwEAYDVQQDDAlib29rc3RvcmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXkC9htdmhr523ccqv/BrZBBpXWWtpURuuEpwpoPiYXPcpq7jtcsosFcEiQyEP0soLHQnK7u1nLWAvNcrHubnKDC0P+O86ucVnc9iIMKuAc7zc2HFhPMp0FKK6la3tftKzSR5yWicYTsvTVlLnK1TXki6h+rGtgQ7adunWaWlQDOPLZkhf4gQADxUIcrl2+WZmCJliJzo3n5aQcaq7kOAqRU/JP6HeOHNimyHCapdb4q2AqtDYno+tL8rZqkEGrccczizlf/9CAQ/x6FxOvZ4SSrC5pG5U+CNoB2qQDDpVMicT2GJlGEc8cMq9CwDbN7clO5yb58D02DjK89PEs+jDAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAImZccBZzONa1A8X/2yK7KBZH/R3yFOjnDDjb3w2ewF/R63UFiqg++E0NLXNZ8RRSLUVmP81eaoxB6jbyOEvOUDb2CERQSHZ/CMA/DMahPSURKfbG90FlGqH/wuDUyExI+gKoCql30+4pZGx9f0ltAjzv7s8kd8ED/zXmQQ+cRJ7lSx3EP+kHXwfN0/5uvn+8z6JMz2uukaZ3bgwWKEhB3Fq3qAchNzdsg/QWh+H4RCFAuNOqZ/w5osuQwMnNv6JAhfs9kq+fOFQfet+kbSS2eD9KnSwj/M7xkzTCPTCcKtENU52Sa8ss10BNn4EdDfFcI4Pp4LEtdSzbFeq0JVqhUQ=" ], + "priority" : [ "100" ] + } + }, { + "id" : "94b5df69-4b72-4c71-9a79-d2a6dea53e7f", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "9b5b06f5-440d-47d3-a8d5-93886095fc34" ], + "secret" : [ "bKrTHv_hzWSy95b1-aWFDQ" ], + "priority" : [ "100" ] + } + }, { + "id" : "fedc6aa1-0f9e-4d18-ba68-645d8bddbca1", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAzYSKXFgaqCnJTOPQy9qa5i713BEnVcnFgVP7wNCSo1hFKVMaSF9gBAy5L7TFptgy3jmMZgvCCqxWjmxkUXy/qAKNsL7aWFbez9qypTTyloSilDnf2FAxIsORa7IB+LJq7vryIBZfvhgykBMYwEQV/Thrc/TtivobdWZyxIr9xn4FtzwuslgXeVnd+6PxmMkrh9TcZx5jxyUNUvO/n5Px4Hf4tO8auZHb4ZkR3XXMczjbQk4IOJuPEDFwwgUGw1JzgUFIFChhCn5a7FYfxmQgmIH8SMnYFykr8/1eGMNcLkkVNrHja325z+sb2XWrhHmZTSvc0XsuXn+yw1eBgzWpkQIDAQABAoIBABVskg9dFF9QJtbT/2PG3MOu5Mydgaoa4/LOzlVb8NNkG+aaTgi6H4xghqwLVljlnAzoNyN6qY5bB5VmhYEspd6V8I8aoDCaErVcjSe1eK358uhMjbITLwIanMXbK0Mxlz3lLVkst+udQmUcRoXheyDX8TSn9VzZ5IOlK8TjH+AhekyP/RvMx5J71q6zBcfVrfxdYRHdilPw0GOHBPr8XBy3uvtl0r1Y9vucl6vpbpLIoeKy3MqpVh5bqr0Dc7jIrfhvD79gs22cRstUKTvWARNtvO+YGNc7nO9J9aDsRMgwgsryvRP0np4obkbKa//3ggtG0Y8rwPuQSdyZkgSetDkCgYEA+LX7TnCMTntjrWKFoHrn3MDd0JJIPap8kCsd5zyiuDDq+4UFi97DsuDe4pyvXV4sC4OjKYWsyOXQ+qSxOhYyccqK5fqCKPFRlot5nKTsppmjaLnAn/vmpYTjeSlgqFaXhw0NPCu+gNQdb3mFITaDrDgHPgCkV8+pz/rR/iaMWCkCgYEA04p9tYiR1fAa89Kf6swI3/j6+Uro1JuPJCS+IVfXuiAe6fgJJCrNxegl3LQOhlgJWmU1j7Qhte2uX/i4UmJYW92drZV9U4xssTUx3qjRjnK4IsRG53vW59g3lFRB/KpFnWpBu4S5dDTFripDhOh7q6f9QhRAqmoCeZdWkjK8kykCgYBwkP0jLXFzfAlLSlIspjQXB/vdELg9oNIr9jfclucW8p0IJdayzapJZdm7Oytr2NmahIPuoR/o9Ys2RiAoqC9ArWlcWZtbqI1C0/HA4ixx4wvAtIHoZvynYZQLHJ0w07BVYaTh3PKmIFsKPzfwJlpGMj2k5Gl9jPHQvjcZDp1qsQKBgGXagcqdVjKfKCsL1+nSkdddZ2IO2mo7+EDb3Bvr0rgQbTIUNjqOzYMj0s8gdvnie6ZlotNtVOnm5hXZiM5KAedBnoEMwEoxC8iLJ1bhC/09aUF8JfOhIgmBhslJhHgmd9xiZAHo1u6H1kKUoOvmkF1HRl7Bp50l68klwA4mlbt5AoGBAM1hrV1tci3n1CoN5kum/rMDntaTWEATk4MLx9gwcHX45KPo3mmU2W05F7zlfkzPBQrblxnUfICsXxYnSupbDOdMeM8/WQEbvRxzgFiKfq4k8EKvsqxXOsbaQVY/zY/N5xQ01UfTkqrFCL67S5b43kZsEPer8iEYH8m5Hz0w6tGr" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICoTCCAYkCBgGPDlWT6TANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlib29rc3RvcmUwHhcNMjQwNDI0MDQxOTAwWhcNMzQwNDI0MDQyMDQwWjAUMRIwEAYDVQQDDAlib29rc3RvcmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNhIpcWBqoKclM49DL2prmLvXcESdVycWBU/vA0JKjWEUpUxpIX2AEDLkvtMWm2DLeOYxmC8IKrFaObGRRfL+oAo2wvtpYVt7P2rKlNPKWhKKUOd/YUDEiw5FrsgH4smru+vIgFl++GDKQExjARBX9OGtz9O2K+ht1ZnLEiv3GfgW3PC6yWBd5Wd37o/GYySuH1NxnHmPHJQ1S87+fk/Hgd/i07xq5kdvhmRHddcxzONtCTgg4m48QMXDCBQbDUnOBQUgUKGEKflrsVh/GZCCYgfxIydgXKSvz/V4Yw1wuSRU2seNrfbnP6xvZdauEeZlNK9zRey5ef7LDV4GDNamRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABWk+BtO2Lboq5JSA3zYSNwZpXjJ1Tpr9e+p3lzTZQbtu/hOLkl0faPUipnOnJp/UWJu3MKGFHy1oaZIUbt/E/G8SESnH7HufU8nRZ5kVJHOm8YMK+94Stak4Y6Pgz/YKJmWqqfo4qt8LMyMOH7HNTs3/J/MAkaTNTlopJbMLnoXWBF5ly5qHAu3zHcYUI3mive67GUV1Z9vK+Z9JVMiqSeiW8iMtG4GXKdrgsC5Jb4uAGc6vjxKUJQipvBrBiqzST/2O+8CDWYbzoPsZZM8HFMC1tAbIjlagtOVPCt0oFuv6Tr2hN7zRebU7zSSuh9d9X8mJVTTYq91bs13TR+s+C4=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "76cac48b-ee9e-431b-a5b4-35e09e8d17ed", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "bad7d4b6-f303-49b2-85e0-30302305927e", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "22c85f9c-5c6a-4a7e-b2cd-0fa40de2fc38", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "69a9fa80-2c5a-4c8b-aaca-aa79eca8eec0", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "cc07bdd0-43f6-4175-a0d8-f2d8c387f19c", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "99afbaa9-82b3-4a69-addc-cc711010a1ba", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "a29e71ce-81ee-488e-86e4-16aa4901d029", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "b1a82149-7707-4f86-b956-7ddd207db78a", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "92b6c9b6-24e2-4903-bc3f-b2dd3b87d8e0", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "cc5dd9a2-4586-419a-9c99-7b0a819cb93a", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "dc4a8767-409f-4dbd-95a8-1f823daa47c9", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "268720bb-2b3d-4d03-be6f-09cba97cbfd2", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "7dfa8c67-01c2-4256-bc6a-fb3e77927181", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "37fe7d53-72fb-4dcb-98db-83027408e1f9", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "e0066f2b-0f29-4875-9a67-76c4d32cd1c0", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "05226b4e-c428-4d39-b96a-106ee5dc49a9", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "fb25ee9a-e841-47c4-a07d-ee9e7e20f58a", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "dba15a69-343b-46ea-b924-12c6ffe0a218", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "3d24a56c-9d8b-4e7f-9024-a28e7f27b28c", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "6839bbe3-e1c0-44df-8a27-351c16771596", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DeviceCodeLifespan" : "600", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false" + }, + "keycloakVersion" : "24.0.4", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/notification-service/src/main/resources/application.properties b/notification-service/src/main/resources/application.properties index 6463394..2112385 100644 --- a/notification-service/src/main/resources/application.properties +++ b/notification-service/src/main/resources/application.properties @@ -1,9 +1,15 @@ +# Application Configuration spring.application.name=notification-service + +# Server Configuration server.port=8083 server.shutdown=graceful + +# Management Endpoints management.endpoints.web.exposure.include=* management.info.git.mode=full +# Notification Service Configuration notification.order-events-exchange=orders-exchange notification.new-orders-queue=new-orders notification.delivered-orders-queue=delivered-orders @@ -11,17 +17,20 @@ notification.cancelled-orders-queue=cancelled-orders notification.error-orders-queue=error-orders notification.support-email=anton@example.com +# Database Configuration spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:35432/postgres} spring.datasource.username=${DB_USERNAME:postgres} spring.datasource.password=${DB_PASSWORD:postgres} spring.jpa.open-in-view=false spring.jpa.show-sql=true +# RabbitMQ Configuration spring.rabbitmq.host=${RABBITMQ_HOST:localhost} spring.rabbitmq.port=${RABBITMQ_PORT:5672} spring.rabbitmq.username=${RABBITMQ_USERNAME:guest} spring.rabbitmq.password=${RABBITMQ_PASSWORD:guest} +# Mail Configuration spring.mail.host=${MAIL_HOST:127.0.0.1} spring.mail.port=${MAIL_PORT:1025} spring.mail.username=${MAIL_USERNAME:PLACEHOLDER} diff --git a/order-service/pom.xml b/order-service/pom.xml index 0269d4e..ec0bd73 100644 --- a/order-service/pom.xml +++ b/order-service/pom.xml @@ -23,10 +23,11 @@ 5.13.0 2.43.0 2.44.0 - 4.5.1 + 4.6.0 3.5.4 1.0-alpha-13 azdanov/bookstore-${project.artifactId} + 3.3.1 @@ -54,6 +55,14 @@ org.flywaydb flyway-core + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.springframework.boot + spring-boot-starter-security + io.github.resilience4j resilience4j-spring-boot3 @@ -101,6 +110,11 @@ spring-boot-starter-test test + + org.springframework.security + spring-security-test + test + org.springframework.boot spring-boot-testcontainers @@ -149,6 +163,12 @@ ${wiremock-testcontainers.version} test + + com.github.dasniko + testcontainers-keycloak + ${testcontainers-keycloak.version} + test + @@ -217,5 +237,12 @@ + + + maven_central + Maven Central + https://repo.maven.apache.org/maven2/ + + diff --git a/order-service/src/main/java/dev/azdanov/orderservice/config/OpenAPI3Configuration.java b/order-service/src/main/java/dev/azdanov/orderservice/config/OpenAPI3Configuration.java new file mode 100644 index 0000000..ab74f3a --- /dev/null +++ b/order-service/src/main/java/dev/azdanov/orderservice/config/OpenAPI3Configuration.java @@ -0,0 +1,49 @@ +package dev.azdanov.orderservice.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.OAuthFlow; +import io.swagger.v3.oas.models.security.OAuthFlows; +import io.swagger.v3.oas.models.security.Scopes; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import java.util.List; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +class OpenAPI3Configuration { + + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") + String issuerUri; + + @Value("${swagger.api-gateway-url}") + String apiGatewayUrl; + + @Bean + OpenAPI openApi() { + return new OpenAPI() + .info(new Info() + .title("Order Service APIs") + .description("BookStore Order Service APIs") + .version("v1.0.0") + .contact(new Contact().name("azdanov").email("anton@azdanov.dev"))) + .servers(List.of(new Server().url(apiGatewayUrl))) + .addSecurityItem(new SecurityRequirement().addList("Authorization")) + .components(new Components() + .addSecuritySchemes( + "security_auth", + new SecurityScheme() + .in(SecurityScheme.In.HEADER) + .type(SecurityScheme.Type.OAUTH2) + .flows(new OAuthFlows() + .authorizationCode(new OAuthFlow() + .authorizationUrl(issuerUri + "/protocol/openid-connect/auth") + .tokenUrl(issuerUri + "/protocol/openid-connect/token") + .scopes(new Scopes().addString("openid", "openid scope")))))); + } +} diff --git a/order-service/src/main/java/dev/azdanov/orderservice/config/SecurityConfig.java b/order-service/src/main/java/dev/azdanov/orderservice/config/SecurityConfig.java new file mode 100644 index 0000000..bb249a5 --- /dev/null +++ b/order-service/src/main/java/dev/azdanov/orderservice/config/SecurityConfig.java @@ -0,0 +1,29 @@ +package dev.azdanov.orderservice.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CorsConfigurer; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(c -> c.requestMatchers("/actuator/**", "/v3/api-docs/**") + .permitAll() + .anyRequest() + .authenticated()) + .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .cors(CorsConfigurer::disable) + .csrf(CsrfConfigurer::disable) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); + return http.build(); + } +} diff --git a/order-service/src/main/java/dev/azdanov/orderservice/domain/SecurityService.java b/order-service/src/main/java/dev/azdanov/orderservice/domain/SecurityService.java index 1d5f344..c4e523a 100644 --- a/order-service/src/main/java/dev/azdanov/orderservice/domain/SecurityService.java +++ b/order-service/src/main/java/dev/azdanov/orderservice/domain/SecurityService.java @@ -1,11 +1,28 @@ package dev.azdanov.orderservice.domain; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.stereotype.Service; @Service public class SecurityService { - public String getLoginUserName() { - return "user"; + private static final Logger log = LoggerFactory.getLogger(SecurityService.class); + + public Optional getLoginUserName() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication instanceof JwtAuthenticationToken jwtAuthToken) { + Jwt jwt = jwtAuthToken.getToken(); + return Optional.ofNullable(jwt.getClaimAsString("preferred_username")); + } + + log.error("Failed to get user name from security context for authentication: {}", authentication); + return Optional.empty(); } } diff --git a/order-service/src/main/java/dev/azdanov/orderservice/web/controllers/OrderController.java b/order-service/src/main/java/dev/azdanov/orderservice/web/controllers/OrderController.java index 970a9b8..d80d8fb 100644 --- a/order-service/src/main/java/dev/azdanov/orderservice/web/controllers/OrderController.java +++ b/order-service/src/main/java/dev/azdanov/orderservice/web/controllers/OrderController.java @@ -7,22 +7,18 @@ import dev.azdanov.orderservice.domain.models.CreateOrderResponse; import dev.azdanov.orderservice.domain.models.OrderBrief; import dev.azdanov.orderservice.domain.models.OrderDetails; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.Valid; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/orders") -public class OrderController { +@SecurityRequirement(name = "security_auth") +class OrderController { private static final Logger log = LoggerFactory.getLogger(OrderController.class); @@ -37,27 +33,28 @@ public class OrderController { @PostMapping @ResponseStatus(HttpStatus.CREATED) CreateOrderResponse createOrder(@Valid @RequestBody CreateOrderRequest request) { - String userName = securityService.getLoginUserName(); + String userName = getUserName(); log.info("Creating order for user: {}", userName); - return orderService.createOrder(userName, request); } @GetMapping List getOrders() { - String userName = securityService.getLoginUserName(); + String userName = getUserName(); log.info("Fetching orders for user {}", userName); - return orderService.findOrders(userName); } @GetMapping("/{orderNumber}") - OrderDetails getOrder(@PathVariable(value = "orderNumber") String orderNumber) { - String userName = securityService.getLoginUserName(); + OrderDetails getOrder(@PathVariable String orderNumber) { + String userName = getUserName(); log.info("Fetching order {} for user {}", orderNumber, userName); - return orderService .findUserOrder(userName, orderNumber) .orElseThrow(() -> new OrderNotFoundException(orderNumber)); } + + private String getUserName() { + return securityService.getLoginUserName().orElseThrow(); + } } diff --git a/order-service/src/main/java/dev/azdanov/orderservice/web/exceptions/GlobalExceptionHandler.java b/order-service/src/main/java/dev/azdanov/orderservice/web/exceptions/GlobalExceptionHandler.java index a8c4521..d9785dd 100644 --- a/order-service/src/main/java/dev/azdanov/orderservice/web/exceptions/GlobalExceptionHandler.java +++ b/order-service/src/main/java/dev/azdanov/orderservice/web/exceptions/GlobalExceptionHandler.java @@ -23,7 +23,7 @@ class GlobalExceptionHandler extends ResponseEntityExceptionHandler { private static final String SERVICE_NAME = "order-service"; - public static final String CATEGORY_GENERIC = "Generic"; + private static final String CATEGORY_GENERIC = "Generic"; private static final URI INTERNAL_SERVER_ERROR_TYPE = URI.create("https://http.dev/500"); private static final URI NOT_FOUND_TYPE = URI.create("https://http.dev/404"); diff --git a/order-service/src/main/resources/application.properties b/order-service/src/main/resources/application.properties index 541b4d0..418a6ef 100644 --- a/order-service/src/main/resources/application.properties +++ b/order-service/src/main/resources/application.properties @@ -1,11 +1,15 @@ +# Application Configuration spring.application.name=order-service +# Server Configuration server.port=8082 server.shutdown=graceful +# Management Endpoints management.endpoints.web.exposure.include=* management.info.git.mode=full +# Orders Service Configuration orders.catalog-service-url=http://localhost:8081 orders.order-events-exchange=orders-exchange orders.new-orders-queue=new-orders @@ -13,9 +17,19 @@ orders.delivered-orders-queue=delivered-orders orders.cancelled-orders-queue=cancelled-orders orders.error-orders-queue=error-orders +# Job Scheduling orders.publish-order-events-job-cron=*/5 * * * * * orders.new-orders-job-cron=*/10 * * * * * +# OAuth2 Configuration +OAUTH2_SERVER_URL=http://localhost:9191 +REALM_URL=${OAUTH2_SERVER_URL}/realms/bookstore +spring.security.oauth2.resourceserver.jwt.issuer-uri=${REALM_URL} + +# Swagger Configuration +swagger.api-gateway-url=http://localhost:8989/orders + +# Database Configuration spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:25432/postgres} spring.datasource.username=${DB_USERNAME:postgres} spring.datasource.password=${DB_PASSWORD:postgres} @@ -23,14 +37,17 @@ spring.jpa.open-in-view=false spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=validate +# RabbitMQ Configuration spring.rabbitmq.host=${RABBITMQ_HOST:localhost} spring.rabbitmq.port=${RABBITMQ_PORT:5672} spring.rabbitmq.username=${RABBITMQ_USERNAME:guest} spring.rabbitmq.password=${RABBITMQ_PASSWORD:guest} +# Resilience4j Retry Configuration resilience4j.retry.backends.catalog-service.max-attempts=2 resilience4j.retry.backends.catalog-service.wait-duration=1s +# Resilience4j Circuit Breaker Configuration resilience4j.circuitbreaker.backends.catalog-service.sliding-window-type=COUNT_BASED resilience4j.circuitbreaker.backends.catalog-service.sliding-window-size=6 resilience4j.circuitbreaker.backends.catalog-service.minimum-number-of-calls=4 diff --git a/order-service/src/test/java/dev/azdanov/orderservice/AbstractIntegrationTest.java b/order-service/src/test/java/dev/azdanov/orderservice/AbstractIntegrationTest.java index 1cfd566..8cf7f8a 100644 --- a/order-service/src/test/java/dev/azdanov/orderservice/AbstractIntegrationTest.java +++ b/order-service/src/test/java/dev/azdanov/orderservice/AbstractIntegrationTest.java @@ -6,28 +6,50 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +import com.fasterxml.jackson.annotation.JsonProperty; import com.github.tomakehurst.wiremock.client.WireMock; import io.restassured.RestAssured; import java.math.BigDecimal; +import java.util.Objects; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.keycloak.OAuth2Constants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; import org.wiremock.integrations.testcontainers.WireMockContainer; @SpringBootTest(webEnvironment = RANDOM_PORT) -@Import({PostgresConfig.class, RabbitMqConfig.class}) +@Import({PostgresConfig.class, RabbitMqConfig.class, KeycloakConfig.class}) +@AutoConfigureMockMvc public abstract class AbstractIntegrationTest { + static final String CLIENT_ID = "bookstore-webapp"; + static final String CLIENT_SECRET = "P1sibsIrELBhmvK18BOzw1bUl96DcP2z"; + static final String USERNAME = "azdanov"; + static final String PASSWORD = "azdanov"; + + @Autowired + OAuth2ResourceServerProperties oAuth2ResourceServerProperties; + @LocalServerPort int port; static WireMockContainer wiremockServer = new WireMockContainer("wiremock/wiremock:3.5.2-alpine"); + @Autowired + protected MockMvc mockMvc; + @BeforeAll static void beforeAll() { wiremockServer.start(); @@ -61,4 +83,27 @@ protected static void mockGetProductByCode(String code, String name, BigDecimal .withStatus(200) .withBody(payload))); } + + private final RestClient restClient = RestClient.builder().build(); + + protected String getToken() { + MultiValueMap request = new LinkedMultiValueMap<>(); + request.add(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD); + request.add(OAuth2Constants.CLIENT_ID, CLIENT_ID); + request.add(OAuth2Constants.CLIENT_SECRET, CLIENT_SECRET); + request.add(OAuth2Constants.USERNAME, USERNAME); + request.add(OAuth2Constants.PASSWORD, PASSWORD); + + KeyCloakToken token = restClient + .post() + .uri(oAuth2ResourceServerProperties.getJwt().getIssuerUri() + "/protocol/openid-connect/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(request) + .retrieve() + .body(KeyCloakToken.class); + + return Objects.requireNonNull(token).accessToken(); + } + + record KeyCloakToken(@JsonProperty("access_token") String accessToken) {} } diff --git a/order-service/src/test/java/dev/azdanov/orderservice/KeycloakConfig.java b/order-service/src/test/java/dev/azdanov/orderservice/KeycloakConfig.java new file mode 100644 index 0000000..d0ce383 --- /dev/null +++ b/order-service/src/test/java/dev/azdanov/orderservice/KeycloakConfig.java @@ -0,0 +1,20 @@ +package dev.azdanov.orderservice; + +import dasniko.testcontainers.keycloak.KeycloakContainer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.DynamicPropertyRegistry; + +@TestConfiguration(proxyBeanMethods = false) +class KeycloakConfig { + + @Bean + KeycloakContainer keycloak(DynamicPropertyRegistry registry) { + var keycloak = + new KeycloakContainer("quay.io/keycloak/keycloak:24.0.4").withRealmImportFile("/keycloak/realm.json"); + registry.add( + "spring.security.oauth2.resourceserver.jwt.issuer-uri", + () -> keycloak.getAuthServerUrl() + "/realms/bookstore"); + return keycloak; + } +} diff --git a/order-service/src/test/java/dev/azdanov/orderservice/MockOAuth2UserContextFactory.java b/order-service/src/test/java/dev/azdanov/orderservice/MockOAuth2UserContextFactory.java new file mode 100644 index 0000000..d2a718f --- /dev/null +++ b/order-service/src/test/java/dev/azdanov/orderservice/MockOAuth2UserContextFactory.java @@ -0,0 +1,52 @@ +package dev.azdanov.orderservice; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.test.context.support.WithSecurityContextFactory; +import org.springframework.util.StringUtils; + +public class MockOAuth2UserContextFactory implements WithSecurityContextFactory { + + @Override + public SecurityContext createSecurityContext(WithMockOAuth2User withUser) { + String username = StringUtils.hasLength(withUser.username()) ? withUser.username() : withUser.value(); + if (username == null) { + throw new IllegalArgumentException( + withUser + " cannot have null username on both username and value properties"); + } + + List authorities = Stream.of(withUser.roles()) + .map(MockOAuth2UserContextFactory::toGrantedAuthority) + .collect(Collectors.toList()); + + Map claims = Map.of( + "preferred_username", username, + "userId", withUser.id(), + "realm_access", authorities); + Map headers = Map.of("header", "mock"); + + Jwt jwt = new Jwt("mock-jwt-token", Instant.now(), Instant.now().plusSeconds(300), headers, claims); + Authentication authentication = new JwtAuthenticationToken(jwt, authorities); + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authentication); + return context; + } + + private static SimpleGrantedAuthority toGrantedAuthority(String role) { + if (role.startsWith("ROLE_")) { + throw new IllegalArgumentException("roles cannot start with ROLE_. Got " + role); + } + return new SimpleGrantedAuthority("ROLE_" + role); + } +} diff --git a/order-service/src/test/java/dev/azdanov/orderservice/WithMockOAuth2User.java b/order-service/src/test/java/dev/azdanov/orderservice/WithMockOAuth2User.java new file mode 100644 index 0000000..0964389 --- /dev/null +++ b/order-service/src/test/java/dev/azdanov/orderservice/WithMockOAuth2User.java @@ -0,0 +1,25 @@ +package dev.azdanov.orderservice; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.security.test.context.support.WithSecurityContext; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@WithSecurityContext(factory = MockOAuth2UserContextFactory.class) +public @interface WithMockOAuth2User { + + long id() default -1; + + String value() default "user"; + + String username() default ""; + + String[] roles() default {"USER"}; +} diff --git a/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/GetOrdersTests.java b/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/GetOrdersTests.java new file mode 100644 index 0000000..f38547d --- /dev/null +++ b/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/GetOrdersTests.java @@ -0,0 +1,17 @@ +package dev.azdanov.orderservice.web.controllers; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import dev.azdanov.orderservice.AbstractIntegrationTest; +import dev.azdanov.orderservice.WithMockOAuth2User; +import org.junit.jupiter.api.Test; + +class GetOrdersTests extends AbstractIntegrationTest { + + @Test + @WithMockOAuth2User(username = "user") + void shouldGetOrdersSuccessfully() throws Exception { + mockMvc.perform(get("/api/v1/orders")).andExpect(status().isOk()); + } +} diff --git a/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerIntegrationTest.java b/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerIntegrationTest.java index 1ca929f..c09432d 100644 --- a/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerIntegrationTest.java +++ b/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerIntegrationTest.java @@ -29,6 +29,7 @@ void shouldCreateOrderSuccessfully() { var payload = createValidOrderRequest(); given().contentType(ContentType.JSON) + .header("Authorization", "Bearer " + getToken()) .body(payload) .when() .post("/api/v1/orders") @@ -41,6 +42,7 @@ void shouldCreateOrderSuccessfully() { void shouldReturnBadRequestWhenMandatoryDataIsMissing() { var payload = createOrderRequestWithInvalidCustomer(); given().contentType(ContentType.JSON) + .header("Authorization", "Bearer " + getToken()) .body(payload) .when() .post("/api/v1/orders") @@ -54,6 +56,7 @@ class GetOrdersTests { @Test void shouldGetOrdersSuccessfully() { List orderSummaries = given().when() + .header("Authorization", "Bearer " + getToken()) .get("/api/v1/orders") .then() .statusCode(200) @@ -72,6 +75,7 @@ class GetOrderByOrderNumberTests { @Test void shouldGetOrderSuccessfully() { given().when() + .header("Authorization", "Bearer " + getToken()) .get("/api/v1/orders/{orderNumber}", orderNumber) .then() .statusCode(200) diff --git a/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerWebTest.java b/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerWebTest.java index 01c8b1f..94b6f74 100644 --- a/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerWebTest.java +++ b/order-service/src/test/java/dev/azdanov/orderservice/web/controllers/OrderControllerWebTest.java @@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -15,6 +16,7 @@ import dev.azdanov.orderservice.domain.OrderService; import dev.azdanov.orderservice.domain.SecurityService; import dev.azdanov.orderservice.domain.models.CreateOrderRequest; +import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; @@ -24,6 +26,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(OrderController.class) @@ -45,16 +48,18 @@ class OrderControllerWebTest { @BeforeEach void setUp() { - given(securityService.getLoginUserName()).willReturn(USERNAME); + given(securityService.getLoginUserName()).willReturn(Optional.of(USERNAME)); } @ParameterizedTest(name = "[{index}]-{0}") @MethodSource("createOrderRequestProvider") + @WithMockUser void shouldReturnBadRequestWhenOrderPayloadIsInvalid(CreateOrderRequest request) throws Exception { given(orderService.createOrder(eq(USERNAME), any(CreateOrderRequest.class))) .willReturn(null); mockMvc.perform(post("/api/v1/orders") + .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); diff --git a/order-service/src/test/resources/keycloak/realm.json b/order-service/src/test/resources/keycloak/realm.json new file mode 100644 index 0000000..a4c8b90 --- /dev/null +++ b/order-service/src/test/resources/keycloak/realm.json @@ -0,0 +1,1927 @@ +{ + "id" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "realm" : "bookstore", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "43273753-bab6-4292-a25f-9092d3cb9331", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "attributes" : { } + }, { + "id" : "92ff70a5-7f6e-4f3e-a0b3-5f41a86d1824", + "name" : "default-roles-bookstore", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "attributes" : { } + }, { + "id" : "f6f78c38-43c5-47b3-a18c-1123340fe4f2", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "6d798e56-6d05-498a-8dbb-c1b895579391", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "cdaecae4-fee6-4dab-a7db-38f1abc6f32e", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "fc063e96-aa8d-434a-848b-807afe0926a7", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "c2f19f50-ca4a-4190-8956-81bc10e24356", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "f788fc1b-f470-49f7-8d3e-a398dc56e172", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "1409542d-0123-4626-bfd2-351a69194335", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "b2fbda88-f9b1-4c70-866b-fc52bbca7aed", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "dbe27a6e-5f7a-40b4-b469-e8a9d527cbd3", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "47ae5c38-c3b2-4fc0-87bd-e6b9f5fabe8d", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "1e459d5f-7e2c-4e40-b93a-65a84120bf1e", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "8872c0d6-5c5e-445d-8894-01682e7f05e2", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "862015d5-29d6-4024-a54e-81fddc69555c", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "30cd142d-0033-40f9-85f5-0706561da1fa", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "ef3207ec-bfd9-43c1-ae5e-23fd4ced270d", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "985e3850-84ea-494d-ae4d-e9d1972bed4d", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "16800317-78b4-4f9a-b73c-e128a287ee9f", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "18a42192-7d8e-461c-aafc-6b155834df19", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "e7aa784e-48de-421b-970e-420f694b8724", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + }, { + "id" : "f7b281d9-7a6f-4451-aaca-000918e6f292", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-realms", "query-users", "manage-events", "view-authorization", "view-realm", "impersonation", "view-identity-providers", "query-clients", "manage-identity-providers", "manage-clients", "query-groups", "view-clients", "view-users", "view-events", "manage-realm", "manage-authorization", "manage-users", "create-client" ] + } + }, + "clientRole" : true, + "containerId" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "bookstore-webapp" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "02f1701d-813b-4770-a7cb-adcb74b5c724", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "336bf23b-ace7-408d-8d28-6a9d46e05ab8", + "attributes" : { } + } ], + "account" : [ { + "id" : "0c4ecd80-08bf-4212-ae6d-2f70740c8c1f", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "3e124c1a-3d27-405f-a1b4-aeab3a14b3b3", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "90bf93b6-f4fc-4f6b-b5c3-0bcc50045791", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "89a2f5b1-1141-4ec4-a6ee-36dcbaa59795", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "5ee3cb01-3fab-47b3-8996-2c1dbe0f9897", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "ff46c2f7-e863-4a53-a3f4-4274e70d237d", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "0eea8cd2-1ef4-4505-b12d-16603160a754", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + }, { + "id" : "c0eebf2d-0287-4359-bc19-5ea21cf0256f", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "92ff70a5-7f6e-4f3e-a0b3-5f41a86d1824", + "name" : "default-roles-bookstore", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "34ec12c9-e4e6-4931-9e7a-3bc6e27484b2" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "users" : [ { + "id" : "b6d13f59-de3f-4cea-85a4-814b5f187efc", + "username" : "anton", + "firstName" : "Anton", + "lastName" : "Zdanov", + "email" : "anton@zdanov.dev", + "emailVerified" : true, + "createdTimestamp" : 1713932815276, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "8da0d5c7-eddc-4e98-968c-e2a769c335a2", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1715530098371, + "secretData" : "{\"value\":\"2NjmRd09vNLmNOsxMlf2hLbuedwWIHaEXcS/9h3tkCNEqGrX72pvihp8jJXql6bBG4H2GiQoTZ7orERh3fui6Q==\",\"salt\":\"YY10c+SzEMVrCErJGe+xdQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-bookstore" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "362b5bb3-0e0a-4ee6-9b59-53e2ca1adeac", + "username" : "azdanov", + "firstName" : "Anton", + "lastName" : "Zdanov", + "email" : "anton@azdanov.dev", + "emailVerified" : true, + "createdTimestamp" : 1713932760828, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "f228b1f9-4426-49f2-a294-a4b11c6500cb", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1715530110509, + "secretData" : "{\"value\":\"mrZFtlyCmedeDQHG4ycqQx1s0Y6WSKdXzir0Vr37bDfnNlEfYlESUe20EbKUIocRL+BkZ+vyGUMZNleL/O/8MA==\",\"salt\":\"tQEoDRZNWG/OdYXo84WIXA==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-bookstore" ], + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "b87d8bec-e224-4689-ab08-8c33787d0321", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/bookstore/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/bookstore/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "eb5db48a-a3f6-447a-90a2-9c21b21630b9", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/bookstore/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/bookstore/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "eee6ed78-86a6-42b7-9780-8bee93bc52b6", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "388746c3-146a-4608-8140-ea63b8a0afe8", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "cae03f1a-94cd-448c-90d0-259e87c0ab59", + "clientId" : "bookstore-webapp", + "name" : "Bookstore", + "description" : "", + "rootUrl" : "http://localhost:8080", + "adminUrl" : "http://localhost:8080", + "baseUrl" : "http://localhost:8080", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "P1sibsIrELBhmvK18BOzw1bUl96DcP2z", + "redirectUris" : [ "http://localhost:8989/webjars/swagger-ui/oauth2-redirect.html", "http://localhost:8080/login/oauth2/code/bookstore-webapp" ], + "webOrigins" : [ "http://localhost:8080", "http://localhost:8989" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1713932702", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "http://localhost:8080", + "oauth2.device.authorization.grant.enabled" : "false", + "display.on.consent.screen" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "f606bfbb-610c-4969-a174-789fa7b208a2", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "0f3a5547-8a62-42d7-8840-ebcb49abf69e", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "414aaef3-820b-4d9c-a931-c388f23b87fd", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "336bf23b-ace7-408d-8d28-6a9d46e05ab8", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "db3d01f0-9795-4560-acc0-b99a338918fc", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "ddaefabd-c000-4a8b-98ea-e5eb6c992a1d", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/bookstore/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/bookstore/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "3f0a4f78-0f5b-4d6b-90f8-7eb6b8c4da11", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "cfb1a11f-2ffe-4b31-90b0-c410ca1e3beb", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "315d0814-5a8a-44f6-8610-66bd2c526c7d", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "1cfede7a-5db5-4e38-8be9-c57bfa285385", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String" + } + }, { + "id" : "45594d8a-ec78-44c9-95a4-9bd36aba4423", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "f8f4b27b-edb9-4136-ab45-de6f90ac12dc", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "21c9df62-f4e0-43d8-8d91-076d65286548", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "0961ee05-4b2f-4f5b-870e-39654c7bd950", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "d8052c93-a2f3-433e-91e1-d4fd0ebe11bb", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "97643e26-2d63-4699-aec9-b53bd303a9ac", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "bf5e0bcb-fc3d-42b0-99e0-8e5dbd228da4", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "414252cb-43fc-4ba2-9240-9bd87ac6eebb", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "14627a60-1a8e-4a2f-bc6d-b50f8457fef7", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "fcd6b286-667f-45cb-bb34-61d5be62728a", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "d441c11d-c5db-4993-b382-8c7ef720724c", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "4daac834-93bc-4143-8644-3242547f224b", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "9e51a5f9-fc70-4a14-8534-734810ad2452", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "84a0ac3a-a458-438a-8e12-3e85d8ab1740", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "7d5531c7-4a47-4b6c-a42e-8a82e04bb69a", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "0f347420-c1e5-44a5-baa3-89f1ed17b9c0", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "5abd8354-ec66-4f23-b447-6424d6d669c0", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "9c346e30-d1b2-4297-8a59-7ec2862fe7f1", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "ea8d58b6-ccdd-4c68-98f2-e57a3c8f097c", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "4f5b206a-cde1-4503-be00-f1f85333c7fb", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "0e45624b-fc58-4d07-9b09-4d6b8603e3f8", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "11ec387e-48e6-4478-8dfa-93f4d406a00a", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "8b4d55ff-99bf-4e8d-afc7-74d41934b9f3", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "79cf6096-fce1-44ea-9438-ca25e81b9005", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "6762c33a-e313-4c63-af6e-66c87581ccc1", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "aa13a0bc-7d99-44a8-9a89-6399f59bd8dd", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "4d0f7201-0064-4dea-a828-12bb5aa6c0ec", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "dd3815ca-93b9-409d-9e05-f0213eae52a0", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "f7d6b611-0c1e-4fa4-a7f6-979310a388e8", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "e5bbe4cd-c6e3-40eb-a8e6-b9de521f7c90", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "23e96991-df81-44e7-9a17-7e4db7972480", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + }, { + "id" : "a124c30e-bf74-452f-a65e-cf33ef29dda5", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "358f6efb-3a42-473d-b50f-bfeb240f6127", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "534166af-5b1b-4944-8a72-fab7d1900f3a", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "238a46bc-e29c-489d-8ce6-1392d45ad57f", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper" ] + } + }, { + "id" : "964f7766-4f2c-4107-9f5a-f6afc964cb23", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "1bbb3377-76a9-47f1-96e4-83b864f765e3", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "5de966b0-da21-425e-806f-1b065a37d946", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper" ] + } + }, { + "id" : "5f8f9fff-2279-4840-ae9f-83e3b36fd9c9", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "73e652e0-a743-40bf-ad71-3e0abd1d2e9e", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "a833d514-aee3-46fc-981b-b15ef00fef35", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "4f0f5137-4c9f-4682-87c7-0841ec903a8c", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "1fd29724-b5e9-451f-9f2a-58aa511af3ef", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "c1a8b75c-70f4-4ddd-a5ab-db5c3faea0ab" ], + "secret" : [ "9AGobYatVwpMiZpMVuHwCWrnrjI4EkjGtGspeyJ7jXcFvHbsI8DDQAbh3jnj_H1SJSyI4XO5w5y8hUecpLO3Ox0S7LryI_yS-3S_x7doRofS21zZqSN9FGCZpZlT2cGpOV2aO2Idj_3MAmJhPyB7tPaPk1xJS-tFeWprkYMgZTs" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "96db5738-ed32-4461-abab-ab0712de2361", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEogIBAAKCAQEAl5AvYbXZoa+dt3HKr/wa2QQaV1lraVEbrhKcKaD4mFz3Kau47XLKLBXBIkMhD9LKCx0Jyu7tZy1gLzXKx7m5ygwtD/jvOrnFZ3PYiDCrgHO83NhxYTzKdBSiupWt7X7Ss0keclonGE7L01ZS5ytU15IuofqxrYEO2nbp1mlpUAzjy2ZIX+IEAA8VCHK5dvlmZgiZYic6N5+WkHGqu5DgKkVPyT+h3jhzYpshwmqXW+KtgKrQ2J6PrS/K2apBBq3HHM4s5X//QgEP8ehcTr2eEkqwuaRuVPgjaAdqkAw6VTInE9hiZRhHPHDKvQsA2ze3JTucm+fA9Ng4yvPTxLPowwIDAQABAoIBAARJCl3r41xj98ShDgcNzMCIEiqYF6ZhqbOxSQMQTxcXwdFGzrLABSFb+Br85Olqn1XTG1zGhqXdLZQIJFSjVrJ0x61k73asm/1B1ER9g8vc+eEQqZwDOQIxNyypYMKPTGvPTHIVRHe2eqLeFPf+TKbPtkvUJ8PtGztoE1KBpn2h5IdSqsBp9VsuCUSU67hgJ7C10HQSqm7LAKbioz661MK+h+7o5RbopM6YGKIQeqdWJbTiNs7DtvZwHOm/mRIDC222NCFPNMNuUoTGNDwlJ2P5YNN4svWWHPn2snamaV/zK4z7IGxVfVmG7waN4Tg4B8spbrQrLNSrA6eAj572Z0kCgYEAxlNyYS1W/JaFCI+tVBgbbKifkeiyBNJYQ6iCUSehJezBICK0Tq8C5YYYD35hJrRuDeBacbUBhO6ic/jUDvkzz3jVMnag5Ye5gwXJl20GEXMuWvJOjkSNsBM2dROxmUR5HqkgapGoF3Dv84GWgt685eiD3Gg2V08/rszi0QOXw60CgYEAw6NxwF1pNoD4pgOXEpM+rKsarE7jzOM3GSxIBukqyi/MfEPtAArpVQwM6wH7zT41noRHkokVsDtu5ZxhNC3J5douDSa1XioI1drARS3JTBX0kwB1c/ztERfMEIQboJnVsyeexKUrJzogMctJa1JgkPgGGlVrXlcB6AETewOybC8CgYAexen0jC14Kf7gcjLJ3qe6t3tzD3E38E+DyAzYfNazZMYer2Pc5akEZkcreEeo0sxU2OAPzq86qjCI4C4/WImigrRKDUAv/VxaqTY7ic4sPPwPXpOz3Y1CqeiESBcBmi9VA5clEg7L/FkvJodtLRSQdACp3X25FHHkNKRpHC6bTQKBgBCpAY3dJpGSqBoTIjFdwq+Xnpzf1vxiuRV2tOeJwbAYI/TNIAip5Tbt4Me+kn/m9U/7edDbxbINTaNWDt0PrSrayqMqEbXkJH55s3fwrzzAfeRq3JW1C0cyXJlndqbVkuI/1IBwhF1B0O4MnsL57+lIhdSGQ69z/2CPfuYx6WbXAoGAWE1ikNs6ujz7aTnaPwPK9bCxO8tEsk5qm34R4otIq+GIeaCjia5zCbXJPXpENJPBQgsCH1fHxT/Nt4CZaECE+3Ibg8AuNXnfsVMv8QbnDS3JtQI5RT8n0cnnZd+8c5ioI9QAgR2Hv0hD4cFtfjbzs6QUNSOBH+VEmiox1PYMeYs=" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICoTCCAYkCBgGPDlWStjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlib29rc3RvcmUwHhcNMjQwNDI0MDQxOTAwWhcNMzQwNDI0MDQyMDQwWjAUMRIwEAYDVQQDDAlib29rc3RvcmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXkC9htdmhr523ccqv/BrZBBpXWWtpURuuEpwpoPiYXPcpq7jtcsosFcEiQyEP0soLHQnK7u1nLWAvNcrHubnKDC0P+O86ucVnc9iIMKuAc7zc2HFhPMp0FKK6la3tftKzSR5yWicYTsvTVlLnK1TXki6h+rGtgQ7adunWaWlQDOPLZkhf4gQADxUIcrl2+WZmCJliJzo3n5aQcaq7kOAqRU/JP6HeOHNimyHCapdb4q2AqtDYno+tL8rZqkEGrccczizlf/9CAQ/x6FxOvZ4SSrC5pG5U+CNoB2qQDDpVMicT2GJlGEc8cMq9CwDbN7clO5yb58D02DjK89PEs+jDAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAImZccBZzONa1A8X/2yK7KBZH/R3yFOjnDDjb3w2ewF/R63UFiqg++E0NLXNZ8RRSLUVmP81eaoxB6jbyOEvOUDb2CERQSHZ/CMA/DMahPSURKfbG90FlGqH/wuDUyExI+gKoCql30+4pZGx9f0ltAjzv7s8kd8ED/zXmQQ+cRJ7lSx3EP+kHXwfN0/5uvn+8z6JMz2uukaZ3bgwWKEhB3Fq3qAchNzdsg/QWh+H4RCFAuNOqZ/w5osuQwMnNv6JAhfs9kq+fOFQfet+kbSS2eD9KnSwj/M7xkzTCPTCcKtENU52Sa8ss10BNn4EdDfFcI4Pp4LEtdSzbFeq0JVqhUQ=" ], + "priority" : [ "100" ] + } + }, { + "id" : "94b5df69-4b72-4c71-9a79-d2a6dea53e7f", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "9b5b06f5-440d-47d3-a8d5-93886095fc34" ], + "secret" : [ "bKrTHv_hzWSy95b1-aWFDQ" ], + "priority" : [ "100" ] + } + }, { + "id" : "fedc6aa1-0f9e-4d18-ba68-645d8bddbca1", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAzYSKXFgaqCnJTOPQy9qa5i713BEnVcnFgVP7wNCSo1hFKVMaSF9gBAy5L7TFptgy3jmMZgvCCqxWjmxkUXy/qAKNsL7aWFbez9qypTTyloSilDnf2FAxIsORa7IB+LJq7vryIBZfvhgykBMYwEQV/Thrc/TtivobdWZyxIr9xn4FtzwuslgXeVnd+6PxmMkrh9TcZx5jxyUNUvO/n5Px4Hf4tO8auZHb4ZkR3XXMczjbQk4IOJuPEDFwwgUGw1JzgUFIFChhCn5a7FYfxmQgmIH8SMnYFykr8/1eGMNcLkkVNrHja325z+sb2XWrhHmZTSvc0XsuXn+yw1eBgzWpkQIDAQABAoIBABVskg9dFF9QJtbT/2PG3MOu5Mydgaoa4/LOzlVb8NNkG+aaTgi6H4xghqwLVljlnAzoNyN6qY5bB5VmhYEspd6V8I8aoDCaErVcjSe1eK358uhMjbITLwIanMXbK0Mxlz3lLVkst+udQmUcRoXheyDX8TSn9VzZ5IOlK8TjH+AhekyP/RvMx5J71q6zBcfVrfxdYRHdilPw0GOHBPr8XBy3uvtl0r1Y9vucl6vpbpLIoeKy3MqpVh5bqr0Dc7jIrfhvD79gs22cRstUKTvWARNtvO+YGNc7nO9J9aDsRMgwgsryvRP0np4obkbKa//3ggtG0Y8rwPuQSdyZkgSetDkCgYEA+LX7TnCMTntjrWKFoHrn3MDd0JJIPap8kCsd5zyiuDDq+4UFi97DsuDe4pyvXV4sC4OjKYWsyOXQ+qSxOhYyccqK5fqCKPFRlot5nKTsppmjaLnAn/vmpYTjeSlgqFaXhw0NPCu+gNQdb3mFITaDrDgHPgCkV8+pz/rR/iaMWCkCgYEA04p9tYiR1fAa89Kf6swI3/j6+Uro1JuPJCS+IVfXuiAe6fgJJCrNxegl3LQOhlgJWmU1j7Qhte2uX/i4UmJYW92drZV9U4xssTUx3qjRjnK4IsRG53vW59g3lFRB/KpFnWpBu4S5dDTFripDhOh7q6f9QhRAqmoCeZdWkjK8kykCgYBwkP0jLXFzfAlLSlIspjQXB/vdELg9oNIr9jfclucW8p0IJdayzapJZdm7Oytr2NmahIPuoR/o9Ys2RiAoqC9ArWlcWZtbqI1C0/HA4ixx4wvAtIHoZvynYZQLHJ0w07BVYaTh3PKmIFsKPzfwJlpGMj2k5Gl9jPHQvjcZDp1qsQKBgGXagcqdVjKfKCsL1+nSkdddZ2IO2mo7+EDb3Bvr0rgQbTIUNjqOzYMj0s8gdvnie6ZlotNtVOnm5hXZiM5KAedBnoEMwEoxC8iLJ1bhC/09aUF8JfOhIgmBhslJhHgmd9xiZAHo1u6H1kKUoOvmkF1HRl7Bp50l68klwA4mlbt5AoGBAM1hrV1tci3n1CoN5kum/rMDntaTWEATk4MLx9gwcHX45KPo3mmU2W05F7zlfkzPBQrblxnUfICsXxYnSupbDOdMeM8/WQEbvRxzgFiKfq4k8EKvsqxXOsbaQVY/zY/N5xQ01UfTkqrFCL67S5b43kZsEPer8iEYH8m5Hz0w6tGr" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICoTCCAYkCBgGPDlWT6TANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlib29rc3RvcmUwHhcNMjQwNDI0MDQxOTAwWhcNMzQwNDI0MDQyMDQwWjAUMRIwEAYDVQQDDAlib29rc3RvcmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNhIpcWBqoKclM49DL2prmLvXcESdVycWBU/vA0JKjWEUpUxpIX2AEDLkvtMWm2DLeOYxmC8IKrFaObGRRfL+oAo2wvtpYVt7P2rKlNPKWhKKUOd/YUDEiw5FrsgH4smru+vIgFl++GDKQExjARBX9OGtz9O2K+ht1ZnLEiv3GfgW3PC6yWBd5Wd37o/GYySuH1NxnHmPHJQ1S87+fk/Hgd/i07xq5kdvhmRHddcxzONtCTgg4m48QMXDCBQbDUnOBQUgUKGEKflrsVh/GZCCYgfxIydgXKSvz/V4Yw1wuSRU2seNrfbnP6xvZdauEeZlNK9zRey5ef7LDV4GDNamRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABWk+BtO2Lboq5JSA3zYSNwZpXjJ1Tpr9e+p3lzTZQbtu/hOLkl0faPUipnOnJp/UWJu3MKGFHy1oaZIUbt/E/G8SESnH7HufU8nRZ5kVJHOm8YMK+94Stak4Y6Pgz/YKJmWqqfo4qt8LMyMOH7HNTs3/J/MAkaTNTlopJbMLnoXWBF5ly5qHAu3zHcYUI3mive67GUV1Z9vK+Z9JVMiqSeiW8iMtG4GXKdrgsC5Jb4uAGc6vjxKUJQipvBrBiqzST/2O+8CDWYbzoPsZZM8HFMC1tAbIjlagtOVPCt0oFuv6Tr2hN7zRebU7zSSuh9d9X8mJVTTYq91bs13TR+s+C4=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "76cac48b-ee9e-431b-a5b4-35e09e8d17ed", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "bad7d4b6-f303-49b2-85e0-30302305927e", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "22c85f9c-5c6a-4a7e-b2cd-0fa40de2fc38", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "69a9fa80-2c5a-4c8b-aaca-aa79eca8eec0", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "cc07bdd0-43f6-4175-a0d8-f2d8c387f19c", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "99afbaa9-82b3-4a69-addc-cc711010a1ba", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "a29e71ce-81ee-488e-86e4-16aa4901d029", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "b1a82149-7707-4f86-b956-7ddd207db78a", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "92b6c9b6-24e2-4903-bc3f-b2dd3b87d8e0", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "cc5dd9a2-4586-419a-9c99-7b0a819cb93a", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "dc4a8767-409f-4dbd-95a8-1f823daa47c9", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "268720bb-2b3d-4d03-be6f-09cba97cbfd2", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "7dfa8c67-01c2-4256-bc6a-fb3e77927181", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "37fe7d53-72fb-4dcb-98db-83027408e1f9", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "e0066f2b-0f29-4875-9a67-76c4d32cd1c0", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "05226b4e-c428-4d39-b96a-106ee5dc49a9", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "fb25ee9a-e841-47c4-a07d-ee9e7e20f58a", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "dba15a69-343b-46ea-b924-12c6ffe0a218", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "3d24a56c-9d8b-4e7f-9024-a28e7f27b28c", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "6839bbe3-e1c0-44df-8a27-351c16771596", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DeviceCodeLifespan" : "600", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false" + }, + "keycloakVersion" : "24.0.4", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/order-service/src/test/resources/scripts/INIT_ORDERS.sql b/order-service/src/test/resources/scripts/INIT_ORDERS.sql index c64958a..19d7d9f 100644 --- a/order-service/src/test/resources/scripts/INIT_ORDERS.sql +++ b/order-service/src/test/resources/scripts/INIT_ORDERS.sql @@ -8,11 +8,11 @@ insert into orders (id, order_number, username, delivery_address_line1, delivery_address_line2, delivery_address_city, delivery_address_state, delivery_address_zip_code, delivery_address_country, status, comments) -values (1, 'order-123', 'user', +values (1, 'order-123', 'azdanov', 'Anton', 'anton@example.com', '11111111', '123 Main St', 'Apt 1', 'Hamburg', 'DE', '75001', 'Germany', 'NEW', null), - (2, 'order-456', 'user', + (2, 'order-456', 'azdanov', 'Ždanov', 'azdanov@example.com', '2222222', '123 Main St', 'Apt 1', 'Tallinn', 'HA', '500072', 'Estonia', 'NEW', null); diff --git a/webapp/pom.xml b/webapp/pom.xml index 3818771..d61fcb8 100644 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -44,10 +44,22 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-client + nz.net.ultraq.thymeleaf thymeleaf-layout-dialect + + org.thymeleaf.extras + thymeleaf-extras-springsecurity6 + org.webjars @@ -81,6 +93,11 @@ spring-boot-starter-test test + + org.springframework.security + spring-security-test + test + diff --git a/webapp/src/main/java/dev/azdanov/webapp/clients/ClientsConfig.java b/webapp/src/main/java/dev/azdanov/webapp/clients/ClientsConfig.java index 6d8c135..51c16af 100644 --- a/webapp/src/main/java/dev/azdanov/webapp/clients/ClientsConfig.java +++ b/webapp/src/main/java/dev/azdanov/webapp/clients/ClientsConfig.java @@ -3,8 +3,13 @@ import dev.azdanov.webapp.ApplicationProperties; import dev.azdanov.webapp.clients.catalog.CatalogServiceClient; import dev.azdanov.webapp.clients.orders.OrderServiceClient; +import java.time.Duration; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.web.client.RestClientCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.web.client.RestClient; import org.springframework.web.client.support.RestClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; @@ -18,23 +23,30 @@ class ClientsConfig { this.properties = properties; } + @Bean + RestClientCustomizer restClientCustomizer(OAuth2AuthorizedClientService authorizedClientService) { + return restClientBuilder -> restClientBuilder + .baseUrl(properties.apiGatewayUrl()) + .requestInitializer(new OAuth2ClientInitializer(authorizedClientService)) + .requestFactory(ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS + .withConnectTimeout(Duration.ofSeconds(5)) + .withReadTimeout(Duration.ofSeconds(5)))); + } + @Bean CatalogServiceClient catalogServiceClient(RestClient.Builder restClientBuilder) { - RestClient restClient = - restClientBuilder.baseUrl(properties.apiGatewayUrl()).build(); - RestClientAdapter exchangeAdapter = RestClientAdapter.create(restClient); - HttpServiceProxyFactory factory = - HttpServiceProxyFactory.builderFor(exchangeAdapter).build(); - return factory.createClient(CatalogServiceClient.class); + return createServiceClient(restClientBuilder.build(), CatalogServiceClient.class); } @Bean OrderServiceClient orderServiceClient(RestClient.Builder restClientBuilder) { - RestClient restClient = - restClientBuilder.baseUrl(properties.apiGatewayUrl()).build(); - RestClientAdapter exchangeAdapter = RestClientAdapter.create(restClient); - HttpServiceProxyFactory factory = - HttpServiceProxyFactory.builderFor(exchangeAdapter).build(); - return factory.createClient(OrderServiceClient.class); + return createServiceClient(restClientBuilder.build(), OrderServiceClient.class); + } + + private T createServiceClient(RestClient restClient, Class clientClass) { + return HttpServiceProxyFactory.builder() + .exchangeAdapter(RestClientAdapter.create(restClient)) + .build() + .createClient(clientClass); } } diff --git a/webapp/src/main/java/dev/azdanov/webapp/clients/OAuth2ClientInitializer.java b/webapp/src/main/java/dev/azdanov/webapp/clients/OAuth2ClientInitializer.java new file mode 100644 index 0000000..357f25d --- /dev/null +++ b/webapp/src/main/java/dev/azdanov/webapp/clients/OAuth2ClientInitializer.java @@ -0,0 +1,39 @@ +package dev.azdanov.webapp.clients; + +import java.util.Optional; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestInitializer; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.AbstractOAuth2Token; + +class OAuth2ClientInitializer implements ClientHttpRequestInitializer { + + private final OAuth2AuthorizedClientService authorizedClientService; + + OAuth2ClientInitializer(OAuth2AuthorizedClientService authorizedClientService) { + this.authorizedClientService = authorizedClientService; + } + + @Override + public void initialize(ClientHttpRequest request) { + getOAuth2AuthenticationToken() + .flatMap(this::getOAuth2AuthorizedClient) + .map(OAuth2AuthorizedClient::getAccessToken) + .map(AbstractOAuth2Token::getTokenValue) + .ifPresent(request.getHeaders()::setBearerAuth); + } + + private Optional getOAuth2AuthenticationToken() { + return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) + .filter(OAuth2AuthenticationToken.class::isInstance) + .map(OAuth2AuthenticationToken.class::cast); + } + + private Optional getOAuth2AuthorizedClient(OAuth2AuthenticationToken oauthToken) { + return Optional.ofNullable(authorizedClientService.loadAuthorizedClient( + oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName())); + } +} diff --git a/webapp/src/main/java/dev/azdanov/webapp/config/SecurityConfig.java b/webapp/src/main/java/dev/azdanov/webapp/config/SecurityConfig.java new file mode 100644 index 0000000..7cd1764 --- /dev/null +++ b/webapp/src/main/java/dev/azdanov/webapp/config/SecurityConfig.java @@ -0,0 +1,54 @@ +package dev.azdanov.webapp.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CorsConfigurer; +import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + private final ClientRegistrationRepository clientRegistrationRepository; + + SecurityConfig(ClientRegistrationRepository clientRegistrationRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests(auth -> auth.requestMatchers( + "/", + "/error", + "/js/*", + "/css/*", + "/images/*", + "/webjars/**", + "/actuator/**", + "/products/**", + "/api/products/**") + .permitAll() + .anyRequest() + .authenticated()) + .cors(CorsConfigurer::disable) + .csrf(Customizer.withDefaults()) + .oauth2Login(Customizer.withDefaults()) + .logout(logout -> logout.clearAuthentication(true) + .invalidateHttpSession(true) + .logoutSuccessHandler(oidcLogoutSuccessHandler())) + .build(); + } + + private LogoutSuccessHandler oidcLogoutSuccessHandler() { + OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler = + new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository); + oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}"); + return oidcLogoutSuccessHandler; + } +} diff --git a/webapp/src/main/resources/application.properties b/webapp/src/main/resources/application.properties index 566bfe5..a747713 100644 --- a/webapp/src/main/resources/application.properties +++ b/webapp/src/main/resources/application.properties @@ -1,8 +1,24 @@ +# Application Configuration spring.application.name=webapp + +# Server Configuration server.port=8080 server.shutdown=graceful + +# Spring MVC Configuration spring.mvc.problemdetails.enabled=true +# Management Endpoints management.endpoints.web.exposure.include=* +# Bookstore API Gateway URL bookstore.api-gateway-url=http://localhost:8989 + +# OAuth2 Configuration +OAUTH2_SERVER_URL=http://localhost:9191 +spring.security.oauth2.client.registration.bookstore-webapp.client-id=bookstore-webapp +spring.security.oauth2.client.registration.bookstore-webapp.client-secret=P1sibsIrELBhmvK18BOzw1bUl96DcP2z +spring.security.oauth2.client.registration.bookstore-webapp.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.bookstore-webapp.scope=openid, profile +spring.security.oauth2.client.registration.bookstore-webapp.redirect-uri={baseUrl}/login/oauth2/code/bookstore-webapp +spring.security.oauth2.client.provider.bookstore-webapp.issuer-uri=${OAUTH2_SERVER_URL}/realms/bookstore diff --git a/webapp/src/main/resources/static/js/cart.js b/webapp/src/main/resources/static/js/cart.js index b6b68df..e17e4f6 100644 --- a/webapp/src/main/resources/static/js/cart.js +++ b/webapp/src/main/resources/static/js/cart.js @@ -62,10 +62,14 @@ document.addEventListener("alpine:init", () => { this.submitting = true; let order = { ...this.orderForm, items: this.cart.items }; + const token = document.querySelector("#csrf").content; + const header = document.querySelector("#csrfHeader").content; + try { const response = await fetch("/api/orders", { method: "POST", headers: { + [header]: token, "Content-Type": "application/json", }, body: JSON.stringify(order), diff --git a/webapp/src/main/resources/templates/cart.html b/webapp/src/main/resources/templates/cart.html index b9cd3c8..87bf271 100644 --- a/webapp/src/main/resources/templates/cart.html +++ b/webapp/src/main/resources/templates/cart.html @@ -5,6 +5,11 @@ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}" > + + Cart + + +
diff --git a/webapp/src/main/resources/templates/layout.html b/webapp/src/main/resources/templates/layout.html index da6c4e6..04e11cd 100644 --- a/webapp/src/main/resources/templates/layout.html +++ b/webapp/src/main/resources/templates/layout.html @@ -4,11 +4,12 @@ xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" + xmlns:sec="http://www.thymeleaf.org/extras/spring-security" > - Bookstore + Bookstore
diff --git a/webapp/src/main/resources/templates/order_details.html b/webapp/src/main/resources/templates/order_details.html index c4d616d..7b4291d 100644 --- a/webapp/src/main/resources/templates/order_details.html +++ b/webapp/src/main/resources/templates/order_details.html @@ -6,6 +6,9 @@ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}" > + + Order Details +
diff --git a/webapp/src/main/resources/templates/orders.html b/webapp/src/main/resources/templates/orders.html index 706487c..cffa195 100644 --- a/webapp/src/main/resources/templates/orders.html +++ b/webapp/src/main/resources/templates/orders.html @@ -5,6 +5,9 @@ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}" > + + Orders +
diff --git a/webapp/src/test/java/dev/azdanov/webapp/WebappApplicationTests.java b/webapp/src/test/java/dev/azdanov/webapp/WebappApplicationTests.java deleted file mode 100644 index 17347af..0000000 --- a/webapp/src/test/java/dev/azdanov/webapp/WebappApplicationTests.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.azdanov.webapp; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class WebappApplicationTests { - - @Test - void contextLoads() {} -}