@@ -11,11 +11,9 @@ import org.apache.pekko.http.scaladsl.server.Directives.*
1111import org .apache .pekko .http .scaladsl .server .{AuthenticationFailedRejection , Directive1 , Route }
1212import org .apache .pekko .http .scaladsl .unmarshalling .Unmarshal
1313import org .keycloak .TokenVerifier
14- import org .keycloak .adapters .KeycloakDeploymentBuilder
1514import org .keycloak .admin .client .{CreatedResponseUtil , Keycloak }
1615import org .keycloak .jose .jws .AlgorithmType
1716import org .keycloak .representations .AccessToken
18- import org .keycloak .representations .adapters .config .AdapterConfig
1917import org .keycloak .representations .idm .{ClientRepresentation , CredentialRepresentation , UserRepresentation }
2018import org .slf4j .{Logger , LoggerFactory }
2119
@@ -53,12 +51,15 @@ import scala.util.{Failure, Success}
5351object OIDCKeycloak extends App with CORSHandler with JsonSupport {
5452 val logger : Logger = LoggerFactory .getLogger(this .getClass)
5553
54+ private val CLIENT_ID = " my-test-client"
55+ private val REALM_NAME = " test"
56+
5657 implicit val system : ActorSystem = ActorSystem ()
5758 implicit val executionContext : ExecutionContextExecutor = system.dispatcher
5859
5960 def runKeycloak () = {
6061 // Pin to same version as "keycloakVersion" in build.sbt
61- val keycloak = new KeycloakContainer (" quay.io/keycloak/keycloak:26.1 " )
62+ val keycloak = new KeycloakContainer (" quay.io/keycloak/keycloak:26.3 " )
6263 // Keycloak config taken from:
6364 // https://github.com/keycloak/keycloak/blob/main/examples/js-console/example-realm.json
6465 .withRealmImportFile(" keycloak_realm_config.json" )
@@ -81,7 +82,7 @@ object OIDCKeycloak extends App with CORSHandler with JsonSupport {
8182 def createTestUser (keycloakAdminClient : Keycloak ): Unit = {
8283 val username = " test"
8384 val password = " test"
84- val usersResource = keycloakAdminClient.realm(" test " ).users()
85+ val usersResource = keycloakAdminClient.realm(REALM_NAME ).users()
8586
8687 val user = new UserRepresentation ()
8788 user.setEnabled(true )
@@ -106,13 +107,12 @@ object OIDCKeycloak extends App with CORSHandler with JsonSupport {
106107 userResource.resetPassword(passwordCred)
107108
108109 logger.info(s " User $username created with userId: $userId" )
109- logger.info(s " User $username/ $password may sign in via: http://localhost: ${keycloak.getHttpPort}/realms/test /account " )
110+ logger.info(s " User $username/ $password may sign in via: http://localhost: ${keycloak.getHttpPort}/realms/ $REALM_NAME /account " )
110111 }
111112
112113 def createClientConfig (keycloakAdminClient : Keycloak ): Unit = {
113- val clientId = " my-test-client"
114114 val clientRepresentation = new ClientRepresentation ()
115- clientRepresentation.setClientId(clientId )
115+ clientRepresentation.setClientId(CLIENT_ID )
116116 clientRepresentation.setProtocol(" openid-connect" )
117117
118118 val redirectUriTestingOnly = new util.ArrayList [String ]()
@@ -122,10 +122,10 @@ object OIDCKeycloak extends App with CORSHandler with JsonSupport {
122122 webOriginsTestingOnly.add(" *" )
123123 clientRepresentation.setWebOrigins(webOriginsTestingOnly)
124124
125- val resp = keycloakAdminClient.realm(" test " ).clients().create(clientRepresentation)
126- logger.info(s " Successfully created client config for clientId: $clientId , response status: " + resp.getStatus)
125+ val resp = keycloakAdminClient.realm(REALM_NAME ).clients().create(clientRepresentation)
126+ logger.info(s " Successfully created client config for clientId: $CLIENT_ID , response status: " + resp.getStatus)
127127
128- val clients : util.List [ClientRepresentation ] = keycloakAdminClient.realm(" test " ).clients().findByClientId(clientId )
128+ val clients : util.List [ClientRepresentation ] = keycloakAdminClient.realm(REALM_NAME ).clients().findByClientId(CLIENT_ID )
129129 logger.info(s " Successfully read ClientRepresentation for clientId: ${clients.get(0 ).getClientId}" )
130130 }
131131
@@ -136,12 +136,13 @@ object OIDCKeycloak extends App with CORSHandler with JsonSupport {
136136 }
137137
138138 def runBackendServer (keycloak : KeycloakContainer ): Unit = {
139- val config = new AdapterConfig ()
140- config.setAuthServerUrl(keycloak.getAuthServerUrl)
141- config.setRealm(" test" )
142- config.setResource(" my-test-client" )
143- val keycloakDeployment = KeycloakDeploymentBuilder .build(config)
144- logger.info(" Dynamic authServerBaseUrl: " + keycloakDeployment.getAuthServerBaseUrl)
139+ val authServerUrl = keycloak.getAuthServerUrl
140+ val realmUrl = s " $authServerUrl/realms/ $REALM_NAME"
141+ val jwksUrl = s " $realmUrl/protocol/openid-connect/certs "
142+
143+ logger.info(s " Realm URL: $realmUrl" )
144+ logger.info(s " JSON Web Key Set (JWKS) URL: $jwksUrl" )
145+ logger.info(s " Client ID: $CLIENT_ID" )
145146
146147
147148 def generateKey (keyData : KeyData ): PublicKey = {
@@ -153,7 +154,7 @@ object OIDCKeycloak extends App with CORSHandler with JsonSupport {
153154 }
154155
155156 val publicKeys : Future [Map [String , PublicKey ]] =
156- Http ().singleRequest(HttpRequest (uri = keycloakDeployment.getJwksUrl )).flatMap(response => {
157+ Http ().singleRequest(HttpRequest (uri = jwksUrl )).flatMap(response => {
157158 val json = Unmarshal (response).to[String ]
158159 val keys = json.map { jsonString =>
159160 decode[Keys ](jsonString) match {
@@ -164,9 +165,8 @@ object OIDCKeycloak extends App with CORSHandler with JsonSupport {
164165 keys.map(_.keys.map(k => (k.kid, generateKey(k))).toMap)
165166 })
166167
167-
168- // Alternative:
169- // https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/security-directives/authenticateOAuth2.html
168+ // Alternative impl to:
169+ // https://pekko.apache.org/docs/pekko-http/current/routing-dsl/directives/security-directives/authenticateOAuth2.html
170170 def authenticate : Directive1 [AccessToken ] =
171171 extractCredentials.flatMap {
172172 case Some (OAuth2BearerToken (token)) =>
@@ -205,7 +205,7 @@ object OIDCKeycloak extends App with CORSHandler with JsonSupport {
205205 get {
206206 authenticate { token =>
207207 // To have "real data": Read 'UserRepresentation' from Keycloak via the admin client and then strip down
208- val usersOrig = adminClient.realm(" test " ).users().list().asScala
208+ val usersOrig = adminClient.realm(REALM_NAME ).users().list().asScala
209209 val usersBasic = UsersKeycloak (usersOrig.collect(each => UserKeycloak (Option (each.getFirstName), Option (each.getLastName), Option (each.getEmail))).toSeq)
210210 complete(HttpResponse (StatusCodes .OK , entity = usersBasic.asJson.noSpaces))
211211 }
0 commit comments