Web ã¢ããªã±ã¼ã·ã§ã³ãä¿è·ããããã® OpenID Connect èªå¯ã³ã¼ãããã¼ã¡ã«ããºã
Quarkus OIDC ã¨ã¯ã¹ãã³ã·ã§ã³ãæä¾ããæ¥çæ¨æºã® OpenID Connect (OIDC) èªå¯ã³ã¼ãããã¼ã¡ã«ããºã ã使ç¨ãã¦ãWeb ã¢ããªã±ã¼ã·ã§ã³ãä¿è·ãããã¨ãã§ãã¾ãã
OIDC èªå¯ã³ã¼ãããã¼ã¡ã«ããºã ã®æ¦è¦
Quarkus OpenID Connect (OIDC) ã¨ã¯ã¹ãã³ã·ã§ã³ã¯ãKeycloak ãªã©ã® OIDC æºæ ã®èªå¯ãµã¼ãã¼ã§ãµãã¼ãããã¦ãã OIDC èªå¯ã³ã¼ãããã¼ã¡ã«ããºã ã使ç¨ãã¦ãã¢ããªã±ã¼ã·ã§ã³ HTTP ã¨ã³ããã¤ã³ããä¿è·ã§ãã¾ãã
èªå¯ã³ã¼ãããã¼ã¡ã«ããºã ã¯ãWeb ã¢ããªã±ã¼ã·ã§ã³ã®ã¦ã¼ã¶ã¼ã Keycloak ãªã©ã® OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ããã¦ãã°ã¤ã³ããããã¨ã§ãã¦ã¼ã¶ã¼ãèªè¨¼ãã¾ãã èªè¨¼å¾ãOIDC ãããã¤ãã¼ã¯ãèªè¨¼ãæåãããã¨ã確èªããèªå¯ã³ã¼ãã使ç¨ãã¦ã¦ã¼ã¶ã¼ãã¢ããªã±ã¼ã·ã§ã³ã«ãªãã¤ã¬ã¯ããã¾ãã æ¬¡ã«ãã¢ããªã±ã¼ã·ã§ã³ã¯ããã®ã³ã¼ãã OIDC ãããã¤ãã¼ã¨äº¤æãã¦ãID ãã¼ã¯ã³ (èªè¨¼ãããã¦ã¼ã¶ã¼ã表ã)ãã¢ã¯ã»ã¹ãã¼ã¯ã³ãããã³ãªãã¬ãã·ã¥ãã¼ã¯ã³ãåå¾ããã¦ã¼ã¶ã¼ã®ã¢ããªã±ã¼ã·ã§ã³ã¸ã®ã¢ã¯ã»ã¹ãèªå¯ãã¾ãã
次ã®å³ã¯ãQuarkus ã«ãããèªå¯ã³ã¼ãããã¼ã¡ã«ããºã ã®æ¦è¦ã示ãã¦ãã¾ãã
-
Quarkus ã¦ã¼ã¶ã¼ã¯ãQuarkus
web-appã¢ããªã±ã¼ã·ã§ã³ã¸ã®ã¢ã¯ã»ã¹ããªã¯ã¨ã¹ããã¾ãã -
Quarkus web-app ã¯ãã¦ã¼ã¶ã¼ãèªå¯ã¨ã³ããã¤ã³ããã¤ã¾ãèªè¨¼ç¨ã® OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ããã¾ãã
-
OIDC ãããã¤ãã¼ã¯ãã¦ã¼ã¶ã¼ããã°ã¤ã³ã¨èªè¨¼ã®ããã³ããã«ãªãã¤ã¬ã¯ããã¾ãã
-
ããã³ããã§ãã¦ã¼ã¶ã¼ã¯èªåã®ã¦ã¼ã¶ã¼ã¯ã¬ãã³ã·ã£ã«ãå ¥åãã¾ãã
-
OIDC ãããã¤ãã¼ã¯ãå ¥åãããã¦ã¼ã¶ã¼ã®ã¯ã¬ãã³ã·ã£ã«ãèªè¨¼ããèªè¨¼ã«æåããã¨èªå¯ã³ã¼ããçºè¡ãããã®ã³ã¼ããã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã¨ãã¦å«ã㦠Quarkus web-app ã«ã¦ã¼ã¶ã¼ããªãã¤ã¬ã¯ããã¾ãã
-
Quarkus web-app ã¯ããã®èªå¯ã³ã¼ãã OIDC ãããã¤ãã¼ã¨äº¤æããIDãã¢ã¯ã»ã¹ãããã³ãªãã¬ãã·ã¥ã®åãã¼ã¯ã³ãåå¾ãã¾ãã
èªå¯ã³ã¼ãããã¼ãå®äºããQuarkus web-app ã¯çºè¡ããããã¼ã¯ã³ã使ç¨ãã¦ã¦ã¼ã¶ã¼ã«é¢ããæ å ±ã«ã¢ã¯ã»ã¹ãããã®ã¦ã¼ã¶ã¼ã«é¢é£ãããã¼ã«ãã¼ã¹ã®èªå¯ãä»ä¸ãã¾ãã çºè¡ããããã¼ã¯ã³ã¯ä»¥ä¸ã®ã¨ããã§ãã
-
IDãã¼ã¯ã³: Quarkus
web-appã¢ããªã±ã¼ã·ã§ã³ã¯ãID ãã¼ã¯ã³ã®ã¦ã¼ã¶ã¼æ å ±ã使ç¨ãã¦ãèªè¨¼ãããã¦ã¼ã¶ã¼ãå®å ¨ã«ãã°ã¤ã³ã§ããããã«ããWeb ã¢ããªã±ã¼ã·ã§ã³ã«ãã¼ã«ãã¼ã¹ã®ã¢ã¯ã»ã¹ãæä¾ãã¾ãã -
ã¢ã¯ã»ã¹ãã¼ã¯ã³: Quarkus web-app ã¯ãã¢ã¯ã»ã¹ãã¼ã¯ã³ã使ç¨ã㦠UserInfo API ã«ã¢ã¯ã»ã¹ããèªè¨¼ãããã¦ã¼ã¶ã¼ã«é¢ããè¿½å æ å ±ãåå¾ããããå¥ã®ã¨ã³ããã¤ã³ãã«ä¼æããããããã¨ãããã¾ãã
-
ãªãã¬ãã·ã¥ãã¼ã¯ã³: (ãªãã·ã§ã³) ID ããã³ã¢ã¯ã»ã¹ãã¼ã¯ã³ã®æå¹æéãåããå ´åãQuarkus web-app ã¯ãªãã¬ãã·ã¥ãã¼ã¯ã³ã使ç¨ãã¦æ°ãã ID ããã³ã¢ã¯ã»ã¹ãã¼ã¯ã³ãåå¾ã§ãã¾ãã
OIDCè¨å®ãããã㣠ã®ãªãã¡ã¬ã³ã¹ã¬ã¤ãããåç §ãã ããã
OIDC èªå¯ã³ã¼ãããã¼ã¡ã«ããºã ã使ç¨ã㦠Web ã¢ããªã±ã¼ã·ã§ã³ãä¿è·ããæ¹æ³ã«ã¤ãã¦ã¯ãOIDC èªå¯ã³ã¼ãããã¼ã使ç¨ãã Web ã¢ããªã±ã¼ã·ã§ã³ã®ä¿è· ãåç §ãã¦ãã ããã
OIDC ãã¢ã©ã¼ãã¼ã¯ã³èªè¨¼ã使ç¨ãã¦ãµã¼ãã¹ã¢ããªã±ã¼ã·ã§ã³ãä¿è·ãããå ´åã¯ãOIDC ãã¢ã©ã¼ãã¼ã¯ã³èªè¨¼ ãåç §ãã¦ãã ããã
ãã«ãããã³ãã¸ã®å¯¾å¿æ¹æ³ã«ã¤ãã¦ã¯ãOpenID Connect (OIDC) ãã«ãããã³ã·ã¼ã®ä½¿ç¨ ã¬ã¤ãããèªã¿ãã ããã
If you are a Vert.x OIDC user, learn about migration options in the Migrate from Vert.x OIDC to Quarkus OIDC guide.
èªå¯ã³ã¼ãããã¼ã¡ã«ããºã ã®å©ç¨ã«ã¤ãã¦
èªè¨¼ã³ã¼ãããã¼ããµãã¼ãããããã®Quarkusã®è¨å®
èªè¨¼ã³ã¼ãããã¼èªè¨¼ãæå¹ã«ããã«ã¯ã quarkus.oidc.application-type ããããã£ã web-app ã«è¨å®ããå¿
è¦ãããã¾ãã
é常ãQuarkus OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ã¿ã¤ãã¯ãQuarkus ã¢ããªã±ã¼ã·ã§ã³ã HTML ãã¼ã¸ãæä¾ããããã³ãã¨ã³ãã¢ããªã±ã¼ã·ã§ã³ã§ãOIDC ã·ã³ã°ã«ãµã¤ã³ãªã³ãã°ã¤ã³ãå¿
è¦ãªå ´åã«è¨å®ããå¿
è¦ãããã¾ãã
Quarkus OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ã§ã¯ãèªå¯ã³ã¼ãããã¼ãã¦ã¼ã¶ã¼èªè¨¼ã®åªå
æ¹æ³ã¨ãã¦å®ç¾©ããã¦ãã¾ãã
ã¢ããªã±ã¼ã·ã§ã³ã HTML ãã¼ã¸ãæä¾ããã¨åæã« REST API ãæä¾ããèªè¨¼ã³ã¼ãããã¼èªè¨¼ã¨ ãã¢ã©ã¢ã¯ã»ã¹ãã¼ã¯ã³èªè¨¼ã® 両æ¹ãå¿
è¦ãªå ´åã代ããã« quarkus.oidc.application-type ããããã£ã hybrid ã«è¨å®ã§ãã¾ãããã®å ´åããã¢ã©ã»ã¢ã¯ã»ã¹ãã¼ã¯ã³ãå«ã Bearer èªè¨¼ã¹ãã¼ã ãæã¤ HTTP Authorization ãªã¯ã¨ã¹ãã»ããããè¨å®ããã¦ããªãå ´åã«ã®ã¿ãèªå¯ã³ã¼ãã»ããã¼ãããªã¬ããã¾ãã
OIDC ãããã¤ãã¼ã¨ã³ããã¤ã³ãã¸ã®ã¢ã¯ã»ã¹è¨å®
OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ã¯ãOIDC ãããã¤ãã¼ã®èªå¯ããã¼ã¯ã³ã JsonWebKey (JWK) ã»ãããããã¦å ´åã«ãã£ã¦ã¯ UserInfoãã¤ã³ããã¹ãã¯ã·ã§ã³ãã»ãã·ã§ã³çµäº (RP-initiated logout) ã¨ã³ããã¤ã³ãã® URL ãå¿
è¦ã¨ãã¾ãã
è¦ç´ã«ããã quarkus.oidc.auth-server-url ã§è¨å®ãããã¨ã§ /.well-known/openid-configuration ã®ãã¹ã追å ãããã¨ã§æ¤åºããã¾ãã
ã¾ãããã£ã¹ã«ããªã¼ã¨ã³ããã¤ã³ããå©ç¨ã§ããªãå ´åãããã£ã¹ã«ããªã¼ã¨ã³ããã¤ã³ãã®ã©ã¦ã³ãããªãããæ¸ããããå ´åã¯ãã¨ã³ããã¤ã³ãã®ãã£ã¹ã«ããªã¼ãç¡å¹ã«ããç¸å¯¾ãã¹å¤ãè¨å®ãããã¨ãã§ãã¾ãã ä¾:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.discovery-enabled=false
# Authorization endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/auth
quarkus.oidc.authorization-path=/protocol/openid-connect/auth
# Token endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/token
quarkus.oidc.token-path=/protocol/openid-connect/token
# JWK set endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/certs
quarkus.oidc.jwks-path=/protocol/openid-connect/certs
# UserInfo endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/userinfo
quarkus.oidc.user-info-path=/protocol/openid-connect/userinfo
# Token Introspection endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/token/introspect
quarkus.oidc.introspection-path=/protocol/openid-connect/token/introspect
# End-session endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/logout
quarkus.oidc.end-session-path=/protocol/openid-connect/logout
ä¸é¨ã® OIDC ãããã¤ãã¼ã¯ãã¡ã¿ãã¼ã¿ãã£ã¹ã«ããªã¼ããµãã¼ããã¦ãã¾ãããèªå¯ã³ã¼ãããã¼ãå®äºããããã¦ã¼ã¶ã¼ãã°ã¢ã¦ããªã©ã®ã¢ããªã±ã¼ã·ã§ã³æ©è½ããµãã¼ããããããããã«å¿ è¦ãªãã¹ã¦ã®ã¨ã³ããã¤ã³ã URL å¤ã¯è¿ãã¾ããã ãã®å¶éãåé¿ããã«ã¯ã次ã®ä¾ã«ç¤ºãããã«ãä¸è¶³ãã¦ããã¨ã³ããã¤ã³ã URL å¤ããã¼ã«ã«ã§è¨å®ãã¾ãã
# Metadata is auto-discovered but it does not return an end-session endpoint URL
quarkus.oidc.auth-server-url=http://localhost:8180/oidcprovider/account
# Configure the end-session URL locally.
# It can be an absolute or relative (to 'quarkus.oidc.auth-server-url') address
quarkus.oidc.end-session-path=logout
æ¤åºãããã¨ã³ããã¤ã³ã URL ããã¼ã«ã«ã® Quarkus ã¨ã³ããã¤ã³ãã§æ©è½ãããããå
·ä½çãªå¤ãå¿
è¦ãªå ´åã¯ããã®åãè¨å®ã使ç¨ãã¦ãã¨ã³ããã¤ã³ã URL ããªã¼ãã¼ã©ã¤ãã§ãã¾ãã
ãã¨ãã°ãã°ãã¼ãã«ã¨ã³ãã»ãã·ã§ã³ã¨ã³ããã¤ã³ãã¨ã¢ããªã±ã¼ã·ã§ã³åºæã®ã¨ã³ãã»ãã·ã§ã³ã¨ã³ããã¤ã³ãã®ä¸¡æ¹ããµãã¼ããããããã¤ãã¼ã¯ã http://localhost:8180/oidcprovider/account/global-logout ã®ãããªã°ãã¼ãã«ã¨ã³ãã»ãã·ã§ã³ URL ãè¿ãã¾ãã
ãã® URL ã¯ãã¦ã¼ã¶ã¼ãç¾å¨ãã°ã¤ã³ãã¦ãããã¹ã¦ã®ã¢ããªã±ã¼ã·ã§ã³ããã¦ã¼ã¶ã¼ããã°ã¢ã¦ããã¾ãã
ãã ããç¾å¨ã®ã¢ããªã±ã¼ã·ã§ã³ã§ç¹å®ã®ã¢ããªã±ã¼ã·ã§ã³ã®ã¿ããã¦ã¼ã¶ã¼ããã°ã¢ã¦ããããå¿
è¦ãããå ´åã¯ã quarkus.oidc.end-session-path=logout ãã©ã¡ã¼ã¿ã¼ãè¨å®ãããã¨ã§ãã°ãã¼ãã«ã¨ã³ãã»ãã·ã§ã³ URL ããªã¼ãã¼ã©ã¤ãã§ãã¾ãã
OIDC ãããã¤ãã¼ã®ã¯ã©ã¤ã¢ã³ãèªè¨¼
OIDC ãããã¤ãã¼ã¯é常ãOIDC ã¨ã³ããã¤ã³ãã¨ããåãããéã«ãã¢ããªã±ã¼ã·ã§ã³ã®èå¥ã¨èªè¨¼ãè¡ãå¿
è¦ãããã¾ãã
Quarkus OIDCãç¹ã« quarkus.oidc.runtime.OidcProviderClient ã¯ã©ã¹ã¯ãèªå¯ã³ã¼ãã IDãã¢ã¯ã»ã¹ããªãã¬ãã·ã¥ãã¼ã¯ã³ã¨äº¤æããå¿
è¦ãããå ´åãã¾ã㯠ID ãã¢ã¯ã»ã¹ãã¼ã¯ã³ããªãã¬ãã·ã¥ã¾ãã¯ã¤ã³ããã¹ãã¯ãããå¿
è¦ãããå ´åã«ãOIDC ãããã¤ãã¼ã§èªè¨¼ãã¾ãã
é常ãã¯ã©ã¤ã¢ã³ã ID ã¨ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ããã¯ãã¢ããªã±ã¼ã·ã§ã³ã OIDC ãããã¤ãã¼ã«ç»é²ããéã«å®ç¾©ããã¾ãã ãã¹ã¦ã® OIDC ã¯ã©ã¤ã¢ã³ãèªè¨¼ ãªãã·ã§ã³ããµãã¼ãããã¦ãã¾ãã ä¾:
client_secret_basic ã®ä¾:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=mysecret
ãããã¯:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
次ã®ä¾ã¯ãã¯ã¬ãã³ã·ã£ã«ãããã¤ãã¼ ããåå¾ããã·ã¼ã¯ã¬ããã示ãã¦ãã¾ãã
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
# This is a key which will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc.credentials.client-secret.provider.key=mysecret-key
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc.credentials.client-secret.provider.name=oidc-credentials-provider
client_secret_post ã®ä¾quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
quarkus.oidc.credentials.client-secret.method=post
client_secret_jwt ã®ä¾ãç½²åã¢ã«ã´ãªãºã 㯠HS256:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
client_secret_jwt ã®ä¾ããã®å ´åãã·ã¼ã¯ã¬ãã㯠ã¯ã¬ãã³ã·ã£ã«ãããã¤ãã¼ ããåå¾ããã¾ããquarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
# This is a key which will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc.credentials.jwt.secret-provider.key=mysecret-key
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc.credentials.jwt.secret-provider.name=oidc-credentials-provider
RS256 ã§ãã private_key_jwt ã®ä¾:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key=Base64-encoded private key representation
private_key_jwt ã®ä¾ãç½²åã¢ã«ã´ãªãºã 㯠RS256:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-file=privateKey.pem
private_key_jwt ã®ä¾ãç½²åã¢ã«ã´ãªãºã ã¯RS256:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-store-file=keystore.pkcs12
quarkus.oidc.credentials.jwt.key-store-password=mypassword
quarkus.oidc.credentials.jwt.key-password=mykeypassword
# Private key alias inside the keystore
quarkus.oidc.credentials.jwt.key-id=mykeyAlias
client_secret_jwtãã¾ã㯠private_key_jwt èªè¨¼æ¹æ³ã使ç¨ãããã¨ã§ãã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ããã OIDC ãããã¤ãã¼ã«éä¿¡ãããªãããããä¸éè
ãæ»æã«ãã£ã¦ã·ã¼ã¯ã¬ãããååããããªã¹ã¯ãåé¿ã§ãã¾ãã
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.source=bearer (1)
quarkus.oidc.credentials.jwt.token-path=/var/run/secrets/tokens (2)
| 1 | JWT ãã¢ã©ã¼ãã¼ã¯ã³ã使ç¨ã㦠OIDC ãããã¤ãã¼ã¯ã©ã¤ã¢ã³ããèªè¨¼ãã¾ãã詳細ã¯ãJWT ã使ç¨ããã¯ã©ã¤ã¢ã³ãèªè¨¼ ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããã |
| 2 | JWT ãã¢ã©ã¼ãã¼ã¯ã³ã¸ã®ãã¹ãQuarkus ã¯ãã¡ã¤ã«ã·ã¹ãã ããæ°ãããã¼ã¯ã³ããã¼ããããã¼ã¯ã³ã®æå¹æéãåããã¨åãã¼ããã¾ãã |
JWT èªè¨¼ã®è¿½å ãªãã·ã§ã³
client_secret_jwtã private_key_jwtãã¾ã㯠Apple ã® post_jwt èªè¨¼æ¹æ³ã使ç¨ããã¦ããå ´åãJWT ç½²åã¢ã«ã´ãªãºã ããã¼èå¥åãaudienceãsubjectãããã³ issuer ãã«ã¹ã¿ãã¤ãºãããã¨ãã§ãã¾ãã
ä¾:
# private_key_jwt client authentication
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-file=privateKey.pem
# This is a token key identifier 'kid' header - set it if your OIDC provider requires it:
# Note if the key is represented in a JSON Web Key (JWK) format with a `kid` property, then
# using 'quarkus.oidc.credentials.jwt.token-key-id' is not necessary.
quarkus.oidc.credentials.jwt.token-key-id=mykey
# Use RS512 signature algorithm instead of the default RS256
quarkus.oidc.credentials.jwt.signature-algorithm=RS512
# The token endpoint URL is the default audience value, use the base address URL instead:
quarkus.oidc.credentials.jwt.audience=${quarkus.oidc-client.auth-server-url}
# custom subject instead of the client id:
quarkus.oidc.credentials.jwt.subject=custom-subject
# custom issuer instead of the client id:
quarkus.oidc.credentials.jwt.issuer=custom-issuer
Apple POST JWT
Apple OIDC ãããã¤ãã¼ã¯ã private_key_jwt èªè¨¼æ¹å¼ã§çæããã JWT ãã·ã¼ã¯ã¬ããã¨ãã client_secret_post æ¹å¼ã使ç¨ãã¦ãã¾ãããApple ã¢ã«ã¦ã³ãåºæã® issuer 㨠subject ã®ã¯ã¬ã¼ã ã使ç¨ãã¦ãã¾ãã
Quarkus Security ã§ã¯ã quarkus-oidc ã¯ã鿍æºã® client_secret_post_jwt èªè¨¼æ¹æ³ããµãã¼ãããã¦ãã¾ãããã®èªè¨¼æ¹æ³ã¯ã以ä¸ã®ããã«è¨å®ã§ãã¾ãã
# Apple provider configuration sets a 'client_secret_post_jwt' authentication method
quarkus.oidc.provider=apple
quarkus.oidc.client-id=${apple.client-id}
quarkus.oidc.credentials.jwt.key-file=ecPrivateKey.pem
quarkus.oidc.credentials.jwt.token-key-id=${apple.key-id}
# Apple provider configuration sets ES256 signature algorithm
quarkus.oidc.credentials.jwt.subject=${apple.subject}
quarkus.oidc.credentials.jwt.issuer=${apple.issuer}
ç¸äº TLS (mTLS)
OIDC ãããã¤ãã¼ã«ãã£ã¦ã¯ãç¸äº TLS èªè¨¼ããã»ã¹ã®ä¸é¨ã¨ãã¦ã¯ã©ã¤ã¢ã³ãã®èªè¨¼ãè¦æ±ããå ´åãããã¾ãã
次ã®ä¾ã¯ã mTLS ããµãã¼ãããããã« quarkus-oidc ãè¨å®ããæ¹æ³ã示ãã¦ãã¾ãã
quarkus.oidc.tls.tls-configuration-name=oidc
# configure hostname verification if necessary
#quarkus.tls.oidc.hostname-verification-algorithm=NONE
# Keystore configuration
quarkus.tls.oidc.key-store.p12.path=client-keystore.p12
quarkus.tls.oidc.key-store.p12.password=${key-store-password}
# Add more keystore properties if needed:
#quarkus.tls.oidc.key-store.p12.alias=keyAlias
#quarkus.tls.oidc.key-store.p12.alias-password=keyAliasPassword
# Truststore configuration
quarkus.tls.oidc.trust-store.p12.path=client-truststore.p12
quarkus.tls.oidc.trust-store.p12.password=${trust-store-password}
# Add more truststore properties if needed:
#quarkus.tls.oidc.trust-store.p12.alias=certAlias
POST ã¯ã¨ãªã¼
Strava OAuth2 ãããã¤ãã¼ ãªã©ã®ä¸é¨ã®ãããã¤ãã¼ã§ã¯ãã¯ã©ã¤ã¢ã³ãã¯ã¬ãã³ã·ã£ã«ã HTTP POST ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã¨ãã¦éä¿¡ããå¿ è¦ãããã¾ãã
quarkus.oidc.provider=strava
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
quarkus.oidc.credentials.client-secret.method=query
ã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ã³ããã¤ã³ãèªè¨¼
ä¸é¨ã® OIDC ãããã¤ãã¼ã§ã¯ãBasic èªè¨¼ã¨ client_id ããã³ client_secret ã¨ã¯ç°ãªãã¯ã¬ãã³ã·ã£ã«ã使ç¨ãã¦ãã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ã³ããã¤ã³ãã¸ã®èªè¨¼ãè¡ãå¿
è¦ãããã¾ãã
OIDC provider client authentication ã»ã¯ã·ã§ã³ã«è¨è¼ããã¦ããããã«ã以åã«ã»ãã¥ãªãã£ã¼èªè¨¼ãè¨å®ãã¦ã client_secret_basic ã¾ã㯠client_secret_post ã¯ã©ã¤ã¢ã³ãèªè¨¼ã¡ã½ããããµãã¼ããã¦ããå ´åã¯ã次ã®ããã«è¿½å ã®è¨å®ãé©ç¨ãããã¨ãæ¨å¥¨ãã¾ãã
ãã¼ã¯ã³ãã¤ã³ããã¹ãã¯ã·ã§ã³ããå¿
è¦ããããã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ã³ããã¤ã³ãåºæã®èªè¨¼ã¡ã«ããºã ãå¿
è¦ãªå ´åã¯ã quarkus-oidc ã以ä¸ã®ããã«è¨å®ã§ãã¾ãã
quarkus.oidc.introspection-credentials.name=introspection-user-name
quarkus.oidc.introspection-credentials.secret=introspection-user-secret
OIDC ãªã¯ã¨ã¹ããã£ã«ã¿ã¼
You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more OidcRequestFilter implementations, which can update or add new request headers, customize a request body and can also log requests.
ä¾:
package io.quarkus.it.keycloak;
import io.quarkus.oidc.OidcConfigurationMetadata;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.smallrye.mutiny.Uni;
@ApplicationScoped
@Unremovable
public class OidcTokenRequestCustomizer implements OidcRequestFilter {
@Override
public Uni<Void> filter(OidcRequestFilterContext requestContext) {
OidcConfigurationMetadata metadata = requestContext.contextProperties().get(OidcConfigurationMetadata.class.getName()); (1)
// Metadata URI is absolute, request URI value is relative
if (metadata.getTokenUri().endsWith(requestContext.request().uri())) { (2)
requestContext.request().putHeader("TokenGrantDigest", calculateDigest(requestContext.requestBody().toString()));
}
return Uni.createFrom().voidItem();
}
private String calculateDigest(String bodyString) {
// Apply the required digest algorithm to the body string
}
}
| 1 | ãµãã¼ãããã¦ãããã¹ã¦ã® OIDC ã¨ã³ããã¤ã³ãã¢ãã¬ã¹ãå«ã¾ãã OidcConfigurationMetadata ãåå¾ãã¾ãã |
| 2 | OidcConfigurationMetadata ã使ç¨ãã¦ãOIDC ãã¼ã¯ã³ã¨ã³ããã¤ã³ãã¸ã®ãªã¯ã¨ã¹ãã®ã¿ããã£ã«ã¿ã¼ãã¾ãã |
ã¾ãã¯ã @OidcEndpoint ã¢ããã¼ã·ã§ã³ã使ç¨ãã¦ããã®ãã£ã«ã¿ã¼ã OIDC æ¤åºã¨ã³ããã¤ã³ãããã®ã¬ã¹ãã³ã¹ã«ã®ã¿é©ç¨ãããã¨ãã§ãã¾ãã
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcRequestContext;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.smallrye.mutiny.Uni;
@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.DISCOVERY) (1)
public class OidcDiscoveryRequestCustomizer implements OidcRequestFilter {
@Override
public Uni<Void> filter(OidcRequestFilterContext requestContext) {
requestContext.request().putHeader("Discovery", "OK");
return Uni.createFrom().voidItem();
}
}
| 1 | ãã®ãã£ã«ã¿ã¼ããOIDC æ¤åºã¨ã³ããã¤ã³ãã®ã¿ã対象ã¨ãããªã¯ã¨ã¹ãã«å¶éãã¾ãã |
OidcRequestContextProperties ã使ç¨ãã¦ããªã¯ã¨ã¹ãããããã£ã¼ã«ã¢ã¯ã»ã¹ã§ãã¾ãã
ç¾å¨ã tenand_id ãã¼ã使ç¨ã㦠OIDC ããã³ã ID ã«ã¢ã¯ã»ã¹ãã grant_type ãã¼ã使ç¨ã㦠OIDC ãããã¤ãã¼ããã¼ã¯ã³ãåå¾ããããã«ä½¿ç¨ããã°ã©ã³ãã¿ã¤ãã«ã¢ã¯ã»ã¹ã§ãã¾ãã
ãã¼ã¯ã³ã¨ã³ããã¤ã³ãã«ãªã¯ã¨ã¹ãããå ´åã grant_type 㯠authorization_code ã¾ã㯠refresh_token ã°ã©ã³ãã¿ã¤ãã®ããããã«ã®ã¿è¨å®ã§ãã¾ãããã以å¤ã®å ´å㯠null ã«ãªãã¾ãã
OidcRequestFilter can customize a request body by preparing an instance of io.vertx.mutiny.core.buffer.Buffer
and setting it on a request context, for example:
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcRequestContextProperties;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.buffer.Buffer;
@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.TOKEN)
public class TokenRequestFilter implements OidcRequestFilter {
@Override
public Uni<Void> filter(OidcRequestFilterContext rc) {
// Add more required properties to the token request
rc.requestBody(Buffer.buffer(rc.requestBody().toString() + "&opaque_token_param=opaque_token_value"));
return Uni.createFrom().voidItem();
}
}
OIDC ã¬ã¹ãã³ã¹ãã£ã«ã¿ã¼
1 ã¤ä»¥ä¸ã® OidcResponseFilter å®è£
ãç»é²ãããã¨ã§ãOIDC ãããã¤ãã¼ããã®ã¬ã¹ãã³ã¹ããã£ã«ã¿ãªã³ã°ã§ãã¾ããããã«ãããã¬ã¹ãã³ã¹ã®ã¹ãã¼ã¿ã¹ããããã¼ãæ¬æããã§ãã¯ãã¦ããã°ã«è¨é²ãããããã®ä»ã®ã¢ã¯ã·ã§ã³ãå®è¡ãããã§ãã¾ãã
ãã¹ã¦ã® OIDC ã¬ã¹ãã³ã¹ãã¤ã³ã¿ã¼ã»ããããåä¸ã®ãã£ã«ã¿ã¼ã使ç¨ãããã¨ãã @OidcEndpoint ã¢ããã¼ã·ã§ã³ã使ç¨ãã¦ãã®ãã£ã«ã¿ã¼ãç¹å®ã®ã¨ã³ããã¤ã³ãã¬ã¹ãã³ã¹ã«ã®ã¿é©ç¨ãããã¨ãã§ãã¾ããä¾:
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.logging.Log;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcResponseFilter;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.oidc.runtime.OidcUtils;
@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.TOKEN) (1)
public class TokenEndpointResponseFilter implements OidcResponseFilter {
@Override
public Uni<Void> filter(OidcResponseFilterContext rc) {
String contentType = rc.responseHeaders().get("Content-Type"); (2)
if (contentType.equals("application/json")
&& OidcConstants.AUTHORIZATION_CODE.equals(rc.requestProperties().get(OidcConstants.GRANT_TYPE)) (3)
&& "code-flow-user-info-cached-in-idtoken".equals(rc.requestProperties().get(OidcUtils.TENANT_ID_ATTRIBUTE)) (3)
&& rc.responseBody().toJsonObject().containsKey("id_token")) { (4)
Log.debug("Authorization code completed for tenant 'code-flow-user-info-cached-in-idtoken'");
}
return Uni.createFrom().voidItem();
}
}
| 1 | ãã®ãã£ã«ã¿ã¼ããOIDC ãã¼ã¯ã³ã¨ã³ããã¤ã³ãã®ã¿ã対象ã¨ãããªã¯ã¨ã¹ãã«å¶éãã¾ãã |
| 2 | ã¬ã¹ãã³ã¹ã® Content-Type ãããã¼ã確èªãã¾ãã |
| 3 | OidcRequestContextProperties ãªã¯ã¨ã¹ãããããã£ã¼ã使ç¨ãã¦ã code-flow-user-info-cached-in-idtoken ããã³ãã® authorization_code ãã¼ã¯ã³ã°ã©ã³ãã¬ã¹ãã³ã¹ã®ã¿ããã§ãã¯ãã¾ãã |
| 4 | ã¬ã¹ãã³ã¹ JSON ã« id_token ããããã£ã¼ãå«ã¾ãã¦ãããã¨ã確èªãã¾ãã |
OidcResponseFilter can customize a response body by preparing an instance of io.vertx.mutiny.core.buffer.Buffer
and setting it on a response context, for example:
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcRequestContextProperties;
import io.quarkus.oidc.common.OidcResponseFilter;
import io.smallrye.mutiny.Uni;
import io.vertx.core.json.JsonObject;
import io.vertx.mutiny.core.buffer.Buffer;
@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.TOKEN)
public class TokenResponseFilter implements OidcResponseFilter {
@Override
public Uni<Void> filter(OidcResponseFilterContext rc) {
JsonObject body = rc.responseBody().toJsonObject();
// JSON `scope` property has multiple values separated by a comma character.
// It must be replaced with a space character.
String scope = body.getString("scope");
body.put("scope", scope.replace(",", " "));
rc.responseBody(Buffer.buffer(body.toString()));
return Uni.createFrom().voidItem();
}
}
Restricting OIDC request and response filters to authorization code flow
When you have both the authorization code and bearer access token flows supported by multiple OIDC tenants and the filters have to deal with a flow specific logic, you can instead have them restricted to the authorization code flow with the io.quarkus.oidc.AuthorizationCodeFlow annotation and the bearer access token flow with the 'io.quarkus.oidc.BearerTokenAuthentication' annotation.
ä¾:
package io.quarkus.it.keycloak;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.AuthorizationCodeFlow;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@AuthorizationCodeFlow
@ApplicationScoped
@Unremovable
public class CustomOidcRequestFilter implements OidcRequestFilter {
@Override
public Uni<Void> filter(OidcRequestFilterContext requestContext) {
requestContext.request().putHeader("custom-header-name", "custom-header-value");
return Uni.createFrom().voidItem();
}
}
OIDC ãããã¤ãã¼ã¸ã®ãªãã¤ã¬ã¯ã㨠OIDC ãããã¤ãã¼ããã®ãªãã¤ã¬ã¯ã
ã¦ã¼ã¶ã¼ãèªè¨¼ã®ããã« OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ããããå ´åããªãã¤ã¬ã¯ã URL ã«ã¯ redirect_uri ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ãå«ã¾ãã¾ãããã®ãã©ã¡ã¼ã¿ã¼ã¯ãèªè¨¼å®äºå¾ã®ã¦ã¼ã¶ã¼ã®ãªãã¤ã¬ã¯ãå
ããããã¤ãã¼ã«ç¤ºãã¾ãã
ããã§ã®ãªãã¤ã¬ã¯ãå
㯠Quarkus ã¢ããªã±ã¼ã·ã§ã³ã«ãªãã¾ãã
Quarkus ã¯ãããã©ã«ãã§ãã®ãã©ã¡ã¼ã¿ã¼ãç¾å¨ã®ã¢ããªã±ã¼ã·ã§ã³ãªã¯ã¨ã¹ã URL ã«è¨å®ãã¾ãã
ãã¨ãã°ãã¦ã¼ã¶ã¼ã http://localhost:8080/service/1 ã«ãã Quarkus ãµã¼ãã¹ã¨ã³ããã¤ã³ãã«ã¢ã¯ã»ã¹ãããã¨ãã¦ããå ´åã redirect_uri ãã©ã¡ã¼ã¿ã¼ã¯ http://localhost:8080/service/1 ã«è¨å®ããã¾ãã
åæ§ã«ããªã¯ã¨ã¹ã URL ã http://localhost:8080/service/2 ã®å ´åã redirect_uri ãã©ã¡ã¼ã¿ã¼ã¯ http://localhost:8080/service/2 ã«è¨å®ããã¾ãã
ä¸é¨ã® OIDC ãããã¤ãã¼ã§ã¯ããã¹ã¦ã®ãªãã¤ã¬ã¯ã URL ã®ç¹å®ã®ã¢ããªã±ã¼ã·ã§ã³ã«å¯¾ãã¦ã redirect_uri ãåãå¤ (ä¾: http://localhost:8080/service/callback) ãæã¤ãã¨ãå¿
è¦ã§ãã
ãã®ãããªå ´åã¯ã quarkus.oidc.authentication.redirect-path ããããã£ã¼ãè¨å®ããå¿
è¦ãããã¾ãã
ãã¨ãã°ã quarkus.oidc.authentication.redirect-path=/service/callback ã®å ´åãQuarkus 㯠redirect_uri ãã©ã¡ã¼ã¿ã¼ã http://localhost:8080/service/callback ãªã©ã®çµ¶å¯¾ URL ã«è¨å®ãã¾ããããã¯ãç¾å¨ã®ãªã¯ã¨ã¹ã URL ã«é¢ä¿ãªãåãã«ãªãã¾ãã
quarkus.oidc.authentication.redirect-path ãè¨å®ããã¦ããããã¦ã¼ã¶ã¼ã http://localhost:8080/service/callback ãªã©ã®ä¸æã®ã³ã¼ã«ãã㯠URL ã«ãªãã¤ã¬ã¯ããããå¾ã«å
ã®ãªã¯ã¨ã¹ã URL ã復å
ããå¿
è¦ãããå ´åã¯ã quarkus.oidc.authentication.restore-path-after-redirect ããããã£ã¼ã true ã«è¨å®ãã¾ãã
ããã«ããã http://localhost:8080/service/1 ãªã©ã®ãªã¯ã¨ã¹ã URL ã復å
ããã¾ãã
èªè¨¼ãªã¯ã¨ã¹ãã®ã«ã¹ã¿ãã¤ãº
ããã©ã«ãã§ã¯ãã¦ã¼ã¶ã¼ãèªè¨¼ã®ããã« OIDC ãããã¤ãã¼ã®èªå¯ã¨ã³ããã¤ã³ãã«ãªãã¤ã¬ã¯ããããéã«ã response_type (code ã«è¨å®)ã scope (openid ã«è¨å®)ã client_idã redirect_uriãããã³ state ããããã£ã¼ã®ã¿ã HTTP ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã¨ãã¦ãOIDC ãããã¤ãã¼ã®èªå¯ã¨ã³ããã¤ã³ãã«æ¸¡ããã¾ãã
quarkus.oidc.authentication.extra-params ã使ç¨ãã¦ãããã«ããããã£ã¼ã追å ã§ãã¾ãã
ãã¨ãã°ãä¸é¨ã® OIDC ãããã¤ãã¼ã¯ããªãã¤ã¬ã¯ã URI ã®ãã©ã°ã¡ã³ãã®ä¸é¨ã¨ãã¦èªå¯ã³ã¼ããè¿ããã¨ã鏿ããå ´åããããããã«ããèªè¨¼ããã»ã¹ã䏿ããã¾ãã
次ã®ä¾ã¯ããã®åé¡ãåé¿ããæ¹æ³ã示ãã¦ãã¾ãã
quarkus.oidc.authentication.extra-params.response_mode=query
ã¾ããã«ã¹ã¿ã ã® OidcRedirectFilter ã使ç¨ãã¦ãOIDC èªå¯ã¨ã³ããã¤ã³ãã¸ã®ãªãã¤ã¬ã¯ããå«ã OIDC ãªãã¤ã¬ã¯ããã«ã¹ã¿ãã¤ãºããæ¹æ³ã«ã¤ãã¦ã¯ãOIDC ãªãã¤ã¬ã¯ããã£ã«ã¿ã¼ ã»ã¯ã·ã§ã³ãåç
§ãã¦ãã ããã
èªè¨¼ã¨ã©ã¼ã¬ã¹ãã³ã¹ã®ã«ã¹ã¿ãã¤ãº
ã¦ã¼ã¶ã¼ã OIDC èªå¯ã¨ã³ããã¤ã³ãã«ãªãã¤ã¬ã¯ããããQuarkus ã¢ããªã±ã¼ã·ã§ã³ãèªè¨¼ããå¿
è¦ã«å¿ãã¦èªå¯ããå ´åããã®ãªãã¤ã¬ã¯ããªã¯ã¨ã¹ãã¯ããªãã¤ã¬ã¯ã URI ã«ç¡å¹ãªã¹ã³ã¼ããå«ã¾ãã¦ããå ´åãªã©ã«å¤±æããå¯è½æ§ãããã¾ãã
ãã®ãããªå ´åããããã¤ãã¼ã¯ãæ³å®ããã code ãã©ã¡ã¼ã¿ã¼ã§ã¯ãªãã error ããã³ error_description ãã©ã¡ã¼ã¿ã¼ã使ç¨ãã¦ã¦ã¼ã¶ã¼ã Quarkus ã«ãªãã¤ã¬ã¯ããã¾ãã
ããã¯ããã¨ãã°ããããã¤ãã¼ã¸ã®ãªãã¤ã¬ã¯ãã«ç¡å¹ãªã¹ã³ã¼ãã¾ãã¯ãã®ä»ã®ç¡å¹ãªãã©ã¡ã¼ã¿ã¼ãå«ã¾ãã¦ããå ´åã«çºçããå¯è½æ§ãããã¾ãã
ãã®ãããªå ´åãããã©ã«ãã§ HTTP 401 ã¨ã©ã¼ãè¿ããã¾ãã
ãã ããã¦ã¼ã¶ã¼ã«ããããããã¨ã©ã¼ã¡ãã»ã¼ã¸ãè¿ãããã«ãã«ã¹ã¿ã ãããªãã¯ã¨ã©ã¼ã¨ã³ããã¤ã³ããå¼ã³åºãããã«ãªã¯ã¨ã¹ãã§ãã¾ãã
ãããè¡ãã«ã¯ã quarkus.oidc.authentication.error-path ããããã£ã¼ã以ä¸ã®ããã«è¨å®ãã¾ãã
quarkus.oidc.authentication.error-path=/error
ããããã£ã¼ããã©ã¯ã¼ãã¹ã©ãã·ã¥ (/) æåã§å§ã¾ãããã¹ãç¾å¨ã®ã¨ã³ããã¤ã³ãã®ãã¼ã¹ URI ã¨ç¸å¯¾çã§ãããã¨ã確èªãã¾ãã
ãã¨ãã°ãããã '/error' ã«è¨å®ãããç¾å¨ã®ãªã¯ã¨ã¹ã URI ã https://localhost:8080/callback?error=invalid_scope ã®å ´åãæçµçã« https://localhost:8080/error?error=invalid_scope ã¸ãªãã¤ã¬ã¯ãããã¾ãã
|
ã¦ã¼ã¶ã¼ããã®ãã¼ã¸ã«ãªãã¤ã¬ã¯ãããã¦åèªè¨¼ãããªãããã«ããã«ã¯ããã®ã¨ã©ã¼ã¨ã³ããã¤ã³ãããããªãã¯ãªã½ã¼ã¹ã§ãããã¨ã確èªãã¾ãã |
OIDC ãªãã¤ã¬ã¯ããã£ã«ã¿ã¼
1 ã¤ä»¥ä¸ã® io.quarkus.oidc.OidcRedirectFilter å®è£
ãç»é²ãã¦ãOIDC èªå¯ããã³ãã°ã¢ã¦ãã¨ã³ããã¤ã³ãã¸ã® OIDC ãªãã¤ã¬ã¯ãã ãã§ãªããã«ã¹ã¿ã ã¨ã©ã¼ããã³ã»ãã·ã§ã³æéåããã¼ã¸ã¸ã®ãã¼ã«ã«ãªãã¤ã¬ã¯ãããã£ã«ã¿ãªã³ã°ã§ãã¾ããã«ã¹ã¿ã OidcRedirectFilter ã¯ãããã«ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ããã³ã¬ã¹ãã³ã¹ãããã¼ã追å ãã¦ãæ°ãã Cookie ãè¨å®ã§ãã¾ãã
ãã¨ãã°ã次ã®åç´ãªã«ã¹ã¿ã OidcRedirectFilter ã¯ãQuarkus OIDC ã§å®è¡ã§ãããã¹ã¦ã®ãªãã¤ã¬ã¯ããªã¯ã¨ã¹ãã«å¯¾ãã¦è¿½å ã®ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã¨ã«ã¹ã¿ã ã¬ã¹ãã³ã¹ãããã¼ã追å ãã¾ãã
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.OidcRedirectFilter;
@ApplicationScoped
@Unremovable
public class GlobalOidcRedirectFilter implements OidcRedirectFilter {
@Override
public void filter(OidcRedirectContext context) {
if (context.redirectUri().contains("/session-expired-page")) {
context.additionalQueryParams().add("redirect-filtered", "true,"); (1)
context.routingContext().response().putHeader("Redirect-Filtered", "true"); (2)
}
}
}
| 1 | ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã追å ãã¾ããã¯ã¨ãªã¼ã®ååã¨å¤ã¯ Quarkus OIDC ã«ãã£ã¦ URL ã¨ã³ã³ã¼ãããããã¨ã«æ³¨æãã¦ãã ããããã®å ´åããªãã¤ã¬ã¯ã URI ã« redirect-filtered=true%20C ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã追å ããã¾ãã |
| 2 | ã«ã¹ã¿ã HTTP ã¬ã¹ãã³ã¹ãããã¼ã追å ãã¾ãã |
OIDC èªå¯ãã¤ã³ãã®è¿½å ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ãè¨å®ããæ¹æ³ã«ã¤ãã¦ã¯ãèªè¨¼ãªã¯ã¨ã¹ãã®ã«ã¹ã¿ãã¤ãº ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããã
ãã¼ã«ã«ã¨ã©ã¼ããã³ã»ãã·ã§ã³æéåããã¼ã¸ç¨ã®ã«ã¹ã¿ã OidcRedirectFilter ã«ããããã®ãããªãã¼ã¸ã®çæã«å½¹ç«ã¤ã»ãã¥ã¢ãª Cookie ã使ãããã¨ãã§ãã¾ãã
ãã¨ãã°ãã»ãã·ã§ã³ã®æå¹æéãåããç¾å¨ã®ã¦ã¼ã¶ã¼ãã http://localhost:8080/session-expired-page ã§å©ç¨ã§ããã«ã¹ã¿ã ã»ãã·ã§ã³æéåããã¼ã¸ã«ãªãã¤ã¬ã¯ãããå¿
è¦ãããã¨ãã¾ããæ¬¡ã®ã«ã¹ã¿ã OidcRedirectFilter ã¯ãOIDC ããã³ãã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ããã使ç¨ãã¦ãã«ã¹ã¿ã session_expired Cookie å
ã®ã¦ã¼ã¶ã¼åãæå·åãã¾ãã
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.jwt.Claims;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.Redirect;
import io.quarkus.oidc.Redirect.Location;
import io.quarkus.oidc.TenantFeature;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.jwt.build.Jwt;
@ApplicationScoped
@Unremovable
@TenantFeature("tenant-refresh")
@Redirect(Location.SESSION_EXPIRED_PAGE) (1)
public class SessionExpiredOidcRedirectFilter implements OidcRedirectFilter {
@Override
public void filter(OidcRedirectContext context) {
if (context.redirectUri().contains("/session-expired-page")) {
AuthorizationCodeTokens tokens = context.routingContext().get(AuthorizationCodeTokens.class.getName()); (2)
String userName = OidcUtils.decodeJwtContent(tokens.getIdToken()).getString(Claims.preferred_username.name()); (3)
String jwe = Jwt.preferredUserName(userName).jwe()
.encryptWithSecret(context.oidcTenantConfig().credentials.secret.get()); (4)
OidcUtils.createCookie(context.routingContext(), context.oidcTenantConfig(), "session_expired",
jwe + "|" + context.oidcTenantConfig().tenantId.get(), 10); (5)
}
}
}
| 1 | ãã®ãªãã¤ã¬ã¯ããã£ã«ã¿ã¼ã¯ãã»ãã·ã§ã³æéåããã¼ã¸ã¸ã®ãªãã¤ã¬ã¯ãä¸ã«ã®ã¿å¼ã³åºãããããã«ãã¦ãã ããã |
| 2 | æéåãã«ãªã£ãã»ãã·ã§ã³ã«é¢é£ä»ãããã AuthorizationCodeTokens ãã¼ã¯ã³ã«ã RoutingContext 屿§ã¨ãã¦ã¢ã¯ã»ã¹ãã¾ãã |
| 3 | ID ãã¼ã¯ã³ã¯ã¬ã¼ã ããã³ã¼ãããã¦ã¼ã¶ã¼åãåå¾ãã¾ãã |
| 4 | ç¾å¨ã® OIDC ããã³ãã®ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ããã§æå·åããã JWT ãã¼ã¯ã³ã«ã¦ã¼ã¶ã¼åãä¿åãã¾ãã |
| 5 | æå·åããããã¼ã¯ã³ã¨ããã³ã ID ãåºåãæå "|" ã使ç¨ãã¦çµåãã5 ç§éæå¹ãªã«ã¹ã¿ã session_expired Cookie ã使ãã¾ããã«ã¹ã¿ã Cookie ã«ããã³ã ID ãè¨é²ããã¨ããã«ãããã³ã OIDC ã»ããã¢ããã§æ£ããã»ãã·ã§ã³æéåããã¼ã¸ãçæããå ´åã«å½¹ç«ã¡ã¾ãã |
次ã«ããã® Cookie ããã»ãã·ã§ã³æéåããã¼ã¸ãçæãããããªã㯠JAX-RS ãªã½ã¼ã¹ã§ä½¿ç¨ãã¦ããã®ã¦ã¼ã¶ã¼ã¨å¯¾å¿ãã OIDC ããã³ãã«åããããã¼ã¸ã使ã§ãã¾ããæ¬¡ã«ä¾ã示ãã¾ãã
package io.quarkus.it.keycloak;
import jakarta.inject.Inject;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.runtime.OidcUtils;
import io.quarkus.oidc.runtime.TenantConfigBean;
import io.smallrye.jwt.auth.principal.DefaultJWTParser;
import io.vertx.ext.web.RoutingContext;
@Path("/session-expired-page")
public class SessionExpiredResource {
@Inject
RoutingContext context;
@Inject
TenantConfigBean tenantConfig; (1)
@GET
public String sessionExpired(@CookieParam("session_expired") String sessionExpired) throws Exception {
// Cookie format: jwt|<tenant id>
String[] pair = sessionExpired.split("\\|"); (2)
OidcTenantConfig oidcConfig = tenantConfig.getStaticTenantsConfig().get(pair[1]).getOidcTenantConfig(); (3)
JsonWebToken jwt = new DefaultJWTParser().decrypt(pair[0], oidcConfig.credentials.secret.get()); (4)
OidcUtils.removeCookie(context, oidcConfig, "session_expired"); (5)
return jwt.getClaim(Claims.preferred_username) + ", your session has expired. "
+ "Please login again at http://localhost:8081/" + oidcConfig.tenantId.get(); (6)
}
}
| 1 | ç¾å¨ã®ãã¹ã¦ã® OIDC ããã³ãè¨å®ã«ã¢ã¯ã»ã¹ããããã«ä½¿ç¨ã§ãã TenantConfigBean ãæ³¨å
¥ãã¾ãã |
| 2 | ã«ã¹ã¿ã Cookie å¤ã 2 ã¤ã®é¨åã«åå²ãã¾ããæåã®é¨åã¯æå·åããããã¼ã¯ã³ãæå¾ã®é¨åã¯ããã³ã ID ã§ãã |
| 3 | OIDC ããã³ãè¨å®ãåå¾ãã¾ãã |
| 4 | OIDC ããã³ãã®ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ããã使ç¨ã㦠Cookie å¤ã復å·åãã¾ãã |
| 5 | ã«ã¹ã¿ã Cookie ãåé¤ãã¾ãã |
| 6 | 復å·åããããã¼ã¯ã³å ã®ã¦ã¼ã¶ã¼åã¨ããã³ã ID ã使ç¨ãã¦ããµã¼ãã¹æéåããã¼ã¸ã®ã¬ã¹ãã³ã¹ãçæãã¾ãã |
Authentication completion actions
Sometimes you may need to perform one or more actions upon a successful authorization code flow completion only.
One way to achieve it is to listen to OIDC events such as a user login. However, when an even listener must perform an asycnhronous action, this action will be completed some time after the request that sent the event was allowed to proceed.
When you need an action to be completed before the current request is allowed to proceed, especially when an action requires a blocking call, register one or more io.quarkus.oidc.AuthenticationCompletionAction interface implementations.
ä¾:
package io.quarkus.oidc.test;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.AuthenticationCompletionAction;
import io.smallrye.mutiny.Uni;
@ApplicationScoped
@Unremovable
public class CustomAuthenticationCompletionAction implements AuthenticationCompletionAction {
@Override
public Uni<Void> action(AuthenticationCompletionContext authCompletionContext) {
// perform a required action
return Uni.createFrom().voidItem();
}
}
èªå¯ãã¼ã¿ã¸ã®ã¢ã¯ã»ã¹
èªå¯ã«é¢ããæ å ±ã«ã¯ããã¾ãã¾ãªæ¹æ³ã§ã¢ã¯ã»ã¹ã§ãã¾ãã
ID ããã³ã¢ã¯ã»ã¹ãã¼ã¯ã³ã¸ã®ã¢ã¯ã»ã¹
OIDC ã³ã¼ãèªè¨¼ã¡ã«ããºã ã¯ãèªå¯ã³ã¼ãããã¼ä¸ã« 3 ã¤ã®ãã¼ã¯ã³ IDãã¼ã¯ã³ãã¢ã¯ã»ã¹ãã¼ã¯ã³ããªãã¬ãã·ã¥ãã¼ã¯ã³ãåå¾ãã¾ãã
ID ãã¼ã¯ã³ã¯å¸¸ã« JWT ãã¼ã¯ã³ã§ãããJWT ã¯ã¬ã¼ã ã«ããã¦ã¼ã¶ã¼èªè¨¼ã表ãã¾ãã
ããã使ã£ã¦çºè¡å
ã® OIDC ã¨ã³ããã¤ã³ããã¦ã¼ã¶ã¼åããã®ä» ã¯ã¬ã¼ã ã¨å¼ã°ããæ
å ±ãåå¾ãããã¨ãã§ãã¾ãã
JsonWebToken ã« IdToken ã¨ãã修飾åãã¤ãããã¨ã§ãID ãã¼ã¯ã³ã®ã¯ã¬ã¼ã ã«ã¢ã¯ã»ã¹ãããã¨ãã§ãã¾ãã
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.Authenticated;
@Path("/web-app")
@Authenticated
public class ProtectedResource {
@Inject
@IdToken
JsonWebToken idToken;
@GET
public String getUserName() {
return idToken.getName();
}
}
OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ã¯é常ãã¢ã¯ã»ã¹ãã¼ã¯ã³ã使ç¨ãã¦ãç¾å¨ãã°ã¤ã³ãã¦ããã¦ã¼ã¶ã¼ã«ä»£ãã£ã¦ä»ã®ã¨ã³ããã¤ã³ãã«ã¢ã¯ã»ã¹ãã¾ãã
raw ã¢ã¯ã»ã¹ãã¼ã¯ã³ã«ã¯æ¬¡ã®ããã«ã¢ã¯ã»ã¹ã§ãã¾ãã
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.security.Authenticated;
@Path("/web-app")
@Authenticated
public class ProtectedResource {
@Inject
JsonWebToken accessToken;
// or
// @Inject
// AccessTokenCredential accessTokenCredential;
@GET
public String getReservationOnBehalfOfUser() {
String rawAccessToken = accessToken.getRawToken();
//or
//String rawAccessToken = accessTokenCredential.getToken();
// Use the raw access token to access a remote endpoint.
// For example, use RestClient to set this token as a `Bearer` scheme value of the HTTP `Authorization` header:
// `Authorization: Bearer rawAccessToken`.
return getReservationfromRemoteEndpoint(rawAccesstoken);
}
}
|
èªå¯ã³ã¼ãããã¼ã¢ã¯ã»ã¹ãã¼ã¯ã³ã |
|
|
JsonWebToken 㨠AccessTokenCredential ã®æ³¨å
¥ã¯ã @RequestScoped 㨠@ApplicationScoped ã®ä¸¡æ¹ã®ã³ã³ããã¹ãã§ãµãã¼ãããã¦ãã¾ãã
Quarkus OIDC ã¯ãsession management ããã»ã¹ã®ä¸ç°ã¨ãã¦ããªãã¬ãã·ã¥ãã¼ã¯ã³ã使ç¨ããç¾å¨ã® ID ã¨ã¢ã¯ã»ã¹ãã¼ã¯ã³ãæ´æ°ãã¾ãã
ã¦ã¼ã¶ã¼æ å ±
ID ãã¼ã¯ã³ãç¾å¨èªè¨¼ããã¦ããã¦ã¼ã¶ã¼ã«é¢ããååãªæ
å ±ãæä¾ããªãå ´åã¯ã UserInfo ã¨ã³ããã¤ã³ããã詳細æ
å ±ãåå¾ã§ãã¾ãã
quarkus.oidc.authentication.user-info-required=true ããããã£ã¼ãè¨å®ãã¦ãOIDC UserInfo ã¨ã³ããã¤ã³ããã UserInfo JSON ãªãã¸ã§ã¯ãããªã¯ã¨ã¹ããã¾ãã
èªå¯ã³ã¼ãã®ä»ä¸å¿çã§è¿ãããã¢ã¯ã»ã¹ãã¼ã¯ã³ã使ç¨ãã¦ãOIDC ãããã¤ãã¼ã® UserInfo ã¨ã³ããã¤ã³ãã«ãªã¯ã¨ã¹ããéä¿¡ããã io.quarkus.oidc.UserInfo (åç´ãª jakarta.json.JsonObject ã©ããã¼) ãªãã¸ã§ã¯ãã使ããã¾ãã
io.quarkus.oidc.UserInfo ã¯ãSecurityIdentity userinfo 屿§ã¨ãã¦æ³¨å
¥ã¾ãã¯ã¢ã¯ã»ã¹ã§ãã¾ãã
quarkus.oidc.authentication.user-info-required ã¯ã次ã®ããããã®æ¡ä»¶ãæºããããå ´åã«èªåçã«æå¹ã«ãªãã¾ãã
-
quarkus.oidc.roles.sourceãuserinfoã«è¨å®ããã¦ããå ´åãã¾ãã¯quarkus.oidc.token.verify-access-token-with-user-infoãtrueã«è¨å®ããã¦ããå ´åãã¾ãã¯quarkus.oidc.authentication.id-token-requiredãfalseã«è¨å®ããã¦ããå ´åããã®ãããªå ´åãç¾å¨ã® OIDC ããã³ãã UserInfo ã¨ã³ããã¤ã³ãããµãã¼ããã¦ããå¿ è¦ãããã¾ãã -
io.quarkus.oidc.UserInfoã¤ã³ã¸ã§ã¯ã·ã§ã³ãã¤ã³ããæ¤åºãããå ´åããã ããæå¹ã«ãªãã®ã¯ãç¾å¨ã® OIDC ããã³ãã UserInfo ã¨ã³ããã¤ã³ãããµãã¼ããã¦ããå ´åã ãã§ãã
OIDC è¨å®æ å ±ã¸ã®ã¢ã¯ã»ã¹
ç¾å¨ã®ããã³ãã®æ¤åºããã OpenID Connect è¨å®ã¡ã¿ãã¼ã¿ ã¯ã io.quarkus.oidc.OidcConfigurationMetadata ã§è¡¨ããã SecurityIdentity configuration-metadata 屿§ã¨ãã¦æ³¨å
¥ã¾ãã¯ã¢ã¯ã»ã¹ã§ãã¾ãã
ã¨ã³ããã¤ã³ãããããªãã¯ã®å ´åãããã©ã«ãã®ããã³ãã® OidcConfigurationMetadata ãæ³¨å
¥ããã¾ãã
ãã¼ã¯ã³ã¯ã¬ã¼ã 㨠SecurityIdentity ãã¼ã«ã®ãããã³ã°
æ¤è¨¼æ¸ã¿ãã¼ã¯ã³ãã SecurityIdentity ãã¼ã«ã«ãã¼ã«ããããã³ã°ããæ¹æ³ã¯ããã¢ã©ã¼ãã¼ã¯ã³ ã®æ¹æ³ã¨åãã§ãã å¯ä¸ã®éãã¯ã ID ãã¼ã¯ã³ ãããã©ã«ãã§ãã¼ã«ã®ã½ã¼ã¹ã¨ãã¦ä½¿ç¨ããããã¨ã§ãã
|
Keycloak ã使ç¨ããå ´åã¯ãID ãã¼ã¯ã³ã« |
ãã ããOIDC ãããã¤ãã¼ã«ãã£ã¦ã¯ããã¼ã«ãã¢ã¯ã»ã¹ãã¼ã¯ã³ã¾ãã¯ã¦ã¼ã¶ã¼æ å ±ã«ä¿åãããå ´åãããã¾ãã
ã¢ã¯ã»ã¹ãã¼ã¯ã³ã«ãã¼ã«ãå«ã¾ãã¦ããããã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ããã¦ã³ã¹ããªã¼ã ã¨ã³ããã¤ã³ãã«ä¼æããããã¨ãæå³ãã¦ããªãå ´åã¯ã quarkus.oidc.roles.source=accesstoken ãè¨å®ãã¾ãã
UserInfo ããã¼ã«ã®ã½ã¼ã¹ã§ããå ´åã¯ã quarkus.oidc.roles.source=userinfo ãè¨å®ããå¿
è¦ã«å¿ã㦠quarkus.oidc.roles.role-claim-path ãè¨å®ãã¾ãã
ããã«ãã«ã¹ã¿ã SecurityIdentityAugmentor ã使ç¨ãã¦ãã¼ã«ã追å ãããã¨ãã§ãã¾ãã
詳細ã¯ãSecurityIdentity ã«ã¹ã¿ãã¤ãº ãåç
§ãã¦ãã ããã
ã¾ããHTTP Security ããªã·ã¼ ã使ç¨ãã¦ããã¼ã¯ã³è¦æ±ãã使ããã SecurityIdentity ãã¼ã«ããããã¤ã¡ã³ãåºæã®ãã¼ã«ã«ããããããã¨ãã§ãã¾ãã
ãã¼ã¯ã³ã¨èªè¨¼ãã¼ã¿ã®æå¹æ§ã®ç¢ºèª
èªè¨¼ããã»ã¹ã®ä¸æ ¸ã¨ãªãã®ã¯ããã©ã¹ããã§ã¼ã³ã¨æ å ±ã®æå¹æ§ã確èªãããã¨ã§ãã ããã¯ããã¼ã¯ã³ãä¿¡é ¼ã§ãããã¨ã確èªãããã¨ã«ãã£ã¦è¡ããã¾ãã
ãã¼ã¯ã³æ¤è¨¼ããã³ã¤ã³ããã¹ãã¯ã·ã§ã³
OIDC èªå¯ã³ã¼ãããã¼ãã¼ã¯ã³ã®æ¤è¨¼ããã»ã¹ã¯ããã¢ã©ã¼ãã¼ã¯ã³èªè¨¼ãã¼ã¯ã³ã®æ¤è¨¼ã¨ã¤ã³ããã¹ãã¯ã·ã§ã³ã®ãã¸ãã¯ã«å¾ãã¾ãã 詳細ã¯ããQuarkus OpenID Connect (OIDC) ãã¢ã©ã¼ãã¼ã¯ã³èªè¨¼ãã¬ã¤ãã® ãã¼ã¯ã³ã®æ¤è¨¼ã¨ã¤ã³ããã¹ãã¯ã·ã§ã³ ã®ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããã
|
Quarkus ã® |
ãã¼ã¯ã³ã®ã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ UserInfo ã®ãã£ãã·ã¥
ã³ã¼ãããã¼ã¢ã¯ã»ã¹ãã¼ã¯ã³ã¯ããã¼ã«ã®ã½ã¼ã¹ã§ããã¨äºæ³ãããªãéããã¤ã³ããã¹ãã¯ãããã¾ããã
ãã ããããã㯠UserInfo ãåå¾ããããã«ä½¿ç¨ããã¾ãã
ãã¼ã¯ã³ã¤ã³ããã¹ãã¯ã·ã§ã³ ã¾ã㯠UserInfoããããã¯ãã®ä¸¡æ¹ãå¿
è¦ãªå ´åã¯ãã³ã¼ãããã¼ã¢ã¯ã»ã¹ãã¼ã¯ã³ã使ç¨ãããªã¢ã¼ãå¼ã³åºãã 1 åã¾ã㯠2 åçºçãã¾ãã
ããã©ã«ãã®ãã¼ã¯ã³ãã£ãã·ã¥ã®ä½¿ç¨ã¾ãã¯ã«ã¹ã¿ã ãã£ãã·ã¥å®è£ ã®ç»é²ã®è©³ç´°ã¯ããã¼ã¯ã³ã®ã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ UserInfo ã®ãã£ãã·ã¥ ãåç §ãã¦ãã ããã
JSON Web ãã¼ã¯ã³ã®ã¯ã¬ã¼ã æ¤è¨¼
iss (çºè¡è
) ã¯ã¬ã¼ã ãå«ãã¯ã¬ã¼ã æ¤è¨¼ã®è©³ç´°ã¯ãJSON Web Token ã¯ã¬ã¼ã æ¤è¨¼ ã»ã¯ã·ã§ã³ãåç
§ãã¦ãã ããã
ããã¯ãID ãã¼ã¯ã³ã«é©ç¨ããã web-app ã¢ããªã±ã¼ã·ã§ã³ãã¢ã¯ã»ã¹ãã¼ã¯ã³ã®æ¤è¨¼ããªã¯ã¨ã¹ãããå ´åã¯ãJWT å½¢å¼ã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ã«ãé©ç¨ããã¾ãã
Jose4j Validator
You can register a custom Jose4j Validator to customize the JWT claim verification process. See the Jose4j section for more information.
Proof Key for Code Exchange (PKCE)
Proof Key for Code Exchange (PKCE) ã¯ãèªå¯ã³ã¼ãã®ååã®ãªã¹ã¯ãæå°éã«æãã¾ãã
PKCE ã¯ããã©ã¦ã¶ã¼ã§å®è¡ããã SPA ã¹ã¯ãªãããªã©ã®ãããªã㯠OIDC ã¯ã©ã¤ã¢ã³ãã«ã¨ã£ã¦æãéè¦ã§ãããQuarkus OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ã«è¿½å ã®ä¿è·ãæä¾ãããã¨ãã§ãã¾ãã
PKCE ã使ç¨ããã¨ãQuarkus OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ã¯ãã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ãããå®å
¨ã«ä¿åããããã使ç¨ãã¦ãã¼ã¯ã³ã®ã³ã¼ãã交æã§ããæ©å¯ OIDC ã¯ã©ã¤ã¢ã³ãã¨ãã¦æ©è½ãã¾ãã
次ã®ä¾ã«ç¤ºãããã«ã quarkus.oidc.authentication.pkce-required ããããã£ã¼ã¨ãç¶æ
ã¯ããã¼ã® PKCE ã³ã¼ãæ¤è¨¼ãæå·åããããã«å¿
è¦ãª 32 æåã®ã·ã¼ã¯ã¬ããã使ç¨ãã¦ãOIDC web-app ã¨ã³ããã¤ã³ãã® PKCE ãæå¹ã«ãããã¨ãã§ãã¾ãã
quarkus.oidc.authentication.pkce-required=true
quarkus.oidc.authentication.state-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU
ãã§ã« 32 æåã®ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ãããããå ´åã¯ãå¥ã®ã·ã¼ã¯ã¬ãããã¼ã使ç¨ããå ´åãé¤ãã quarkus.oidc.authentication.pkce-secret ããããã£ã¼ãè¨å®ããå¿
è¦ã¯ããã¾ããã
ãã®ã·ã¼ã¯ã¬ããã¯ãè¨å®ããã¦ããªãå ´åãããã³ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ããã®é·ãã 16 æåæªæºã§ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ããã¸ã®ãã©ã¼ã«ããã¯ãä¸å¯è½ãªå ´åã«ãèªåçæããã¾ãã
ç§å¯éµã¯ãã©ã³ãã ã«çæããã PKCE code_verifier ãæå·åããããã«å¿
è¦ã§ãã䏿¹ãã¦ã¼ã¶ã¼ã¯ code_challenge ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã使ç¨ã㦠OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ããããèªè¨¼ããã¾ãã
code_verifier ã¯ãã¦ã¼ã¶ã¼ã Quarkus ã«ãªãã¤ã¬ã¯ããããã¨ãã«å¾©å·åããã codeãã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ãããããã³ãã®ä»ã®ãã©ã¡ã¼ã¿ã¼ã¨ã¨ãã«ãã¼ã¯ã³ã¨ã³ããã¤ã³ãã«éä¿¡ãããã³ã¼ã交æãå®äºãã¾ãã
code_verifier ã® SHA256 ãã¤ã¸ã§ã¹ãããèªè¨¼ã®ãªã¯ã¨ã¹ãæã«æä¾ããã code_challenge ã¨ä¸è´ããªãå ´åããããã¤ãã¼ã¯ã³ã¼ã交æã«å¤±æãã¾ãã
Pushed Authorization Requests
OAuth 2.0 Pushed Authorization Requests (PAR) allows the authorization server to authenticate the client before any user interaction happens. It mitigates front-channel attacks by preventing malicious actors from intercepting, tampering with, or spoofing the authorization request details in the user’s browser.
You can enable PAR for your OIDC web-app endpoint by setting the quarkus.oidc.authentication.par.enabled configuration property to true.
It will be enabled automatically if the OIDC metadata contains a require_pushed_authorization_requests property that is set to true.
The PAR endpoint is discovered from the OAuth Authorization Server Metadata parameter pushed_authorization_request_endpoint.
If the metadata discovery is disabled, you must configure the PAR endpoint manually like in the example below:
quarkus.oidc.authentication.par.enabled=true
quarkus.oidc.authentication.par.path=/as/par
Rich Authorization Requests
The OAuth 2.0 Rich Authorization Requests (RAR) specification allows OAuth2 client applications to specify fine-grained authorization requirements in the authorization code flow request.
Instead of relying solely on traditional OAuth 2.0 scopes, RAR allows to represent required permissions as JSON that must be URL-encoded and passed as an authorization_details query parameter.
You can configure authorization details using the properties in the quarkus.oidc.authentication.rar configuration namespace:
quarkus.oidc.authentication.rar.type=openid_credential (1)
quarkus.oidc.authentication.rar.simple.credential_configuration_id=vc-scope-mapping (2)
quarkus.oidc.authentication.rar.array.locations=${quarkus.oidc.auth-server-url} (3)
| 1 | The type field is required. |
| 2 | The credential_configuration_id field value vc-scope-mapping is added to the authorization_details as a string. |
| 3 | The common data field locations is added as an array of strings. |
With this configuration, the following JSON is created:
[
{
"type": "openid_credential",
"credential_configuration_id": "vc-scope-mapping",
"locations": [ "http://localhost:34187/realms/quarkus" ]
}
]
Next, this JSON is URL-encoded and added as an authorization_details query parameter to the authorization code flow redirect URL.
Add an authorization_details query parameter with OidcRedirectFilter
A complex JSON structure that cannot be expressed with configuration properties in the quarkus.oidc.authentication.rar configuration namespace can be added with the OidcRedirectFilter.
For example:
package io.quarkus.it.keycloak;
import static io.quarkus.oidc.Redirect.Location.OIDC_AUTHORIZATION;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.Redirect;
@Redirect(OIDC_AUTHORIZATION)
@ApplicationScoped
@Unremovable
class AuthorizationDetailsOidcRedirectFilter implements OidcRedirectFilter {
@Override
public void filter(OidcRedirectContext redirectContext) {
redirectContext.additionalQueryParams().add("authorization_details", """
[
{
"type": "openid_credential",
"credential_configuration_id": "vc-scope-mapping-1",
"locations": [ "http://localhost:34187/realms/quarkus" ]
},
{
"type": "openid_credential",
"credential_configuration_id": "vc-scope-mapping-2",
"locations": [ "http://localhost:34187/realms/quarkus" ]
}
]
""");
}
}
èªè¨¼ã®ã©ã¤ãã¿ã¤ã ã®å¦çã¨å¶å¾¡
èªè¨¼ã®ãã 1 ã¤ã®éè¦ãªè¦ä»¶ã¯ãã¦ã¼ã¶ã¼ããªã¯ã¨ã¹ãã®ãã³ã«èªè¨¼ããªã¯ã¨ã¹ããããã¨ãªããã»ãã·ã§ã³ã®åºã¨ãªããã¼ã¿ãææ°ã§ãããã¨ãä¿è¨¼ãããã¨ã§ãã ã¾ãããã°ã¢ã¦ãã¤ãã³ããæç¤ºçã«ãªã¯ã¨ã¹ããããç¶æ³ãããã¾ãã 以ä¸ã®ãã¤ã³ããåèã«ãQuarkus ã¢ããªã±ã¼ã·ã§ã³ã®ã»ãã¥ãªãã£ã¼ã確ä¿ããããã®é©åãªãã©ã³ã¹ãè¦ã¤ãã¾ãã
ã¯ããã¼
OIDC ã¢ããã¿ã¼ã¯ã¯ããã¼ã使ç¨ãã¦ãã»ãã·ã§ã³ãã³ã¼ãããã¼ããã°ã¢ã¦ãå¾ã®ç¶æ ãä¿æãã¾ãã ãã®ç¶æ ã¯ãèªè¨¼ãã¼ã¿ã®å¯¿å½ãå¶å¾¡ããéè¦ãªè¦ç´ ã§ãã
quarkus.oidc.authentication.cookie-path ããããã£ã¼ã使ç¨ããã¨ãä¿è·ããããªã½ã¼ã¹ã«éè¤ã¾ãã¯ç°ãªãã«ã¼ãã§ã¢ã¯ã»ã¹ããã¨ãã«ãåãã¯ããã¼ã表示ãããããã«ãªãã¾ãã
ä¾:
-
/index.htmlã¨/web-app/service -
/web-app/service1ã¨/web-app/service2 -
/web-app1/serviceã¨/web-app2/service
ããã©ã«ãã§ã¯ã quarkus.oidc.authentication.cookie-path 㯠/ ã«è¨å®ããã¦ãã¾ãããå¿
è¦ã«å¿ãã¦ããã /web-app ãªã©ã®ããå
·ä½çãªãã¹ã«å¤æ´ã§ãã¾ãã
Cookie ãã¹ãåçã«è¨å®ããã«ã¯ã quarkus.oidc.authentication.cookie-path-header ããããã£ã¼ãè¨å®ãã¾ãã
ãã¨ãã°ã X-Forwarded-Prefix HTTP ãããã¼ã®å¤ã使ç¨ã㦠Cookie ãã¹ãåçã«è¨å®ããã«ã¯ãããããã£ã¼ã quarkus.oidc.authentication.cookie-path-header=X-Forwarded-Prefix ã«è¨å®ãã¾ãã
quarkus.oidc.authentication.cookie-path-header ãè¨å®ããã¦ããããç¾å¨ã®ãªã¯ã¨ã¹ãã§è¨å®ããã HTTP ãããã¼ãå©ç¨ã§ããªãå ´åã¯ã quarkus.oidc.authentication.cookie-path ããã§ãã¯ããã¾ãã
ã¢ããªã±ã¼ã·ã§ã³ãè¤æ°ã®ãã¡ã¤ã³ã«ã¾ããã£ã¦ãããã¤ããã¦ããå ´åã¯ãã»ãã·ã§ã³ã¯ããã¼ ããã¹ã¦ã®ä¿è·ããã Quarkus ãµã¼ãã¹ã«è¡¨ç¤ºãããããã«ã quarkus.oidc.authentication.cookie-domain ããããã£ã¼ãè¨å®ãã¾ãã
ãã¨ãã°ã次㮠2 ã¤ã®ãã¡ã¤ã³ã« Quarkus ãµã¼ãã¹ããããã¤ãã¦ããå ´åã¯ã quarkus.oidc.authentication.cookie-domain ããããã£ã¼ã company.net ã«è¨å®ããå¿
è¦ãããã¾ãã
-
https://whatever.wherever.company.net/
-
https://another.address.company.net/
ç¶æ Cookie
ç¶æ
Cookie ã¯ãèªå¯ã³ã¼ãããã¼ã®å®äºããµãã¼ãããããã«ä½¿ç¨ããã¾ãã
èªå¯ã³ã¼ãããã¼ãéå§ãããã¨ãQuarkus ã¯ã¦ã¼ã¶ã¼ã OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ãããåã«ãç¶æ
Cookie ã¨ä¸è´ãã state ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã使ãã¾ãã
ã¦ã¼ã¶ã¼ãèªå¯ã³ã¼ãããã¼ãå®äºããããã« Quarkus ã«ãªãã¤ã¬ã¯ããããã¨ãQuarkus ã¯ãªã¯ã¨ã¹ã URI ã« state ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ãå«ã¾ãã¦ããããããç¾å¨ã®ç¶æ
Cookie ã®å¤ã¨ä¸è´ãã¦ããã¨æ³å®ãã¾ãã
ããã©ã«ãã®ç¶æ
Cookie ã®æå¹æé㯠5 åã§ããããã㯠quarkus.oidc.authentication.state-cookie-age Duration ããããã£ã¼ã使ç¨ãã¦å¤æ´ã§ãã¾ãã
Quarkus ã¯ããã«ãã¿ãèªè¨¼ããµãã¼ãããããã«ãæ°ããèªå¯ã³ã¼ãããã¼ãéå§ããããã³ã«ä¸æã®ç¶æ
Cookie åã使ãã¾ããåãã¦ã¼ã¶ã¼ã«ãã夿°ã®åæèªè¨¼ãªã¯ã¨ã¹ãã«ããã夿°ã®ç¶æ
Cookie ã使ãããå ´åãããã¾ãã
ã¦ã¼ã¶ã¼ã«å¯¾ãã¦ãè¤æ°ã®ãã©ã¦ã¶ã¼ã¿ãã使ç¨ããèªè¨¼ã許å¯ããªãå ´åã¯ã quarkus.oidc.authentication.allow-multiple-code-flows=false ã§ç¡å¹ã«ãããã¨ãæ¨å¥¨ããã¾ããããã«ãããæ°ããã¦ã¼ã¶ã¼èªè¨¼ãã¨ã«åãç¶æ
Cookie åã使ããã¾ãã
ã»ãã·ã§ã³ã¯ããã¼ã¨ããã©ã«ãã® TokenStateManager
OIDC CodeAuthenticationMechanism ã¯ãããã©ã«ãã® io.quarkus.oidc.TokenStateManager ã¤ã³ã¿ã¼ãã§ã¤ã¹å®è£
ã使ç¨ãã¦ãèªå¯ã³ã¼ãã¾ãã¯ãªãã¬ãã·ã¥ä»ä¸å¿çã§è¿ããã IDãã¢ã¯ã»ã¹ãã¼ã¯ã³ãããã³ãªãã¬ãã·ã¥ãã¼ã¯ã³ãæå·åãããã»ãã·ã§ã³ã¯ããã¼ã«ä¿åãã¾ãã
ããã«ãããQuarkus OIDC ã¨ã³ããã¤ã³ãã¯å®å ¨ã«ã¹ãã¼ãã¬ã¹ã«ãªããæé«ã®ã¹ã±ã¼ã©ããªãã£ã¼çµæãéæããã«ã¯ããã®ã¹ãã©ãã¸ã¼ã«å¾ããã¨ãæ¨å¥¨ããã¾ãã
ãã¼ã¿ãã¼ã¹ã¾ãã¯ãã®ä»ã®ãµã¼ãã¼å´ã¹ãã¬ã¼ã¸ã½ãªã¥ã¼ã·ã§ã³ã«ãã¼ã¯ã³ãä¿åããæ¹æ³ã¯ããã®ã¬ã¤ãã® ãã¼ã¿ãã¼ã¹ TokenStateManager ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããããã®æ¹æ³ã¯ãä½ããã®çç±ã§ãã¼ã¯ã³ã®ç¶æ ããµã¼ãã¼ä¸ã«ä¿åããå¿ è¦ãããå ´åã«é©ãã¦ãã¾ãã
ãã¼ã¯ã³ä¿åã®ä»£æ¿æ¹æ³ã«ã¤ãã¦ã¯ãã»ãã·ã§ã³ã¯ããã¼ã¨ã«ã¹ã¿ã TokenStateManager ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ãããããã¯ç¹ã«ãæ¨æºã®ãµã¼ãã¼å´ã¹ãã¬ã¼ã¸ãè¦ä»¶ãæºããã¦ããªããªã©ã®çç±ããããã¼ã¯ã³ç¶æ 管çã®ã«ã¹ã¿ãã¤ãºã½ãªã¥ã¼ã·ã§ã³ãå¿ è¦ãªå ´åã«æé©ã§ãã
ããã©ã«ãã® TokenStateManager ãè¨å®ããã¨ãã¢ã¯ã»ã¹ãã¼ã¯ã³ãã»ãã·ã§ã³ã¯ããã¼ã«ä¿åããã«ãID ãã¼ã¯ã³ã¨ãªãã¬ãã·ã¥ãã¼ã¯ã³ã®ã¿ãã¾ãã¯åä¸ã® ID ãã¼ã¯ã³ã®ã¿ãä¿æã§ãã¾ãã
ã¢ã¯ã»ã¹ãã¼ã¯ã³ã¯ãã¨ã³ããã¤ã³ããæ¬¡ã®ã¢ã¯ã·ã§ã³ãå®è¡ããå¿ è¦ãããå ´åã«ã®ã¿å¿ è¦ã§ãã
-
UserInfoã®åå¾ -
ãã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ã使ç¨ãããã¦ã³ã¹ããªã¼ã ãµã¼ãã¹ã¸ã®ã¢ã¯ã»ã¹
-
ããã©ã«ãã§ãã§ãã¯ãããã¢ã¯ã»ã¹ãã¼ã¯ã³ã«é¢é£ä»ãããããã¼ã«ã®ä½¿ç¨
ãã®ãããªå ´åã¯ã quarkus.oidc.token-state-manager.strategy ããããã£ã¼ã使ç¨ãã¦ããã¼ã¯ã³ç¶æ
ã¹ãã©ãã¸ã¼ã次ã®ããã«è¨å®ãã¾ãã
| 以ä¸ã®å ´å | ããããã£ã¼ã以ä¸ã«è¨å®ãã |
|---|---|
ID ã¨ãªãã¬ãã·ã¥ãã¼ã¯ã³ã®ã¿ãä¿åããå ´å |
|
ID ãã¼ã¯ã³ã®ã¿ãä¿åããå ´å |
|
鏿ããã»ãã·ã§ã³ã¯ããã¼ã¹ãã©ãã¸ã¼ããã¼ã¯ã³ãçµã¿åããã4 KB ãè¶
ãã大ããªã»ãã·ã§ã³ã¯ããã¼å¤ãçæãããå ´åãä¸é¨ã®ãã©ã¦ã¶ã¼ã§ã¯ãã®ãããªã¯ããã¼ãµã¤ãºãå¦çã§ããªãå¯è½æ§ãããã¾ãã
ããã¯ãIDãã¢ã¯ã»ã¹ãã¼ã¯ã³ãããã³ãªãã¬ãã·ã¥ãã¼ã¯ã³ã JWT ãã¼ã¯ã³ã§ã鏿ãããã¹ãã©ãã¸ã¼ã keep-all-tokens ã®å ´åãã¾ãã¯ã¹ãã©ãã¸ã¼ã id-refresh-token ã®å ´åã«ID ãã¼ã¯ã³ã¨ãªãã¬ãã·ã¥ãã¼ã¯ã³ã§çºçããå¯è½æ§ãããã¾ãã
ãã®åé¡ãåé¿ããã«ã¯ã quarkus.oidc.token-state-manager.split-tokens=true ãè¨å®ãã¦ããã¼ã¯ã³ãã¨ã«ä¸æã®ã»ãã·ã§ã³ãã¼ã¯ã³ã使ãã¾ãã
å¥ã®è§£æ±ºçã¨ãã¦ããã¼ã¯ã³ããã¼ã¿ãã¼ã¹ã«ä¿åãããã¨ãæãããã¾ãã
詳細ã¯ããã¼ã¿ãã¼ã¹ TokenStateManager ãåç
§ãã¦ãã ããã
ããã©ã«ãã® TokenStateManager ã¯ããã¼ã¯ã³ãã»ãã·ã§ã³ã¯ããã¼ã«ä¿åããåã«æå·åãã¾ãã
次ã®ä¾ã¯ããã¼ã¯ã³ãåå²ãã¦æå·åããããã«è¨å®ããæ¹æ³ã示ãã¦ãã¾ãã
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.token-state-manager.split-tokens=true
quarkus.oidc.token-state-manager.encryption-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU
ãã¼ã¯ã³ã®æå·åã·ã¼ã¯ã¬ãã㯠32 æå以ä¸ã§ããå¿
è¦ãããã¾ãã
ãã®ãã¼ãè¨å®ããã¦ããªãå ´åã¯ã quarkus.oidc.credentials.secret ã¾ã㯠quarkus.oidc.credentials.jwt.secret ã®ãããããããã·ã¥åããã¦æå·åãã¼ã使ããã¾ãã
Quarkus ãæ¬¡ã®ããããã®èªè¨¼æ¹æ³ã使ç¨ã㦠OIDC ãããã¤ãã¼ã«å¯¾ãã¦èªè¨¼ããå ´åã¯ã quarkus.oidc.token-state-manager.encryption-secret ããããã£ã¼ãè¨å®ãã¾ãã
-
mTLS
-
private_key_jwtã§ã¯ãç§å¯ã® RSA ã¾ã㯠EC ãã¼ã使ç¨ã㦠JWT ãã¼ã¯ã³ã«ç½²åãã¾ãã
ãã以å¤ã®å ´åã¯ãã©ã³ãã ãªãã¼ãçæããã¾ãããQuarkus ã¢ããªã±ã¼ã·ã§ã³ãã¯ã©ã¦ãã§å®è¡ãããè¤æ°ã® Pod ããªã¯ã¨ã¹ãã管çãã¦ããå ´åãåé¡ãçºçããå¯è½æ§ãããã¾ãã
quarkus.oidc.token-state-manager.encryption-required=false ãè¨å®ãããã¨ã§ãã»ãã·ã§ã³ã¯ããã¼ã§ã®ãã¼ã¯ã³æå·åãç¡å¹ã«ãããã¨ãã§ãã¾ãã
ã»ãã·ã§ã³ã¯ããã¼ã¨ã«ã¹ã¿ã TokenStateManager
ãã¼ã¯ã³ãã»ãã·ã§ã³ã¯ããã¼ã«é¢é£ä»ããæ¹æ³ãã«ã¹ã¿ãã¤ãºãããå ´åã¯ãã«ã¹ã¿ã io.quarkus.oidc.TokenStateManager' å®è£
ã `@ApplicationScoped CDI Bean ã¨ãã¦ç»é²ãã¾ãã
ãã¨ãã°ããã¼ã¯ã³ããã£ãã·ã¥ã¯ã©ã¹ã¿ã¼ã«ä¿æãããã¼ã®ã¿ãã»ãã·ã§ã³ã¯ããã¼ã«ä¿åãããã¨ãæ¨å¥¨ãã¾ãã ãã¼ã¯ã³ãè¤æ°ã®ãã¤ã¯ããµã¼ãã¹ãã¼ãã§å©ç¨ã§ããããã«ããå¿ è¦ãããå ´åããã®ã¢ããã¼ãã§ã¯ããã¤ãã®èª²é¡ãçããå¯è½æ§ããããã¨ã«æ³¨æãã¦ãã ããã
ç°¡åãªä¾ãæãã¦ã¿ã¾ãã
package io.quarkus.oidc.test;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import jakarta.inject.Inject;
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.TokenStateManager;
import io.quarkus.oidc.runtime.DefaultTokenStateManager;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
@Alternative
@Priority(1)
public class CustomTokenStateManager implements TokenStateManager {
@Inject
DefaultTokenStateManager tokenStateManager;
@Override
public Uni<String> createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig,
AuthorizationCodeTokens sessionContent, OidcRequestContext<String> requestContext) {
return tokenStateManager.createTokenState(routingContext, oidcConfig, sessionContent, requestContext)
.map(t -> (t + "|custom"));
}
@Override
public Uni<AuthorizationCodeTokens> getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig,
String tokenState, OidcRequestContext<AuthorizationCodeTokens> requestContext) {
if (!tokenState.endsWith("|custom")) {
throw new IllegalStateException();
}
String defaultState = tokenState.substring(0, tokenState.length() - 7);
return tokenStateManager.getTokens(routingContext, oidcConfig, defaultState, requestContext);
}
@Override
public Uni<Void> deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState,
OidcRequestContext<Void> requestContext) {
if (!tokenState.endsWith("|custom")) {
throw new IllegalStateException();
}
String defaultState = tokenState.substring(0, tokenState.length() - 7);
return tokenStateManager.deleteTokens(routingContext, oidcConfig, defaultState, requestContext);
}
}
æå·åãããã»ãã·ã§ã³ã¯ããã¼ã«ãã¼ã¯ã³ãä¿åããããã©ã«ãã® TokenStateManager ã®è©³ç´°ã¯ãã»ãã·ã§ã³ã¯ããã¼ã¨ããã©ã«ãã® TokenStateManager ãåç
§ãã¦ãã ããã
ãã¼ã¯ã³ããã¼ã¿ãã¼ã¹ã«ä¿åããã«ã¹ã¿ã Quarkus TokenStateManager å®è£
ã®è©³ç´°ã¯ããã¼ã¿ãã¼ã¹ TokenStateManager ãåç
§ãã¦ãã ããã
ãã¼ã¿ãã¼ã¹ TokenStateManager
ã¹ãã¼ããã«ãªãã¼ã¯ã³ã¹ãã¬ã¼ã¸ã¹ãã©ãã¸ã¼ãæ¡ç¨ãããå ´åã¯ãQuarkus ãæä¾ããã«ã¹ã¿ã ã® TokenStateManager ã使ç¨ãã¦ãã¢ããªã±ã¼ã·ã§ã³ããã¼ã¯ã³ãæå·åãããã»ãã·ã§ã³ã¯ããã¼ã«ä¿åããã®ã§ã¯ãªãããã¼ã¿ãã¼ã¹ã«ä¿åãããã¨ãã§ãã¾ããããã¯ãã»ãã·ã§ã³ã¯ããã¼ã¨ããã©ã«ãã® TokenStateManager ã»ã¯ã·ã§ã³ã«è¨è¼ããã¦ããã¨ãããããã©ã«ãã§è¨å®ããã¦ãã¾ãã
ãã®æ©è½ã使ç¨ããã«ã¯ã以ä¸ã®ã¨ã¯ã¹ãã³ã·ã§ã³ãããã¸ã§ã¯ãã«è¿½å ãã¾ãã
quarkus extension add oidc-db-token-state-manager
./mvnw quarkus:add-extension -Dextensions='oidc-db-token-state-manager'
./gradlew addExtension --extensions='oidc-db-token-state-manager'
ãã®ã¨ã¯ã¹ãã³ã·ã§ã³ã¯ãããã©ã«ãã® `io.quarkus.oidc.TokenStateManager' ããã¼ã¿ãã¼ã¹ããã¼ã¹ã¨ãããã®ã«ç½®ãæãã¾ãã
OIDC Database Token State Manager ã¯ãèªè¨¼ã IO ã¹ã¬ããã§è¡ãããå¯è½æ§ãé«ãããããããã¯ãåé¿ããããã«å é¨ã§ Reactive SQL ã¯ã©ã¤ã¢ã³ãã使ç¨ãã¾ãã
ãã¼ã¿ãã¼ã¹ã«å¿ãã¦ãReactive SQL ã¯ã©ã¤ã¢ã³ã ã 1 ã¤ã ãå«ãã¦è¨å®ãã¾ãã æ¬¡ã® Reactive SQL ã¯ã©ã¤ã¢ã³ãããµãã¼ãããã¦ãã¾ãã
-
Reactive Microsoft SQL ã¯ã©ã¤ã¢ã³ã
-
Reactive MySQL ã¯ã©ã¤ã¢ã³ã
-
Reactive PostgreSQL ã¯ã©ã¤ã¢ã³ã
-
Reactive Oracle ã¯ã©ã¤ã¢ã³ã
-
Reactive DB2 ã¯ã©ã¤ã¢ã³ã
| ã¢ããªã±ã¼ã·ã§ã³ããã§ã«ããããã® JDBC ãã©ã¤ãã¼ã¨ã¯ã¹ãã³ã·ã§ã³ãåãã Hibernate ORM ã使ç¨ãã¦ããå ´åã¯ãReactive SQL ã¯ã©ã¤ã¢ã³ãã使ç¨ããããã«åãæ¿ããå¿ è¦ã¯ããã¾ããã |
ãã¨ãã°ãHibernate ORM ã¨ã¯ã¹ãã³ã·ã§ã³ã¨ PostgreSQL JDBC ãã©ã¤ãã¼ãä½µç¨ããã¢ããªã±ã¼ã·ã§ã³ããã§ã«ããããã¼ã¿ã½ã¼ã¹ã次ã®ããã«è¨å®ããã¦ããã¨ãã¾ãã
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test
ããã§ãOIDC Database Token State Manager ã使ç¨ãããã¨ã«ããå ´åã¯ã次ã®ä¾åé¢ä¿ã追å ãããªã¢ã¯ãã£ããã©ã¤ãã¼ URL ãè¨å®ããå¿ è¦ãããã¾ãã
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
implementation("io.quarkus:quarkus-oidc-db-token-state-manager")
implementation("io.quarkus:quarkus-reactive-pg-client")
quarkus.datasource.reactive.url=postgresql://localhost:5432/quarkus_test
ããã§ããã¼ã¯ã³ããã¼ã¿ãã¼ã¹ã«ä¿åããæºåãæ´ãã¾ããã
ããã©ã«ãã§ã¯ããã¼ã¯ã³ã®ä¿åã«ä½¿ç¨ããããã¼ã¿ãã¼ã¹ãã¼ãã«ã使ããã¾ããã quarkus.oidc.db-token-state-manager.create-database-table-if-not-exists è¨å®ããããã£ã¼ã使ç¨ãã¦ããã®ãªãã·ã§ã³ãç¡å¹ã«ãããã¨ãã§ãã¾ãã
代ããã« Hibernate ORM ã¨ã¯ã¹ãã³ã·ã§ã³ã§ãã®ãã¼ãã«ã使ããå ´åã¯ã次ã®ããã« Entity ãå«ããå¿
è¦ãããã¾ãã
package org.acme.manager;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Table(name = "oidc_db_token_state_manager") (1)
@Entity
public class OidcDbTokenStateManagerEntity {
@Id
String id;
@Column(name = "id_token", length = 4000) (2)
String idToken;
@Column(name = "refresh_token", length = 4000)
String refreshToken;
@Column(name = "access_token", length = 4000)
String accessToken;
@Column(name = "expires_in")
Long expiresIn;
}
| 1 | Hibernate ORM ã¨ã¯ã¹ãã³ã·ã§ã³ã¯ããã¼ã¿ãã¼ã¹ã¹ãã¼ããçæãããå ´åã«ã®ã¿ããã®ãã¼ãã«ã使ãã¾ãã 詳細ã¯ãHibernate ORM ã¬ã¤ããåç §ãã¦ãã ããã |
| 2 | ãã¼ã¯ã³ã®é·ãã«å¿ãã¦åã®é·ãã鏿ã§ãã¾ãã |
Redis TokenStateManager
ã¹ãã¼ããã«ãã¼ã¯ã³ã¹ãã¬ã¼ã¸ã¹ãã©ãã¸ã¼ã®å¥ã®æ¹æ³ã¨ãã¦ããQuarkus ãæä¾ããã«ã¹ã¿ã TokenStateManager ã使ç¨ãã¦ãã¢ããªã±ã¼ã·ã§ã³ã§ãã¼ã¯ã³ã Redis ãã£ãã·ã¥ã«ä¿åããæ¹æ³ãããã¾ãã
OIDC Redis Token State Manager ã使ç¨ããå ´åã¯ã次ã®ä¾åé¢ä¿ã追å ããå¿
è¦ãããã¾ãã
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-redis-token-state-manager</artifactId>
</dependency>
implementation("io.quarkus:quarkus-oidc-redis-token-state-manager")
Quarkus ã¯ãã¼ã¯ã³ãããã©ã«ãã® Redis ã¯ã©ã¤ã¢ã³ãã«ä¿åãã¾ãã å¥ã® Redis ã¯ã©ã¤ã¢ã³ãã使ç¨ããå ´åã¯ã次ã®ä¾ã®ããã«è¨å®ã§ãã¾ãã
quarkus.oidc.redis-token-state-manager.redis-client-name=my-redis-client (1)
| 1 | my-redis-client ã®ååã¯ã quarkus.redis.my-redis-client.* è¨å®ããããã£ã¼ã§æå®ããã Redis ã¯ã©ã¤ã¢ã³ãè¨å®ãã¼ã«å¯¾å¿ãã¦ããå¿
è¦ãããã¾ãã |
Redis ã¯ã©ã¤ã¢ã³ãã®è¨å®æ¹æ³ã«ã¤ãã¦ã¯ãQuarkus Redis ã¯ã©ã¤ã¢ã³ããªãã¡ã¬ã³ã¹ ãåç §ãã¦ãã ããã
ãã°ã¢ã¦ãã¨æå¹æé
èªè¨¼æ å ±ãæéåãã«ãªãä¸»ãªæ¹æ³ã¯ 2 ã¤ããã¾ãããã¼ã¯ã³ã®æå¹æéãåãã¦æ´æ°ãããªãã£ãå ´åã¨ãæç¤ºçãªãã°ã¢ã¦ãæä½ãããªã¬ã¼ãããå ´åã§ãã
æåã«ãæç¤ºçãªãã°ã¢ã¦ãæä½ã«ã¤ãã¦èª¬æãã¾ãã
|
You can request setting Clear-Site-Data directives for all of the logout operations with a
|
User-initiated logout
ã¦ã¼ã¶ã¼ã¯ã quarkus.oidc.logout.path ããããã£ã¼ã§è¨å®ããã Quarkus ã¨ã³ããã¤ã³ãã®ãã°ã¢ã¦ããã¹ã«ãªã¯ã¨ã¹ããéä¿¡ãããã¨ã§ããã°ã¢ã¦ãããªã¯ã¨ã¹ãã§ãã¾ãã
ãã¨ãã°ãã¨ã³ããã¤ã³ãã¢ãã¬ã¹ã https://application.com/webapp ã§ã quarkus.oidc.logout.path ã /logout ã«è¨å®ããã¦ããå ´åããã°ã¢ã¦ããªã¯ã¨ã¹ã㯠https://application.com/webapp/logout ã«éä¿¡ãããå¿
è¦ãããã¾ãã
ãã®ãã°ã¢ã¦ããªã¯ã¨ã¹ãã¯ã RP-initiated ãã°ã¢ã¦ã ãéå§ãã¾ãã ã¦ã¼ã¶ã¯ãã°ã¢ã¦ãããããã« OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ããããããã§ãã°ã¢ã¦ããæ¬å½ã«æå³ããããã®ã§ããã確èªããã¾ãã
ãã°ã¢ã¦ããå®äºãã quarkus.oidc.logout.post-logout-path ããããã£ã¼ãè¨å®ããã¦ããå ´åãã¦ã¼ã¶ã¼ã¯ã¨ã³ããã¤ã³ãã®ãã°ã¢ã¦ãå¾ã®ãã¼ã¸ã«æ»ããã¾ãã
ãã¨ãã°ãã¨ã³ããã¤ã³ãã¢ãã¬ã¹ã https://application.com/webapp ã§ã quarkus.oidc.logout.post-logout-path ã /signin ã«è¨å®ããã¦ããå ´åãã¦ã¼ã¶ã¼ã¯ https://application.com/webapp/signin ã«æ»ããã¾ãã
ãã® URI ã¯ãOIDC ãããã¤ãã¼ã§æå¹ãª post_logout_redirect_uri ã¨ãã¦ç»é²ããã¦ããå¿
è¦ããããã¨ã«æ³¨æãã¦ãã ããã
quarkus.oidc.logout.post-logout-path ãè¨å®ããã¦ããå ´åã q_post_logout ã¯ããã¼ã使ãããä¸è´ãã state ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ããã°ã¢ã¦ããªãã¤ã¬ã¯ã URI ã«è¿½å ããããã°ã¢ã¦ããå®äºãã㨠OIDC ãããã¤ãã¼ã¯ãã® state ãè¿ãã¾ãã
Quarkus ã® web-app ã¢ããªã±ã¼ã·ã§ã³ã§ã¯ã state ã¯ã¨ãªã¼ãã©ã¡ã¼ã¿ã¼ã q_post_logout ã¯ããã¼ã®å¤ã¨ä¸è´ãããã¨ã確èªãããã¨ãæ¨å¥¨ãã¾ããããã¯ããã¨ãã° Jakarta REST ãã£ã«ã¿ã¼ã§å®è¡ã§ãã¾ãã
OpenID Connect Multi-Tenancy ã使ç¨ããå ´åãã¯ããã¼åãç°ãªããã¨ã«æ³¨æãã¦ãã ããã
ãã¨ãã°ã tenant_1 ID ãæã¤ããã³ãã®å ´å㯠q_post_logout_tenant_1 ã¨ããååã«ãªãã¾ãã
ãã°ã¢ã¦ãããã¼ãéå§ããããã« Quarkus ã¢ããªã±ã¼ã·ã§ã³ãè¨å®ããæ¹æ³ã®ä¾ã次ã«ç¤ºãã¾ãã
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.logout.path=/logout
# Logged-out users should be returned to the /welcome.html site which will offer an option to re-login:
quarkus.oidc.logout.post-logout-path=/welcome.html
# Only the authenticated users can initiate a logout:
quarkus.http.auth.permission.authenticated.paths=/logout
quarkus.http.auth.permission.authenticated.policy=authenticated
# All users can see the Welcome page:
quarkus.http.auth.permission.public.paths=/welcome.html
quarkus.http.auth.permission.public.policy=permit
ã¾ãã quarkus.oidc.authentication.cookie-path ããã¹ã¦ã®ã¢ããªã±ã¼ã·ã§ã³ãªã½ã¼ã¹ã«å
±éã®ãã¹å¤ (ãã®ä¾ã§ã¯ /) ã«è¨å®ãããã¨ãæ¨å¥¨ãã¾ãã
詳細ã¯ãCookies ã»ã¯ã·ã§ã³ãåç
§ãã¦ãã ããã
|
ä¸é¨ã® OIDC ãããã¤ãã¼ã¯ãRP-initiated ãã°ã¢ã¦ã 仿§ããµãã¼ããã¦ããããOpenID Connect ã®å¨ç¥ã® RP-initiated ãã°ã¢ã¦ã 仿§ã«ããã°ã ãã®åé¡ãåé¿ããã«ã¯ã
|
ããã¯ãã£ãã«ãã°ã¢ã¦ã
OIDC ãããã¤ãã¼ã¯ãèªè¨¼ãã¼ã¿ã使ç¨ãã¦ããã¹ã¦ã®ã¢ããªã±ã¼ã·ã§ã³ãå¼·å¶çã«ãã°ã¢ã¦ãããããã¨ãã§ãã¾ãã ããã¯ããã¯ãã£ãã«ãã°ã¢ã¦ãã¨ãã¦ç¥ããã¦ãã¾ãã ãã®å ´åãOIDC ã¯åã¢ããªã±ã¼ã·ã§ã³ããç¹å®ã® URL ãå¼ã³åºãããã°ã¢ã¦ããããªã¬ã¼ãã¾ãã
OIDC ãããã¤ãã¼ã¯ãããã¯ãã£ãã«ãã°ã¢ã¦ã ã使ç¨ãã¦ãã¦ã¼ã¶ã¨ã¼ã¸ã§ã³ãããã¤ãã¹ãã¦ããã®ã¦ã¼ã¶ãç¾å¨ãã°ã¤ã³ãã¦ãããã¹ã¦ã®ã¢ããªã±ã¼ã·ã§ã³ããç¾å¨ã®ã¦ã¼ã¶ããã°ã¢ã¦ããã¾ãã
ããã¯ãã£ãã«ãã°ã¢ã¦ãããµãã¼ãããããã« Quarkus ãè¨å®ããã«ã¯ã次ã®ããã«ãã¾ãã
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.logout.backchannel.path=/back-channel-logout
絶対ç㪠back-channel logout URL ã¯ãç¾å¨ã®ã¨ã³ããã¤ã³ã URL ã« quarkus.oidc.back-channel-logout.path ã追å ãããã¨ã«ãã£ã¦è¨ç®ããã¾ã (ä¾: http://localhost:8080/back-channel-logout)ã
ãã® URL ã¯ãOIDC ãããã¤ãã¼ã®ç®¡çã³ã³ã½ã¼ã«ã§è¨å®ããå¿
è¦ãããã¾ãã
OIDC ãããã¤ãã¼ããç¾å¨ã®ãã°ã¢ã¦ããã¼ã¯ã³ã«æå¹æéãè¨å®ãã¦ããªãå ´åããã°ã¢ã¦ããã¼ã¯ã³ã®æ¤è¨¼ãæåããããã«ãã¼ã¯ã³ã®æå¹æéããããã£ã¼ãè¨å®ããå¿
è¦ãããã¾ãã
ãã¨ãã°ããã°ã¢ã¦ããã¼ã¯ã³ã® iat (çºè¡æå») ãã 10 ç§è¶
éããªãããã«ããã«ã¯ã quarkus.oidc.token.age=10S ãè¨å®ãã¾ãã
ããã³ããã£ãã«ãã°ã¢ã¦ã
ããã³ããã£ãã«ãã°ã¢ã¦ã ã®ãªã³ã¯ã使ç¨ããã¨ããã©ã¦ã¶ã¼ãªã©ã®ã¦ã¼ã¶ã¼ã¨ã¼ã¸ã§ã³ãããç¾å¨ã®ã¦ã¼ã¶ã¼ãç´æ¥ãã°ã¢ã¦ãã§ãã¾ãã ãã㯠Back-channel logout ã¨ä¼¼ã¦ãã¾ããããã°ã¢ã¦ãæé ã¯ãã©ã¦ã¶ã¼ãªã©ã®ã¦ã¼ã¶ã¼ã¨ã¼ã¸ã§ã³ãã«ãã£ã¦å®è¡ãããOIDC ãããã¤ãã¼ãããã¯ã°ã©ã¦ã³ãã§å®è¡ãããã¨ã¯ããã¾ããã ããã¯ãã»ã¨ãã©ä½¿ç¨ãããªããªãã·ã§ã³ã§ãã
Quarkus ã§ããã³ããã£ãã«ã®ãã°ã¢ã¦ãããµãã¼ãããã«ã¯ã以ä¸ã®ããã«è¨å®ãã¾ãã
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.logout.frontchannel.path=/front-channel-logout
ãã®ãã¹ã¯ç¾å¨ã®ãªã¯ã¨ã¹ãã®ãã¹ã¨æ¯è¼ããããããã®ãã¹ãããããã å ´åãã¦ã¼ã¶ã¯ãã°ã¢ã¦ããã¾ãã
ãã¼ã«ã«ãã°ã¢ã¦ã
User-initiated logout ã¯ãã¦ã¼ã¶ã¼ã OIDC ãããã¤ãã¼ãããã°ã¢ã¦ããã¾ãã ãããã·ã³ã°ã«ãµã¤ã³ãªã³ã¨ãã¦ä½¿ç¨ããå ´åã¯ãå¿ è¦ã¨ãããã®ã§ã¯ãªãå¯è½æ§ãããã¾ãã ãã¨ãã°ãOIDC ãããã¤ãã¼ã Google ã®å ´åãGoogle ã¨ãã®ãµã¼ãã¹ãããã°ã¢ã¦ãããã¾ãã 代ããã«ãã¦ã¼ã¶ã¼ã¯ãã®ç¹å®ã®ã¢ããªã±ã¼ã·ã§ã³ãããã°ã¢ã¦ããããã ãããããã¾ããã ãã 1 ã¤ã®ã¦ã¼ã¹ã±ã¼ã¹ã¨ãã¦ã¯ãOIDC ãããã¤ãã¼ã«ãã°ã¢ã¦ãã¨ã³ããã¤ã³ãããªãå ´åãèãããã¾ãã
OidcSession ã使ç¨ãããã¨ã§ããã¼ã«ã«ãã°ã¢ã¦ãããµãã¼ãã§ãã¾ããã¤ã¾ããæ¬¡ã®ä¾ã«ç¤ºãããã«ããã¼ã«ã«ã»ãã·ã§ã³ã¯ããã¼ã®ã¿ãã¯ãªã¢ããã¾ãã
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.quarkus.oidc.OidcSession;
@Path("/service")
public class ServiceResource {
@Inject
OidcSession oidcSession;
@GET
@Path("logout")
public String logout() {
oidcSession.logout().await().indefinitely();
return "You are logged out";
}
}
OidcSession ããã¼ã«ã«ãã°ã¢ã¦ãã«ä½¿ç¨
io.quarkus.oidc.OidcSession ã¯ç¾å¨ã® IdToken ã®ã©ããã¼ã§ãLocal logout ã®å®è¡ãç¾è¡ã»ãã·ã§ã³ã®ããã³ãèå¥åã®åå¾ãã»ãã·ã§ã³ã®æå¹æéã®ç¢ºèªãè¡ãä¸ã§å½¹ã«ç«ã¡ã¾ãã
ä»å¾ããã便å©ãªã¡ã½ããã追å ãããäºå®ã§ãã
ã»ãã·ã§ã³ç®¡ç
ããã©ã«ãã§ã¯ããã°ã¢ã¦ã㯠OIDC ãããã¤ãã¼ã«ãã£ã¦çºè¡ããã ID ãã¼ã¯ã³ã®æå¹æéã«åºã¥ãã¦è¡ããã¾ãã ID ãã¼ã¯ã³ã®æå¹æéãåããã¨ãQuarkus ã¨ã³ããã¤ã³ãã§ã®ç¾å¨ã®ã¦ã¼ã¶ã¼ã»ãã·ã§ã³ã¯ç¡å¹ã«ãªããã¦ã¼ã¶ã¼ã¯èªè¨¼ã®ããã«å度 OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ãããã¾ãã OIDC ãããã¤ãã¼ã®ã»ãã·ã§ã³ãå¼ãç¶ãæå¹ãªå ´åãã¦ã¼ã¶ã¼ã¯å度ã¯ã¬ãã³ã·ã£ã«ãå ¥åãããã¨ãªããèªåçã«åèªè¨¼ããã¾ãã
quarkus.oidc.token.refresh-expired ããããã£ã¼ãæå¹ã«ããã¨ãç¾å¨ã®ã¦ã¼ã¶ã¼ã»ãã·ã§ã³ãèªåçã«å»¶é·ã§ãã¾ãã
true ã«è¨å®ããã¨ãç¾å¨ã® ID ãã¼ã¯ã³ã®æå¹æéãåããã¨ãã«ããªãã¬ãã·ã¥ãã¼ã¯ã³ã®ä»ä¸ã使ç¨ãã¦ãID ãã¼ã¯ã³ã ãã§ãªãã¢ã¯ã»ã¹ãã¼ã¯ã³ã¨ãªãã¬ãã·ã¥ãã¼ã¯ã³ããªãã¬ãã·ã¥ããã¾ãã
|
|
Quarkus OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ã使ç¨ããå ´åãQuarkus OIDC ã³ã¼ãèªè¨¼ã¡ã«ããºã ãã¦ã¼ã¶ã¼ã»ãã·ã§ã³ã®æå¹æéã管çãã¾ãã
ãªãã¬ãã·ã¥ãã¼ã¯ã³ã使ç¨ããã«ã¯ãã»ãã·ã§ã³ã¯ããã¼ã®æå¹æéãæ éã«è¨å®ããå¿ è¦ãããã¾ãã ã»ãã·ã§ã³ã®æå¹æéã¯ãID ãã¼ã¯ã³ã®æå¹æéãããé·ãããªãã¬ãã·ã¥ãã¼ã¯ã³ã®æå¹æéã¨è¿ããçããå¿ è¦ãããã¾ãã
ã»ãã·ã§ã³ã®æå¹æéã¯ãç¾å¨ã® ID ãã¼ã¯ã³ã®æå¹æéã®å¤ã¨ã quarkus.oidc.authentication.session-age-extension ããããã£ã¼ããã³ quarkus.oidc.token.lifespan-grace ããããã£ã¼ã®å¤ãå ç®ãã¦è¨ç®ãã¾ãã
|
å¿
è¦ã«å¿ãã¦ã |
ç¾å¨èªè¨¼ããã¦ããã¦ã¼ã¶ã¼ããä¿è·ããã Quarkus ã¨ã³ããã¤ã³ãã«æ»ããã»ãã·ã§ã³ã¯ããã¼ã«é¢é£ä»ãããã ID ãã¼ã¯ã³ã®æå¹æéãåããã¨ãããã©ã«ãã§ã¯ãã¦ã¼ã¶ã¼ã¯åèªè¨¼ã®ããã« OIDC èªå¯ã¨ã³ããã¤ã³ãã«èªåçã«ãªãã¤ã¬ã¯ãããã¾ãã ã¦ã¼ã¶ã¼ã¨ãã® OIDC ãããã¤ãã¼éã®ã»ãã·ã§ã³ãã¾ã ã¢ã¯ãã£ããªå ´åãOIDC ãããã¤ãã¼ã¯ã¦ã¼ã¶ã¼ã«å度ãã£ã¬ã³ã¸ããå¯è½æ§ãããã¾ããããã¯ãã»ãã·ã§ã³ã ID ãã¼ã¯ã³ãããé·ãç¶ãããã«è¨å®ããã¦ããå ´åã«çºçããå¯è½æ§ãããã¾ãã
quarkus.oidc.token.refresh-expired ã true ã«è¨å®ããã¦ããå ´åãæéåãã® ID ãã¼ã¯ã³ (ããã³ã¢ã¯ã»ã¹ãã¼ã¯ã³) ã¯ãåæèªå¯ã³ã¼ãä»ä¸å¿çã§è¿ããããªãã¬ãã·ã¥ãã¼ã¯ã³ã使ç¨ãã¦æ´æ°ããã¾ãã
ãã®ãªãã¬ãã·ã¥ãã¼ã¯ã³ãããã®ããã»ã¹ã®ä¸ç°ã¨ãã¦ãªãµã¤ã¯ã« (ãªãã¬ãã·ã¥) ãããå¯è½æ§ãããã¾ãã
ãã®çµæãæ°ããã»ãã·ã§ã³ã¯ããã¼ã使ãããã»ãã·ã§ã³ãå»¶é·ããã¾ãã
|
ã¦ã¼ã¶ã¼ããã¾ãã¢ã¯ãã£ãã§ãªãå ´åã¯ã |
ããã«ä¸æ©é²ãã§ãæéåããè¿ã¥ãã¦ãã ID ãã¼ã¯ã³ã¾ãã¯ã¢ã¯ã»ã¹ãã¼ã¯ã³ãäºåã«æ´æ°ãããã¨ãã§ãã¾ãã
quarkus.oidc.token.refresh-token-time-skew ããæ´æ°ãäºæ¸¬ããå¤ã«è¨å®ãã¾ãã
ç¾å¨ã®ã¦ã¼ã¶ã¼ãªã¯ã¨ã¹ãä¸ã«ãç¾å¨ã® ID ãã¼ã¯ã³ããã® quarkus.oidc.token.refresh-token-time-skew å
ã«æéåãã«ãªãã¨è¨ç®ãããå ´åããã¼ã¯ã³ã¯æ´æ°ãããæ°ããã»ãã·ã§ã³ã¯ããã¼ã使ããã¾ãã
ãã®ããããã£ã¼ã¯ãID ãã¼ã¯ã³ã®æå¹æéãããçãå¤ã«è¨å®ããå¿
è¦ãããã¾ãããã®æå¹æéã®å¤ã«è¿ãã»ã©ãID ãã¼ã¯ã³ã®æ´æ°é »åº¦ãé«ããªãã¾ãã
åç´ãª JavaScript 颿°ã使ç¨ã㦠Quarkus ã¨ã³ããã¤ã³ãã«å®æçã« ping ãéä¿¡ããã¦ã¼ã¶ã¼ã¢ã¯ãã£ããã£ã¼ãã¨ãã¥ã¬ã¼ããããã¨ã§ããã®ããã»ã¹ãããã«æé©åã§ãã¾ããããã«ãããã¦ã¼ã¶ã¼ãåèªè¨¼ãããå¿ è¦ãããæéæ ãæå°éã«æãããã¾ãã
|
ã»ãã·ã§ã³ãæ´æ°ã§ããªãå ´åãç¾å¨èªè¨¼ããã¦ããã¦ã¼ã¶ã¼ã OIDC ãããã¤ãã¼ã«ãªãã¤ã¬ã¯ããããåèªè¨¼ãè¡ããã¾ãããã ããã¦ã¼ã¶ã¼ã以åã«èªè¨¼ã«æåããå¾ãã¢ããªã±ã¼ã·ã§ã³ãã¼ã¸ã«ã¢ã¯ã»ã¹ãããã¨ããã¨ãã«çªç¶ OIDC èªè¨¼ãã£ã¬ã³ã¸ç»é¢ã表示ãããã®ã¯ãã¦ã¼ã¶ã¼ã¨ã¯ã¹ããªã¨ã³ã¹ã¨ãã¦æé©ã§ã¯ãªãå¯è½æ§ãããã¾ãã 代ããã«ãã¾ãã¦ã¼ã¶ã¼ããããªãã¯ã®ã¢ããªã±ã¼ã·ã§ã³åºæã®ã»ãã·ã§ã³æéåããã¼ã¸ã«ãªãã¤ã¬ã¯ãããããã«ãªã¯ã¨ã¹ãã§ãã¾ãããã®ãã¼ã¸ã§ãã»ãã·ã§ã³ã®æå¹æéãåãããã¨ãã¦ã¼ã¶ã¼ã«éç¥ããä¿è·ãããã¢ããªã±ã¼ã·ã§ã³ã®ã¦ã§ã«ã«ã ãã¼ã¸ã¸ã®ãªã³ã¯ããã©ã£ã¦åèªè¨¼ããããã«æç¤ºãã¾ããã¦ã¼ã¶ã¼ããªã³ã¯ãã¯ãªãã¯ããã¨ãQuarkus OIDC ãåèªè¨¼ã®ããã« OIDC ãããã¤ãã¼ã¸ã®ãªãã¤ã¬ã¯ããé©ç¨ãã¾ããå¿
è¦ã«å¿ãã¦ã ãã¨ãã°ãã¢ããªã±ã¼ã·ã§ã³ã ã¾ããã«ã¹ã¿ã ã® |
|
ãã®ã¦ã¼ã¶ã¼ã»ãã·ã§ã³ãç¡æéã«å»¶é·ãããã¨ã¯ã§ãã¾ããã æå¹æéãåãã ID ãã¼ã¯ã³ãæã¤å¾©å¸°ã¦ã¼ã¶ã¼ã¯ããªãã¬ãã·ã¥ãã¼ã¯ã³ã®æå¹æéãåããã¨ãOIDC ãããã¤ãã¼ã¨ã³ããã¤ã³ãã§åèªè¨¼ããå¿ è¦ãããã¾ãã |
GitHub ããã³ OIDC 以å¤ã® OAuth2 ãããã¤ãã¼ã¨ã®ã¤ã³ãã°ã¬ã¼ã·ã§ã³
GitHub ã LinkedIn ã®ãããªæåãªãããã¤ãã¼ã¯ OpenID Connect ãããã¤ãã¼ã§ã¯ãªãã authorization code flow ããµãã¼ããã OAuth2 ãããã¤ãã¼ã§ãã
ãã¨ãã°ãGitHub OAuth2 ã LinkedIn OAuth2 ãªã©ã§ãã
OIDC 㯠OAuth2 ã®ä¸ã«æ§ç¯ããã¦ãããã¨ãæãåºãã¦ãã ããã
OIDC ãããã¤ã㨠OAuth2 ãããã¤ãã®ä¸»ãªéãã¯ãOIDC ãããã¤ã㯠OAuth2 ãããã¤ããè¿ãæ¨æºèªå¯ã³ã¼ãããã¼ access ããã³ refresh ãã¼ã¯ã³ã«å ãã¦ãã¦ã¼ã¶èªè¨¼ã表ã ID Token ãè¿ããã¨ã§ãã
GitHub ãªã©ã® OAuth2 ãããã¤ã㯠IdToken ãè¿ããªããããã¦ã¼ã¶ã¼èªè¨¼ã¯ access ãã¼ã¯ã³ã«ãã£ã¦æé»çãã¤éæ¥çã«è¡¨ç¾ããã¾ãã
ãã® access ãã¼ã¯ã³ã¯ãç¾å¨ã®Quarkus web-app ã¢ããªã±ã¼ã·ã§ã³ãèªè¨¼ãããã¦ã¼ã¶ã¼ã«ä»£ãã£ã¦ãã¼ã¿ã«ã¢ã¯ã»ã¹ãããã¨ãèªå¯ãããèªè¨¼ãããã¦ã¼ã¶ã¼ã表ãã¾ãã
OIDC ã®å ´åã¯ãèªè¨¼ã®æå¹æ§ã®è¨¼æã¨ã㦠ID ãã¼ã¯ã³ãæ¤è¨¼ãã¾ãããOAuth2 ã®å ´åã¯ã¢ã¯ã»ã¹ãã¼ã¯ã³ãæ¤è¨¼ãã¾ãã
ããã¯ãã¢ã¯ã»ã¹ãã¼ã¯ã³ãå¿
è¦ã¨ããé常ã¯ã¦ã¼ã¶ã¼æ
å ±ãè¿ãã¨ã³ããã¤ã³ããå¾ã§å¼ã³åºããã¨ã«ãã£ã¦å®è¡ããã¾ãã
ããã¯ãOIDC UserInfo ã¨åæ§ã®ã¢ããã¼ãã§ãã¦ã¼ã¶ã¼ã«ä»£ãã£ã¦ Quarkus OIDC ã UserInfo ãåå¾ãã¾ãã
ãã¨ãã°ãGitHub ã¨é£æºããå ´åãQuarkus ã¨ã³ããã¤ã³ã㯠access ãã¼ã¯ã³ãåå¾ã§ãã¾ããããã«ãããQuarkus ã¨ã³ããã¤ã³ãã¯ç¾å¨ã®ã¦ã¼ã¶ã¼ã® GitHub ãããã¡ã¤ã«ããªã¯ã¨ã¹ãã§ãã¾ãã
ãã®ãã㪠OAuth2 ãµã¼ãã¼ã¨ã®ã¤ã³ãã°ã¬ã¼ã·ã§ã³ããµãã¼ãããã«ã¯ã quarkus-oidc ãå°ãç°ãªãæ¹æ³ã§è¨å®ãã¦ã IdToken: quarkus.oidc.authentication.id-token-required=false ãªãã§èªå¯ã³ã¼ãããã¼ã®å¿çã許å¯ããå¿
è¦ãããã¾ãã
|
ããã«ãããè¤æ°ã® OIDC ãããã¤ãã¼ããµãã¼ãããã¢ããªã±ã¼ã·ã§ã³ã®åãæ±ããç°¡åã«ãªãã¾ãã |
æ¬¡ã®æé ã¯ãè¿ãããã¢ã¯ã»ã¹ãã¼ã¯ã³ãæç¨ã§ãããç¾å¨ã® Quarkus ã¨ã³ããã¤ã³ãã«å¯¾ãã¦æå¹ã ã¨ç¢ºèªãããã¨ã§ãã
æåã®æ¹æ³ã¯ã quarkus.oidc.introspection-path ãè¨å®ã㦠OAuth2 ãããã¤ãã¼ã®ã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ã³ããã¤ã³ããå¼ã³åºããã¨ã§ã (ãããã¤ãã¼ããã®ãããªã¨ã³ããã¤ã³ããæä¾ãã¦ããå ´å)ã
ãã®å ´åã quarkus.oidc.roles.source=accesstoken ã使ç¨ãã¦ãã¢ã¯ã»ã¹ãã¼ã¯ã³ããã¼ã«ã®ã½ã¼ã¹ã¨ãã¦ä½¿ç¨ã§ãã¾ãã
ã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ã³ããã¤ã³ããåå¨ããªãå ´åã¯ã代ããã« UserInfo (å°ãªãã¨ãã¢ã¯ã»ã¹ãã¼ã¯ã³ãæ¤è¨¼ãããã) ããããã¤ãã¼ã«ãªã¯ã¨ã¹ããããã¨ã試è¡ã§ãã¾ãã
ãããè¡ãã«ã¯ã quarkus.oidc.token.verify-access-token-with-user-info=true ãæå®ãã¾ãã
ã¾ãã quarkus.oidc.user-info-path ããããã£ã¼ããã¦ã¼ã¶ã¼æ
å ±ãåå¾ãã URL ã¨ã³ããã¤ã³ã (ã¾ãã¯ã¢ã¯ã»ã¹ãã¼ã¯ã³ã«ãã£ã¦ä¿è·ãããã¨ã³ããã¤ã³ã) ã«è¨å®ããå¿
è¦ãããã¾ãã
GitHub ã®å ´åãã¤ã³ããã¹ãã¯ã·ã§ã³ã¨ã³ããã¤ã³ãããªããããUserInfo ããªã¯ã¨ã¹ãããå¿
è¦ãããã¾ãã
|
UserInfo ãå¿ è¦ãªå ´åããªã¯ã¨ã¹ããã¨ã«ãªã¢ã¼ãå¼ã³åºããå®è¡ããã¾ãã ãããã£ã¦ã ããã©ã«ãã¾ãã¯ã«ã¹ã¿ã ã® UserInfo ãã£ãã·ã¥ãããã¤ãã¼ã使ç¨ã㦠ããç¥ããã¦ããã½ã¼ã·ã£ã« OAuth2 ãããã¤ãã¼ã®ã»ã¨ãã©ã¯å¸¯åå¶éã宿½ãã¦ãããããUserInfo ããã£ãã·ã¥ããã®ãããå ´åãå¤ãã§ãããã |
OAuth2 ãµã¼ãã¼ã¯ãããç¥ãããè¨å®ã¨ã³ããã¤ã³ãããµãã¼ããã¦ããªãå¯è½æ§ãããã¾ãã
ãã®å ´åãæ¤åºãç¡å¹ã«ãã¦ãèªå¯ããã¼ã¯ã³ãã¤ã³ããã¹ãã¯ã·ã§ã³ãããã³ UserInfo ã¨ã³ããã¤ã³ããã¹ãæåã§è¨å®ããå¿
è¦ãããã¾ãã
AppleãFacebookãGitHubãGoogleãMicrosoftãSpotifyãX (æ§ Twitter) ãªã©ã®ããç¥ããã OIDC ã¾ã㯠OAuth2 ãããã¤ãã¼ã®å ´åãQuarkus 㯠quarkus.oidc.provider ããããã£ã¼ã使ç¨ãã¦ãã¢ããªã±ã¼ã·ã§ã³ã®è¨å®ã大å¹
ã«ç°¡ç´ åã§ãã¾ãã
lGitHub OAuth ã¢ããªã±ã¼ã·ã§ã³ã使 ããå¾ã«ã quarkus-oidc ã GitHub ã¨çµ±åããæ¹æ³ã¯æ¬¡ã®ã¨ããã§ãã
Quarkus ã¨ã³ããã¤ã³ããæ¬¡ã®ããã«è¨å®ãã¾ãã
quarkus.oidc.provider=github
quarkus.oidc.client-id=github_app_clientid
quarkus.oidc.credentials.secret=github_app_clientsecret
# user:email scope is requested by default, use 'quarkus.oidc.authentication.scopes' to request different scopes such as `read:user`.
# See https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps for more information.
# Consider enabling UserInfo Cache
# quarkus.oidc.token-cache.max-size=1000
# quarkus.oidc.token-cache.time-to-live=5M
#
# Or having UserInfo cached inside IdToken itself
# quarkus.oidc.cache-user-info-in-idtoken=true
ä»ã®ããç¥ããã¦ãããããã¤ãã¼ã®è¨å®ã«é¢ãã詳細ã¯ãOpenID Connect ãããã¤ãã¼ ãåç §ãã¦ãã ããã
ãã®ãããªã¨ã³ããã¤ã³ãã«å¯¾ãã¦å¿
è¦ãªã®ã¯ãç¾å¨èªè¨¼ããã¦ããã¦ã¼ã¶ã¼ã®ãããã¡ã¤ã«ã GET http://localhost:8080/github/userinfo ã§è¿ããåã
ã® UserInfo ã®ããããã£ã¼ã¨ãã¦ã¢ã¯ã»ã¹ãããã¨ã§ãã
package io.quarkus.it.keycloak;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import io.quarkus.oidc.UserInfo;
import io.quarkus.security.Authenticated;
@Path("/github")
@Authenticated
public class TokenResource {
@Inject
UserInfo userInfo;
@GET
@Path("/userinfo")
@Produces("application/json")
public String getUserInfo() {
return userInfo.getUserInfoString();
}
}
OpenID Connect Multi-Tenancy ã使ç¨ãã¦è¤æ°ã®ã½ã¼ã·ã£ã«ãããã¤ãã¼ (ãã¨ãã°ã IdToken ãè¿ã OIDC ãããã¤ãã¼ã® Google ã¨ã IdToken ãè¿ãã UserInfo ã¸ã®ã¢ã¯ã»ã¹ã®ã¿ã許å¯ãã OAuth2 ãããã¤ãã¼ã® GitHub) ããµãã¼ãããå ´åãGoogle ããã¼ã¨ GitHub ããã¼ã®ä¸¡æ¹ã§ã注å
¥ããã SecurityIdentity ã®ã¿ã使ç¨ãã¦ã¨ã³ããã¤ã³ããåä½ããããã¨ãã§ãã¾ãã
GitHub ããã¼ãã¢ã¯ãã£ããªå ´åã«ãå
é¨ã§çæããã IdToken ã§ä½æãããããªã³ã·ãã«ã UserInfo ãã¼ã¹ã®ããªã³ã·ãã«ã«ç½®ãæããããå ´åã¯ã SecurityIdentity ã®ç°¡åãªæ¡å¼µãå¿
è¦ã«ãªãã¾ãã
package io.quarkus.it.keycloak;
import java.security.Principal;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.UserInfo;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class CustomSecurityIdentityAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
RoutingContext routingContext = identity.getAttribute(RoutingContext.class.getName());
if (routingContext != null && routingContext.normalizedPath().endsWith("/github")) {
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
UserInfo userInfo = identity.getAttribute("userinfo");
builder.setPrincipal(new Principal() {
@Override
public String getName() {
return userInfo.getString("preferred_username");
}
});
identity = builder.build();
}
return Uni.createFrom().item(identity);
}
}
ããã§ãã¦ã¼ã¶ã¼ã Google ã¾ã㯠GitHub ã使ç¨ãã¦ã¢ããªã±ã¼ã·ã§ã³ã«ãµã¤ã³ã¤ã³ããã¨ã次ã®ã³ã¼ããæ©è½ããããã«ãªãã¾ãã
package io.quarkus.it.keycloak;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
@Path("/service")
@Authenticated
public class TokenResource {
@Inject
SecurityIdentity identity;
@GET
@Path("/google")
@Produces("application/json")
public String getGoogleUserName() {
return identity.getPrincipal().getName();
}
@GET
@Path("/github")
@Produces("application/json")
public String getGitHubUserName() {
return identity.getPrincipal().getName();
}
}
ãããããããç°¡åãªä»£æ¿æ¡ã¯ã @IdToken JsonWebToken 㨠UserInfo ã®ä¸¡æ¹ã注å
¥ãã IdToken ãè¿ããããã¤ãã¼ãå¦çããé㯠JsonWebToken ã使ç¨ãã¦ã IdToken ãè¿ããªããããã¤ãã¼ã®å ´å㯠UserInfo ã使ç¨ãããã¨ã§ãã
GitHub OAuth ã¢ããªã±ã¼ã·ã§ã³è¨å®ã«å
¥åããã³ã¼ã«ããã¯ãã¹ããGitHub èªè¨¼ã¨ã¢ããªã±ã¼ã·ã§ã³èªå¯ãæåããå¾ã«ã¦ã¼ã¶ã¼ããªãã¤ã¬ã¯ãããã¨ã³ããã¤ã³ããã¹ã¨ä¸è´ãã¦ãããã¨ã確èªããå¿
è¦ãããã¾ãã
ãã®å ´åã¯ã http://localhost:8080/github/userinfo ã«è¨å®ããå¿
è¦ãããã¾ãã
éè¦ãªèªè¨¼ã¤ãã³ãã®ãªãã¹ã³
éè¦ãª OIDC èªè¨¼ã¤ãã³ããç£è¦ãã @ApplicationScoped Bean ãç»é²ã§ãã¾ãã
ã¦ã¼ã¶ãåãã¦ãã°ã¤ã³ããããåèªè¨¼ããããã»ãã·ã§ã³ããªãã¬ãã·ã¥ãããããã¨ããªã¹ãã¼ãæ´æ°ããã¾ãã
å°æ¥çã«ã¯ãããã«å¤ãã®ã¤ãã³ããå ±åãããããã«ãªãããããã¾ããã
ä¾:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import io.quarkus.oidc.SecurityEvent;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class SecurityEventListener {
public void event(@Observes SecurityEvent event) {
String tenantId = event.getSecurityIdentity().getAttribute("tenant-id");
RoutingContext vertxContext = event.getSecurityIdentity().getAttribute(RoutingContext.class.getName());
vertxContext.put("listener-message", String.format("event:%s,tenantId:%s", event.getEventType().name(), tenantId));
}
}
| ãã»ãã¥ãªãã£ã®ãã³ãã¨ã³ããã¬ã¤ãã® ã»ãã¥ãªãã£ã¤ãã³ãã®ç£è¦ ã»ã¯ã·ã§ã³ã§èª¬æããã¦ããããã«ãä»ã®ã»ãã¥ãªãã£ã¤ãã³ããèããã¨ãã§ãã¾ãã |
ãã¼ã¯ã³ã®å¤±å¹
å ´åã«ãã£ã¦ã¯ãç¾å¨ã®èªå¯ã³ã¼ãããã¼ã¢ã¯ã»ã¹ãã¼ã¯ã³ããªãã¬ãã·ã¥ãã¼ã¯ã³ãåãæ¶ãå¿
è¦ããããã¨ãããã¾ãã
ãã®å ´åã¯ãOIDC ãããã¤ãã¼ã® UserInfoããã¼ã¯ã³ã¤ã³ããã¹ãã¯ã·ã§ã³ã失å¹ã¨ã³ããã¤ã³ãã¸ã®ã¢ã¯ã»ã¹ãæä¾ããã quarkus.oidc.OidcProviderClient ã使ç¨ãã¦ãã¼ã¯ã³ãåãæ¶ããã¨ãã§ãã¾ãã
ãã¨ãã°ãOidcSession ã§ãã¼ã«ã«ãã°ã¢ã¦ããå®è¡ãããã¨ã注å
¥ããã OidcProviderClient ã使ç¨ãã¦ãç¾å¨ã®ã»ãã·ã§ã³ã«é¢é£ä»ãããã¦ããã¢ã¯ã»ã¹ãã¼ã¯ã³ã¨ãªãã¬ãã·ã¥ãã¼ã¯ã³ãåãæ¶ããã¨ãã§ãã¾ãã
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.OidcProviderClient;
import io.quarkus.oidc.OidcSession;
import io.quarkus.oidc.RefreshToken;
import io.smallrye.mutiny.Uni;
@Path("/service")
public class ServiceResource {
@Inject
OidcSession oidcSession;
@Inject
OidcProviderClient oidcProviderClient;
@Inject
AccessTokenCredential accessToken;
@Inject
RefreshToken refreshToken;
@GET
public Uni<String> logout() {
return oidcSession.logout() (1)
.chain(() -> oidcClient.revokeAccessToken(accessToken.getToken())) (2)
.chain(() -> oidcClient.revokeRefreshToken(refreshToken.getToken())) (3)
.map((result) -> "You are logged out");
}
}
| 1 | ã»ãã·ã§ã³ Cookie ãã¯ãªã¢ãã¦ãã¼ã«ã«ãã°ã¢ã¦ããå®è¡ãã¾ãã |
| 2 | èªå¯ã³ã¼ãããã¼ã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ãåãæ¶ãã¾ãã |
| 3 | èªå¯ã³ã¼ãããã¼ã®ãªãã¬ãã·ã¥ãã¼ã¯ã³ãåãæ¶ãã¾ãã |
You can also revoke tokens in the security event listeners.
For example, when your application supports a standard User-initiated logout, you can catch a logout event and revoke tokens:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.OidcProviderClient;
import io.quarkus.oidc.RefreshToken;
import io.quarkus.oidc.SecurityEvent;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.ObservesAsync;
@ApplicationScoped
public class SecurityEventListener {
public CompletionStage<Void> processSecurityEvent(@ObservesAsync SecurityEvent event) {
if (SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED == event.getEventType()) { (1)
return revokeTokens(event.getSecurityIdentity()).subscribeAsCompletionStage();
}
return CompletableFuture.completedFuture(null);
}
private Uni<Void> revokeTokens(SecurityIdentity securityIdentity) {
return Uni.join().all(
revokeAccessToken(securityIdentity),
revokeRefreshToken(securityIdentity)
).andCollectFailures()
.replaceWithVoid()
.onFailure().recoverWithUni(t -> logFailure(t));
}
private static Uni<Boolean> revokeAccessToken(SecurityIdentity securityIdentity) { (2)
OidcProviderClient oidcProvider = securityIdentity.getAttribute(OidcProviderClient.class.getName());
String accessToken = securityIdentity.getCredential(AccessTokenCredential.class).getToken();
return oidcProvider.revokeAccessToken(accessToken);
}
private static Uni<Boolean> revokeRefreshToken(SecurityIdentity securityIdentity) { (3)
OidcProviderClient oidcProvider = securityIdentity.getAttribute(OidcProviderClient.class.getName());
String refreshToken = securityIdentity.getCredential(RefreshToken.class).getToken();
return oidcProvider.revokeRefreshToken(refreshToken);
}
private static Uni<Void> logFailure(Throwable t) {
// Log failure as required
return Uni.createFrom().voidItem();
}
}
| 1 | Revoke tokens if an RP-initiated logout event is observed. |
| 2 | èªå¯ã³ã¼ãããã¼ã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ãåãæ¶ãã¾ãã |
| 3 | èªå¯ã³ã¼ãããã¼ã®ãªãã¬ãã·ã¥ãã¼ã¯ã³ãåãæ¶ãã¾ãã |
䏿µãµã¼ãã¹ã¸ã®ãã¼ã¯ã³ã®ä¼æ
èªå¯ã³ã¼ãããã¼ãã䏿µã®ãµã¼ãã¹ã¸ã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ã®ä¼æã«ã¤ãã¦ã¯ããã¼ã¯ã³ã®ä¼æ ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããã
ã¤ã³ãã°ã¬ã¼ã·ã§ã³ã«é¢ããèæ ®äºé
OIDC ã«ãã£ã¦ä¿è·ãããã¢ããªã±ã¼ã·ã§ã³ã¯ãã·ã³ã°ã«ãã¼ã¸ã¢ããªã±ã¼ã·ã§ã³ããå¼ã³åºããã¨ãã§ããç°å¢ã«çµ±åããã¾ãã ããã¯ãããç¥ããã¦ãã OIDC ãããã¤ãã¼ã¨é£æºããHTTP ãªãã¼ã¹ãããã·ã¼ã®èå¾ã§å®è¡ãããå¿ è¦ãããã»ããå¤é¨ããã³å é¨ã¢ã¯ã»ã¹ãªã©ãå¿ è¦ã¨ããã¾ãã
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããããã®èæ ®äºé ã«ã¤ãã¦èª¬æãã¾ãã
ã·ã³ã°ã«ãã¼ã¸ã¢ããªã±ã¼ã·ã§ã³
ãOpenID Connect (OIDC) ãã¢ã©ã¼ãã¼ã¯ã³èªè¨¼ãã¬ã¤ãã® ã·ã³ã°ã«ãã¼ã¸ã¢ããªã±ã¼ã·ã§ã³ ã»ã¯ã·ã§ã³ã§ææ¡ããã¦ããæ¹æ³ã§ã·ã³ã°ã«ãã¼ã¸ã¢ããªã±ã¼ã·ã§ã³ (SPA) ãå®è£ ããå ´åãè¦ä»¶ãæºããããã確èªã§ãã¾ãã
Quarkus Web ã¢ããªã±ã¼ã·ã§ã³ã§ Fetch ã XMLHttpRequest(XHR) ãªã©ã® SPA ããã³ JavaScript API ã使ç¨ããå ´åã¯ãQuarkus ããã®ãªãã¤ã¬ã¯ãå¾ã«ã¦ã¼ã¶ã¼ãèªè¨¼ãããèªå¯ã¨ã³ããã¤ã³ãã«å¯¾ãã¦ãOIDC ãããã¤ãã¼ãã¯ãã¹ãªãªã¸ã³ãªã½ã¼ã¹å
±æ (CORS) ããµãã¼ãããªãå¯è½æ§ããããã¨ã«æ³¨æãã¦ãã ããã
Quarkus ã¢ããªã±ã¼ã·ã§ã³ã¨ OIDC ãããã¤ãã¼ãç°ãªã HTTP ãã¡ã¤ã³ããã¼ããã¾ãã¯ãã®ä¸¡æ¹ã§ãã¹ãããã¦ããå ´åãèªè¨¼ã«å¤±æãã¾ãã
ãã®ãããªå ´åã¯ã quarkus.oidc.authentication.java-script-auto-redirect ããããã£ã¼ã false ã«è¨å®ãã¾ããããã«ãããQuarkus 㯠499 ã¹ãã¼ã¿ã¹ã³ã¼ã㨠OIDC å¤ãå«ã WWW-Authenticate ãããã¼ãè¿ãããã«æç¤ºããã¾ãã
quarkus.oidc.authentication.java-script-auto-redirect ããããã£ã¼ã false ã«è¨å®ããã¦ããå ´åã« 499 ã¹ãã¼ã¿ã¹ã³ã¼ããè¿ãããããã«ããã«ã¯ããã©ã¦ã¶ã¼ã¹ã¯ãªããã§ç¾å¨ã®ãªã¯ã¨ã¹ãã JavaScript ãªã¯ã¨ã¹ãã¨ãã¦èå¥ããããã®ãããã¼ãè¨å®ããå¿
è¦ãããã¾ãã
ã¹ã¯ãªããã¨ã³ã¸ã³ãã¨ã³ã¸ã³åºæã®ãªã¯ã¨ã¹ããããã¼ãè¨å®ããå ´åã¯ãã«ã¹ã¿ã quarkus.oidc.JavaScriptRequestChecker Bean ãç»é²ã§ãã¾ããããã«ãããç¾å¨ã®ãªã¯ã¨ã¹ãã JavaScript ãªã¯ã¨ã¹ãã§ãããã©ããã Quarkus ã«éç¥ããã¾ãããã¨ãã°ãJavaScript ã¨ã³ã¸ã³ã HX-Request: true ãªã©ã®ãããã¼ãè¨å®ããå ´åã¯ã次ã®ããã«ãã§ãã¯ã§ãã¾ãã
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.JavaScriptRequestChecker;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class CustomJavaScriptRequestChecker implements JavaScriptRequestChecker {
@Override
public boolean isJavaScriptRequest(RoutingContext context) {
return "true".equals(context.request().getHeader("HX-Request"));
}
}
ã¹ãã¼ã¿ã¹ã³ã¼ãã 499 ã®å ´åã¯ãæå¾ã«ãªã¯ã¨ã¹ãããããã¼ã¸ãå度èªã¿è¾¼ã¿ã¾ãã
ãã以å¤ã®å ´åã¯ããã©ã¦ã¶ã¼ã¹ã¯ãªãããæ´æ°ãã X-Requested-With ãããã¼ã« JavaScript å¤ãè¨å®ãã¦ã 499 ã¹ãã¼ã¿ã¹ã³ã¼ãã®å ´åã¯æå¾ã«ãªã¯ã¨ã¹ãããããã¼ã¸ãå度èªã¿è¾¼ã¿ããå¿
è¦ãããã¾ãã
ä¾:
Future<void> callQuarkusService() async {
Map<String, String> headers = Map.fromEntries([MapEntry("X-Requested-With", "JavaScript")]);
await http
.get("https://localhost:443/serviceCall")
.then((response) {
if (response.statusCode == 499) {
window.location.assign("https://localhost.com:443/serviceCall");
}
});
}
ã¯ãã¹ãªãªã¸ã³ãªã½ã¼ã¹å ±æ
å¥ã®ãã¡ã¤ã³ã§å®è¡ããã¦ããã·ã³ã°ã«ãã¼ã¸ã®ã¢ããªã±ã¼ã·ã§ã³ãããã®ã¢ããªã±ã¼ã·ã§ã³ãå©ç¨ããå ´åã¯ãã¯ãã¹ãªãªã¸ã³ãªã½ã¼ã¹å ±æ (CORS) ãè¨å®ããå¿ è¦ãããã¾ãã 詳細ã¯ããã¯ãã¹ãªãªã¸ã³ãªã½ã¼ã¹å ±æãã¬ã¤ãã® CORSãã£ã«ã¿ã¼ ã®ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããã
ã¯ã©ã¦ããããã¤ãã¼ãµã¼ãã¹ã®å¼ã³åºã
Google Cloud
Google Developer Consoles ã® BigQuery ãªã©ã® Google Cloud Services ã«å¯¾ã㦠OIDC èªå¯ã³ã¼ãããã¼æ¨©éãæå¹åãã¦ããç¾å¨èªè¨¼æ¸ã¿ã®ã¦ã¼ã¶ã¼ã«ä»£ãããQuarkus OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ãããããã®ãµã¼ãã¹ã«ã¢ã¯ã»ã¹ã§ããããã«ãããã¨ãå¯è½ã§ãã
Quarkiverse Google ã¯ã©ã¦ããµã¼ãã¹ ã使ç¨ãã¦ããããè¡ããã¨ãã§ãã¾ãã 追å ããå¿ è¦ãããã®ã¯ã 以ä¸ã®ä¾ã«ç¤ºãããã«ã ææ°ã¿ã° ãµã¼ãã¹ä¾åé¢ä¿ã®ã¿ã§ãã
<dependency>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<artifactId>quarkus-google-cloud-bigquery</artifactId>
<version>${quarkiverse.googlecloudservices.version}</version>
</dependency>
implementation("io.quarkiverse.googlecloudservices:quarkus-google-cloud-bigquery:${quarkiverse.googlecloudservices.version}")
次ã«ãGoogle OIDC ããããã£ã¼ãè¨å®ãã¾ãã
quarkus.oidc.provider=google
quarkus.oidc.client-id={GOOGLE_CLIENT_ID}
quarkus.oidc.credentials.secret={GOOGLE_CLIENT_SECRET}
quarkus.oidc.token.issuer=https://accounts.google.com
Quarkus ã¢ããªã±ã¼ã·ã§ã³ã®ãªãã¼ã¹ãããã·ã¼ã®èå¾ã§ã®å®è¡
Quarkus ã¢ããªã±ã¼ã·ã§ã³ããªãã¼ã¹ãããã·ã¼ãã²ã¼ãã¦ã§ã¤ãã¾ãã¯ãã¡ã¤ã¢ã¦ã©ã¼ã«ã®èå¾ã§å®è¡ããã¦ããå ´åã«ãHTTP Host ãããã¼ãå
é¨ IP ã¢ãã¬ã¹ã«ãªã»ããããããã https æ¥ç¶ãçµäºããããããªã©ãã¦ãOIDC èªè¨¼ã¡ã«ããºã ãå½±é¿ãåãããã¨ãããã¾ãã
ãã¨ãã°ãèªå¯ã³ã¼ãããã¼ã® redirect_uri ãã©ã¡ã¼ã¿ã¼ããäºæãããå¤é¨ãã¹ãã§ã¯ãªãå
é¨ãã¹ãã«è¨å®ããã¦ããå ´åãããã¾ãã
ãã®ãããªå ´åããããã·ã¼ã«ãã£ã¦è»¢éãããå ã®ãããã¼ãèªèããããã« Quarkus ãè¨å®ããå¿ è¦ãããã¾ãã 詳細ã¯ããªãã¼ã¹ãããã·ã¼ã®èå¾ã§ã®å®è¡ Vert.x ã®ããã¥ã¡ã³ãã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããã
ãã¨ãã°ãQuarkus ã¨ã³ããã¤ã³ãã Kubernetes Ingress ã®èå¾ã«ããã¯ã©ã¹ã¿ã¼ã§å®è¡ããã¦ããå ´åãè¨ç®ããã redirect_uri ãã©ã¡ã¼ã¿ã¼ãå
é¨ã¨ã³ããã¤ã³ãã¢ãã¬ã¹ãæãã¦ããå¯è½æ§ããããããOIDC ãããã¤ãã¼ãããã®ã¨ã³ããã¤ã³ãã¸ã®ãªãã¤ã¬ã¯ããæ©è½ããªãå¯è½æ§ãããã¾ãã
ãã®åé¡ã¯ãKubernetes Ingress ã«ãã£ã¦å¤é¨ã¨ã³ããã¤ã³ãã¢ãã¬ã¹ã表ãããã« X-ORIGINAL-HOST ãè¨å®ããã¦ããæ¬¡ã®è¨å®ã使ç¨ãããã¨ã§è§£æ±ºã§ãã¾ãã
quarkus.http.proxy.proxy-address-forwarding=true
quarkus.http.proxy.allow-forwarded=false
quarkus.http.proxy.enable-forwarded-host=true
quarkus.http.proxy.forwarded-host-header=X-ORIGINAL-HOST
quarkus.oidc.authentication.force-redirect-https-scheme ããããã£ã¼ã¯ãQuarkus ã¢ããªã±ã¼ã·ã§ã³ã SSL çµäºãªãã¼ã¹ãããã·ã¼ã®èå¾ã§å®è¡ããã¦ããå ´åã«ã使ç¨ã§ãã¾ãã
OIDC ãããã¤ãã¼ã¸ã®å¤é¨ããã³å é¨ã¢ã¯ã»ã¹
OIDC ãããã¤ãã¼ã®å¤é¨ããã¢ã¯ã»ã¹å¯è½ãªèªå¯ããã°ã¢ã¦ããããã³ãã®ä»ã®ã¨ã³ããã¤ã³ãã¯ãèªåæ¤åºããã URL ãå
é¨ URL quarkus.oidc.auth-server-url ã¨ã¯ç°ãªã HTTP(S) URL ãæã¤å ´åãããã¾ãã
ãã®ãããªå ´åãã¨ã³ããã¤ã³ãã¯çºè¡è
ã®æ¤è¨¼ã®å¤±æãå ±åããå¤é¨ããã¢ã¯ã»ã¹å¯è½ãª OIDC ãããã¤ãã¼ã¨ã³ããã¤ã³ãã¸ã®ãªãã¤ã¬ã¯ãã失æããå¯è½æ§ãããã¾ãã
Keycloak ã使ç¨ããå ´åã¯ã KEYCLOAK_FRONTEND_URL ã·ã¹ãã ããããã£ã¼ãå¤é¨ããã¢ã¯ã»ã¹å¯è½ãªãã¼ã¹ URL ã«è¨å®ãã¦èµ·åãã¾ãã
ä»ã® OIDC ãããã¤ãã¼ã¨é£æºããå ´åã¯ããããã¤ãã¼ã®ããã¥ã¡ã³ãã確èªãã¦ãã ããã
OIDC HTTP ã¯ã©ã¤ã¢ã³ããªãã¤ã¬ã¯ã
ãã¡ã¤ã¢ã¦ã©ã¼ã«ã®èå¾ã«ãã OIDC ãããã¤ãã¼ã¯ãQuarkus OIDC HTTP ã¯ã©ã¤ã¢ã³ãã® GET ãªã¯ã¨ã¹ãããæ¢ç¥ã®è¨å®ã¨ã³ããã¤ã³ããªã©ã®ã¨ã³ããã¤ã³ãã«ãªãã¤ã¬ã¯ãããå ´åãããã¾ãã ããã©ã«ãã§ã¯ãQuarkus OIDC HTTP ã¯ã©ã¤ã¢ã³ãã¯ãã»ãã¥ãªãã£ã¼ä¸ã®çç±ãããªãã¤ã¬ã¯ããªã¯ã¨ã¹ãä¸ã«è¨å®ããã Cookie ãé¤å¤ãã¦ãèªåçã« HTTP ãªãã¤ã¬ã¯ããå¾ãã¾ãã
ããã¯ãå¿
è¦ã«å¿ã㦠quarkus.oidc.follow-redirects=false ã§ç¡å¹ã«ã§ãã¾ãã
ãªãã¤ã¬ã¯ãã¸ã®èªå対å¿ãç¡å¹ã«ãªã£ã¦ããå ´åãQuarkus OIDC HTTP ã¯ã©ã¤ã¢ã³ãã¯ãªãã¤ã¬ã¯ããªã¯ã¨ã¹ããåä¿¡ããã¨ããªãã¤ã¬ã¯ã URI ãå ã®ãªã¯ã¨ã¹ã URI ã¨ã¾ã£ããåãã§ããªãã¤ã¬ã¯ããªã¯ã¨ã¹ãä¸ã« 1 ã¤ä»¥ä¸ã® Cookie ãè¨å®ããã¦ããå ´åã«éãããªãã¤ã¬ã¯ã URI ã«å¾ã£ã¦ 1 åã ãå復ã試ã¿ã¾ãã
OIDC SAML ã¢ã¤ãã³ãã£ãã£ã¼ããã¼ã«ã¼
ã¢ã¤ãã³ãã£ãã£ã¼ãããã¤ãã¼ã OpenID Connect ãå®è£
ãã¦ãããã徿¥ã® XML ãã¼ã¹ã® SAML2.0 SSO ãããã³ã«ã®ã¿ãå®è£
ãã¦ããå ´åã quarkus-oidc ã OIDC ã¢ããã¿ã¼ã¨ãã¦ä½¿ç¨ããå ´åã¨åãããã«ãQuarkus ã SAML 2.0 ã¢ããã¿ã¼ã¨ãã¦ä½¿ç¨ãããã¨ã¯ã§ãã¾ããã
ãã ããKeycloakãOktaãAuth0ãMicrosoft ADFS ãªã©ã®å¤ãã® OIDC ãããã¤ãã¼ã¯ãOIDC ãã SAML 2.0 ã¸ã®ããªãã¸ãæä¾ãã¦ãã¾ãã
OIDC ãããã¤ãã¼ã§ SAML 2.0 ãããã¤ãã¼ã¸ã®ã¢ã¤ãã³ãã£ãã£ã¼ããã¼ã«ã¼æ¥ç¶ã使ãã quarkus-oidc ã使ç¨ãã¦ãã® SAML 2.0 ãããã¤ãã¼ã«å¯¾ãã¦ã¦ã¼ã¶ã¼ãèªè¨¼ããOIDC ãããã¤ãã¼ã OIDC 㨠SAML 2.0 ã®éä¿¡ã調æ´ãããã¨ãã§ãã¾ãã
Quarkus ã¨ã³ããã¤ã³ãã«é¢ãã¦ã¯ãåã Quarkus ã»ãã¥ãªãã£ã¼ãOIDC APIã @Authenticatedã SecurityIdentity ãªã©ã®ã¢ããã¼ã·ã§ã³ãªã©ãå¼ãç¶ã使ç¨ã§ãã¾ãã
ãã¨ãã°ã Okta ã SAML 2.0 ãããã¤ãã¼ã§ã Keycloak ã OIDC ãããã¤ãã¼ã ã¨ãã¾ãã
ããã§ã¯ã Keycloak ã Okta SAML 2.0 ãããã¤ãã¼ã¨ä»²ä»ããããã«è¨å®ããæ¹æ³ã説æããä¸è¬çãªã·ã¼ã±ã³ã¹ã示ãã¾ãã
ã¾ãã Okta Dashboard/Applications ã«æ°ãã SAML2 ã¤ã³ãã°ã¬ã¼ã·ã§ã³ã使ãã¾ãã
ãã¨ãã°ã OktaSaml ã¨ååãä»ãã¾ãã
次ã«ãKeycloak SAML ããã¼ã«ã¼ã¨ã³ããã¤ã³ããæãããã«è¨å®ãã¾ãã
ãã®æç¹ã§ãKeycloak ã¬ã«ã ã®åå (ä¾: quarkus) ãç¥ã£ã¦ããå¿
è¦ãããã¾ããKeycloak SAML ããã¼ã«ã¼ã®ã¨ã¤ãªã¢ã¹ã saml ã§ããã¨ä»®å®ãã¦ãã¨ã³ããã¤ã³ãã¢ãã¬ã¹ã http://localhost:8081/realms/quarkus/broker/saml/endpoint ã¨å
¥åãã¾ãã
ãµã¼ãã¹ãããã¤ãã¼ (SP) ã¨ã³ãã£ãã£ã¼ ID ã http://localhost:8081/realms/quarkus ã¨å
¥åãã¾ããããã§ã http://localhost:8081 㯠Keycloak ãã¼ã¹ã¢ãã¬ã¹ã§ã saml ã¯ããã¼ã«ã¼ã¨ã¤ãªã¢ã¹ã§ãã
次ã«ããã® SAML ã¤ã³ãã°ã¬ã¼ã·ã§ã³ãä¿åãããã® Metadata URL ãã¡ã¢ãã¾ãã
ç¶ãã¦ãSAML ãããã¤ãã¼ã Keycloak ã«è¿½å ãã¾ãã
ã¾ããé常ã©ããã«ãæ°ããã¬ã«ã ã使ããããæ¢åã®ã¬ã«ã ã Keycloak ã«ã¤ã³ãã¼ããã¾ãã
ãã®å ´åãã¬ã«ã å㯠quarkus ã«ããå¿
è¦ãããã¾ãã
次ã«ã quarkus ã¬ã«ã ã®ããããã£ã¼ã§ã Identity Providers ã«ç§»åããæ°ãã SAML ãããã¤ãã¼ã追å ãã¾ãã
ã¨ã¤ãªã¢ã¹ã¯ saml ã«è¨å®ããã Redirect URI 㯠http://localhost:8081/realms/quarkus/broker/saml/endpoint ã§ã Service provider entity ID 㯠http://localhost:8081/realms/quarkus ã§ããç¹ã«æ³¨æãã¦ãã ããããããã¯ãåã®æé ã§ Okta SAML Integration ã使ããã¨ãã«å
¥åããå¤ã¨åãã§ãã
æå¾ã«ãåã®æé ã®æå¾ã«æ¸ãçãã Okta SAML Integration Metadata URL ãæãããã« Service entity descriptor ãè¨å®ãã¾ãã
次ã«ãå¿
è¦ã«å¿ãã¦ãAuthentication/browser/Identity Provider Redirector config` ã«ç§»åãã Alias ããããã£ã¼ã¨ Default Identity Provider ããããã£ã¼ã®ä¸¡æ¹ã saml ã«è¨å®ãã¦ããã® Keycloak SAML ãããã¤ãã¼ãããã©ã«ããããã¤ãã¼ã¨ãã¦ç»é²ã§ãã¾ãã
ããã©ã«ãã®ãããã¤ãã¼ã¨ãã¦è¨å®ããªãå ´åã¯ãèªè¨¼æã« Keycloak ã¯æ¬¡ã® 2 ã¤ã®ãªãã·ã§ã³ãæä¾ãã¾ãã
-
SAML ãããã¤ãã¼ã«ããèªè¨¼
-
ååã¨ãã¹ã¯ã¼ãã使ç¨ãã Keycloak ã¸ã®ç´æ¥èªè¨¼
ããã§ãQuarkus OIDC web-app ã¢ããªã±ã¼ã·ã§ã³ããKeycloak quarkus ã¬ã«ã quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus ãæãããã«è¨å®ãã¾ãã
次ã«ãKeycloak OIDC ããã³ Okta SAML 2.0 ãããã¤ãã¼ã«ãã£ã¦æä¾ããã OIDC ãã SAML ã¸ã®ããªãã¸ã使ç¨ãã¦ãQuarkus ã¦ã¼ã¶ã¼ã Okta SAML 2.0 ãããã¤ãã¼ã«èªè¨¼ããæºåãæ´ãã¾ãã
Keycloak ã®å ´åã¨åæ§ã«ãä»ã® OIDC ãããã¤ãã¼ãè¨å®ã㦠SAML ããªãã¸ãæä¾ãããã¨ãã§ãã¾ãã
ãã¹ã
å¥ã® OIDC ã®ãããªãµã¼ãã¼ã¸ã®èªè¨¼ã«é¢ãã¦ã¯ããã¹ããå°é£ã«ãªããã¨ãããããã¾ãã Quarkus ã¯ãã¢ãã¯ãã OIDC ãããã¤ãã¼ã®ãã¼ã«ã«å®è¡ã¾ã§ããã¾ãã¾ãªãªãã·ã§ã³ãæä¾ãã¾ãã
ãã¹ãããã¸ã§ã¯ãã«ä»¥ä¸ã®ä¾åé¢ä¿ã追å ãããã¨ããå§ãã¾ãã
<dependency>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit</artifactId>
<scope>test</scope>
</dependency>
testImplementation("org.htmlunit:htmlunit")
testImplementation("io.quarkus:quarkus-junit")
Dev Services for Keycloak
Keycloak ã«å¯¾ããçµåãã¹ãã®å ´åã¯ãDev services for Keycloak ã使ç¨ãã¾ãã
ãã®ãµã¼ãã¹ã¯ããã¹ãã³ã³ããã¼ãåæåãã quarkus ã¬ã«ã ã使ããã·ã¼ã¯ã¬ãã secret ã使ç¨ã㦠quarkus-app ã¯ã©ã¤ã¢ã³ããè¨å®ãã¾ãã
ã¾ãã admin ããã³ user ãã¼ã«ãæã¤ alice ã¨ã user ãã¼ã«ãæã¤ bob ã® 2 人ã®ã¦ã¼ã¶ã¼ãè¨å®ãã¾ãã
ã¾ãã application.properties ãã¡ã¤ã«ãæºåãã¾ãã
空㮠application.properties ãã¡ã¤ã«ããéå§ããå ´åã Dev Services for Keycloak ã¯æ¬¡ã®ããããã£ã¼ãèªåçã«ç»é²ãã¾ãã
-
å®è¡ä¸ã®ãã¹ãã³ã³ããã¼ãæã
quarkus.oidc.auth-server-url -
quarkus.oidc.client-id=quarkus-app -
quarkus.oidc.credentials.secret=secret
å¿
è¦ãª quarkus-oidc ããããã£ã¼ããã§ã«è¨å®ããã¦ããå ´åã¯ã quarkus.oidc.auth-server-url ã prod ãããã¡ã¤ã«ã«é¢é£ä»ãã¾ãã
ããã«ããã Dev Services for Keycloak ãæå¾
ã©ããã«ã³ã³ããã¼ãèµ·åã§ããããã«ãªãã¾ãã
以ä¸ã¯ãã®ä¾ã§ãã
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
ãã¹ããå®è¡ããåã«ã«ã¹ã¿ã ã¬ã«ã ãã¡ã¤ã«ã Keycloak ã«ã¤ã³ãã¼ãããã«ã¯ã次ã®ããã« Dev services for Keycloak ãè¨å®ãã¾ãã
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.keycloak.devservices.realm-path=quarkus-realm.json
æå¾ã«ãWiremock ã»ã¯ã·ã§ã³ã§èª¬æããã¦ããã¨ãããã¹ãã³ã¼ãã使ãã¾ãã
å¯ä¸ã®éãã¯ã @QuarkusTestResource ãä¸è¦ã«ãªã£ããã¨ã§ãã
@QuarkusTest
public class CodeFlowAuthorizationTest {
}
Wiremock
次ã®ä¾åé¢ä¿ã追å ãã¾ãã
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-test-oidc-server")
REST ãã¹ãã¨ã³ããã¤ã³ããæºåãã application.properties ãè¨å®ãã¾ãã
以ä¸ã«ä¾ã示ãã¾ãã
# keycloak.url is set by OidcWiremockTestResource
quarkus.oidc.auth-server-url=${keycloak.url:replaced-by-test-resource}/realms/quarkus/
quarkus.oidc.client-id=quarkus-web-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
æå¾ã«ãã¹ãã³ã¼ããæ¸ãã¾ããä¾:
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.htmlunit.SilentCssErrorHandler;
import org.htmlunit.WebClient;
import org.htmlunit.html.HtmlForm;
import org.htmlunit.html.HtmlPage;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;
@QuarkusTest
@QuarkusTestResource(OidcWiremockTestResource.class)
public class CodeFlowAuthorizationTest {
@Test
public void testCodeFlow() throws Exception {
try (final WebClient webClient = createWebClient()) {
// the test REST endpoint listens on '/code-flow'
HtmlPage page = webClient.getPage("http://localhost:8081/code-flow");
HtmlForm form = page.getForms().get(0);
// user 'alice' has the 'user' role
form.getInputByName("username").type("alice");
form.getInputByName("password").type("alice");
page = form.getButtonByName("login").click();
assertEquals("alice", page.getBody().asNormalizedText());
}
}
private WebClient createWebClient() {
WebClient webClient = new WebClient();
webClient.setCssErrorHandler(new SilentCssErrorHandler());
return webClient;
}
}
OidcWiremockTestResource 㯠alice 㨠admin ã¦ã¼ã¶ã¼ãèªèãã¾ãã
ã¦ã¼ã¶ã¼ alice ã«ã¯ããã©ã«ãã§ user ãã¼ã«ããããã¾ãããã quarkus.test.oidc.token.user-roles ã·ã¹ãã ããããã£ã¼ã§ã«ã¹ã¿ãã¤ãºã§ãã¾ãã
ã¦ã¼ã¶ã¼ admin ã«ã¯ããã©ã«ãã§ user 㨠admin ãã¼ã«ãããã¾ããã quarkus.test.oidc.token.user-roles ã·ã¹ãã ããããã£ã¼ã§ã«ã¹ã¿ãã¤ãºã§ãã¾ãã
ããã«ã OidcWiremockTestResource ã¯ãã¼ã¯ã³ã®çºè¡è
ã¨å¯¾è±¡ã¦ã¼ã¶ã¼ã https://service.example.com ã«è¨å®ãã¾ããããã¯ã quarkus.test.oidc.token.issuer ããã³ quarkus.test.oidc.token.audience ã·ã¹ãã ããããã£ã¼ã使ç¨ãã¦ã«ã¹ã¿ãã¤ãºã§ãã¾ãã
OidcWiremockTestResource ã¯ããã¹ã¦ã® OIDC ãããã¤ãã¼ãã¨ãã¥ã¬ã¼ãããããã«ä½¿ç¨ã§ãã¾ãã
KeecycloakTestResourceLifecycleManager ã®ä½¿ç¨
Dev Services for Keycloak ã使ç¨ããªãæ£å½ãªçç±ãããå ´åã«ã®ã¿ããã¹ãã«ã¯ KeycloakTestResourceLifecycleManager ã使ç¨ãã¦ãã ããã
Keycloak ã«å¯¾ããçµåãã¹ããå¿
è¦ãªå ´åã¯ãDev Services for Keycloak ã使ç¨ãã¦ãã¹ããããã¨ãæ¨å¥¨ãã¾ãã
ã¾ãã以ä¸ã®ä¾åé¢ä¿ã追å ãã¾ãã
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-test-keycloak-server")
ããã¯ãKeycloak ã³ã³ããã¼ãèµ·åãã io.quarkus.test.common.QuarkusTestResourceLifecycleManager ã®å®è£
ã§ãã io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager ãæä¾ãã¾ãã
次ã«ãMaven Surefire ãã©ã°ã¤ã³ã次ã®ããã«è¨å®ãã¾ã (ãã¤ãã£ãã¤ã¡ã¼ã¸ã§ãã¹ãããå ´åã¯ãMaven Failsafe ãã©ã°ã¤ã³ãåæ§ã«è¨å®ãã¾ã)ã
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<!-- or, alternatively, configure 'keycloak.version' -->
<keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
<!--
Disable HTTPS if required:
<keycloak.use.https>false</keycloak.use.https>
-->
</systemPropertyVariables>
</configuration>
</plugin>
次ã«ãè¨å®ãè¡ããWiremock ã»ã¯ã·ã§ã³ã«èª¬æããã¦ããã®ã¨åãæ¹æ³ã§ãã¹ãã³ã¼ããè¨è¿°ãã¾ãã
å¯ä¸ã®éã㯠QuarkusTestResource ã®ååã§ãã
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
@QuarkusTest
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
public class CodeFlowAuthorizationTest {
}
KeycloakTestResourceLifecycleManager 㯠alice 㨠admin ã¦ã¼ã¶ã¼ãç»é²ãã¾ãã
ã¦ã¼ã¶ã¼ alice ã«ã¯ããã©ã«ãã§ user ãã¼ã«ããããã¾ãããã keycloak.token.user-roles ã·ã¹ãã ããããã£ã¼ã§ã«ã¹ã¿ãã¤ãºã§ãã¾ãã
ã¦ã¼ã¶ã¼ admin ã«ã¯ããã©ã«ãã§ user 㨠admin ãã¼ã«ãããã¾ããã keycloak.token.admin-roles ã·ã¹ãã ããããã£ã¼ã§ã«ã¹ã¿ãã¤ãºã§ãã¾ãã
ããã©ã«ãã§ã¯ã KeycloakTestResourceLifecycleManager ã HTTPS ã使ç¨ã㦠Keycloak ã¤ã³ã¹ã¿ã³ã¹ãåæåãã¾ããããã¯ã keycloak.use.https=false ãæå®ãããã¨ã§ç¡å¹ã«ãããã¨ãã§ãã¾ãã
ããã©ã«ãã®ã¬ã«ã å㯠quarkus ã§ãã¯ã©ã¤ã¢ã³ã ID 㯠quarkus-web-app ã§ããå¿
è¦ã«å¿ãã¦ã keycloak.realmã keycloak.web-app.client ã·ã¹ãã ããããã£ã¼ãè¨å®ãã¦å¤ãã«ã¹ã¿ãã¤ãºãã¦ãã ããã
TestSecurity ã¢ããã¼ã·ã§ã³
@TestSecurity ããã³ @OidcSecurity ã¢ããã¼ã·ã§ã³ã使ç¨ãã¦ãæ¬¡ã®æ³¨å
¥ã®ãããããã¾ã㯠4 ã¤ãã¹ã¦ã«ä¾åãã web-app ã¢ããªã±ã¼ã·ã§ã³ã¨ã³ããã¤ã³ãã³ã¼ãããã¹ãã§ãã¾ãã
-
ID
JsonWebToken -
Access
JsonWebToken -
UserInfo -
OidcConfigurationMetadata
詳細ã¯ãæ³¨å ¥ããã JsonWebToken ã§ã® TestingSecurityã®ä½¿ç¨ ãåç §ãã¦ãã ããã
ãã°ã§ã®ã¨ã©ã¼ç¢ºèª
ãã¼ã¯ã³æ¤è¨¼ã¨ã©ã¼ã®è©³ç´°ã確èªããã«ã¯ã io.quarkus.oidc.runtime.OidcProvider ã® TRACE ã¬ãã«ã®ãã®ã³ã°ãæå¹ã«ããå¿
è¦ãããã¾ãã
quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".min-level=TRACE
OidcProvider ã¯ã©ã¤ã¢ã³ãã®åæåã¨ã©ã¼ã®è©³ç´°ã確èªããã«ã¯ã io.quarkus.oidc.runtime.OidcRecorder ã® TRACE ã¬ãã«ã®ãã®ã³ã°ãæå¹ã«ãã¦ãã ããã
quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".min-level=TRACE
ã¢ããªã±ã¼ã·ã§ã³ã®ã°ãã¼ãã«ãã°ã¬ãã«ã夿´ããã«ã¯ã quarkus dev ã³ã³ã½ã¼ã«ãã j ã¨å
¥åãã¾ãã
ããã°ã©ã ã«ãã OIDC ã®ã¹ã¿ã¼ãã¢ãã
OIDC ããã³ãã¯ã次ã®ä¾ã®ããã«ããã°ã©ã ã§ä½æã§ãã¾ãã
package io.quarkus.it.oidc;
import io.quarkus.oidc.Oidc;
import jakarta.enterprise.event.Observes;
public class OidcStartup {
void observe(@Observes Oidc oidc) {
oidc.createWebApp("http://localhost:8180/realms/quarkus", "quarkus-app", "mysecret");
}
}
ä¸è¨ã®ã³ã¼ãã¯ã application.properties ãã¡ã¤ã«å
ã®æ¬¡ã®è¨å®ã¨ããã°ã©ã çã«åçã§ãã
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.application-type=web-app
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=mysecret
ããã«å¤ãã® OIDC ããã³ãããããã£ã¼ãè¨å®ããå¿
è¦ãããå ´åã¯ã次ã®ä¾ã®ããã« OidcTenantConfig ãã«ãã¼ã使ç¨ãã¾ãã
package io.quarkus.it.oidc;
import io.quarkus.oidc.Oidc;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.Secret.Method;
import jakarta.enterprise.event.Observes;
public class OidcStartup {
void createDefaultTenant(@Observes Oidc oidc) {
var defaultTenant = OidcTenantConfig
.authServerUrl("http://localhost:8180/realms/quarkus/")
.clientId("quarkus-app")
.credentials().clientSecret("mysecret", Method.POST).end()
.build();
oidc.create(defaultTenant);
}
}
è¤æ°ã®ããã³ããé¢ä¿ããããè¤éãªè¨å®ã«ã¤ãã¦ã¯ãOpenID Connect ãã«ãããã³ã·ã¼ã¬ã¤ãã® ãã«ãããã³ãã¢ããªã±ã¼ã·ã§ã³ç¨ã®ããã°ã©ã ã«ãã OIDC ã®èµ·å ã»ã¯ã·ã§ã³ãåç §ãã¦ãã ããã