diff --git a/docs/src/main/asciidoc/oidc-guide.adoc b/docs/src/main/asciidoc/oidc-guide.adoc index 65cb96e6b7a2c..fd21cd2801a74 100644 --- a/docs/src/main/asciidoc/oidc-guide.adoc +++ b/docs/src/main/asciidoc/oidc-guide.adoc @@ -142,10 +142,8 @@ The OpenID Connect extension allows you to define the adapter configuration usin [source,properties] ---- -quarkus.oidc.realm=quarkus -quarkus.oidc.auth-server-url=http://localhost:8180/auth -quarkus.oidc.resource=backend-service -quarkus.oidc.bearer-only=true +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus +quarkus.oidc.client-id=backend-service quarkus.oidc.credentials.secret=secret ---- diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcConfig.java index 57cf54ef469a9..1916735ca7582 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcConfig.java @@ -1,6 +1,5 @@ package io.quarkus.oidc; -import java.util.Map; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; @@ -12,282 +11,39 @@ public class OidcConfig { /** - * Name of the realm. + * The base URL of the OpenID Connect (OIDC) server, for example, 'https://host:port/auth'. + * All the other OIDC server page and service URLs are derived from this URL. + * Note if you work with Keycloak OIDC server, make sure the base URL is in the following format: + * 'https://host:port/auth/realms/{realm}' where '{realm}' has to be replaced by the name of the Keycloak realm. */ @ConfigItem - String realm; + String authServerUrl; /** - * Name of the realm key. + * Relative path of the RFC7662 introspection service address. + * Default value is currently set to the path supported by Keycloak. */ - @ConfigItem - Optional realmPublicKey; + @ConfigItem(defaultValue = "/protocol/openid-connect/token/introspect") + String introspectionPath; /** - * The client-id of the application. Each application has a client-id that is used to identify the application + * Public key for the local JWT token verification. */ @ConfigItem - Optional resource; + Optional publicKey; /** - * The base URL of the Keycloak server. All other Keycloak pages and REST service endpoints are derived from this. - * It is usually of the form https://host:port/auth - */ - @ConfigItem - String authServerUrl; - /** - * If this application is a public client + * The client-id of the application. Each application has a client-id that is used to identify the application */ @ConfigItem - boolean publicClient; + Optional clientId; /** - * Specify the credentials of the application. This is an object notation where the key is the credential type and the - * value is the value of the credential type. Currently password and jwt is supported + * Credentials which the OIDC adapter will use to authenticate to the OIDC server. */ @ConfigItem Credentials credentials; - /** - * This should be set to true for services. If enabled the adapter will not attempt to authenticate users, - * but only verify bearer tokens - */ - @ConfigItem(defaultValue = "true") - boolean bearerOnly; - - //TODO: the following are all config options that the standard keycloak adater supports. we need to see how many of them - //map to vert.x and which ones are still relevant - // /** - // * Ensures that all communication to and from the Keycloak server is over HTTPS. In production this should be set to all. - // * This is OPTIONAL. The default value is external meaning that HTTPS is required by default for external requests. - // * Valid values are 'all', 'external' and 'none' - // */ - // @ConfigItem(defaultValue = "external") - // String sslRequired; - // - // /** - // * The confidential port used by the Keycloak server for secure connections over SSL/TLS - // */ - // @ConfigItem(defaultValue = "8443") - // int confidentialPort; - // - // /** - // * If set to true, the adapter will look inside the token for application level role mappings for the user. - // * If false, it will look at the realm level for user role mappings - // */ - // @ConfigItem - // boolean useResourceRoleMappings; - // - // /** - // * This enables CORS support. It will handle CORS preflight requests. It will also look into the access token to - // * determine valid origins - // */ - // @ConfigItem - // boolean enableCors; - // - // /** - // * If CORS is enabled, this sets the value of the Access-Control-Max-Age header. This is OPTIONAL. If not set, - // * this header is not returned in CORS responses - // */ - // @ConfigItem(defaultValue = "-1") - // int corsMaxAge; - // - // /** - // * If CORS is enabled, this sets the value of the Access-Control-Allow-Headers header. This should be a comma-separated - // * string - // */ - // @ConfigItem - // Optional corsAllowedHeaders; - // - // /** - // * If CORS is enabled, this sets the value of the Access-Control-Allow-Methods header. This should be a comma-separated - // * string - // */ - // @ConfigItem - // Optional corsAllowedMethods; - // - // /** - // * If CORS is enabled, this sets the value of the Access-Control-Expose-Headers header. This should be a comma-separated - // * string - // */ - // @ConfigItem - // Optional corsExposedHeaders; - // - - // - // /** - // * This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST). - // * It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 - // * status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page. - // * Keycloak auto-detects SOAP or REST clients based on typical headers like X-Requested-With, SOAPAction or Accept - // */ - // @ConfigItem - // boolean autodetectBearerOnly; - // - - // - // /** - // * If the Keycloak server requires HTTPS and this config option is set to true the Keycloak server’s certificate is - // * validated via the truststore, but host name validation is not done. This setting should only be used during development - // * and never in production as it will disable verification of SSL certificates. This setting may be useful in test - // * environments - // */ - // @ConfigItem - // boolean allowAnyHostname; - // - // /** - // * If the Keycloak server requires HTTPS and this config option is set to true you do not have to specify a truststore. - // * This setting should only be used during development and never in production as it will disable verification - // * of SSL certificates - // */ - // @ConfigItem - // boolean disableTrustManager; - // - // /** - // * If the adapter should refresh the access token for each request - // */ - // @ConfigItem - // boolean alwaysRefreshToken; - // - // /** - // * The value is the file path to a keystore file. If you prefix the path with classpath:, then the truststore will be - // * obtained from the deployment’s classpath instead. Used for outgoing HTTPS communications to the Keycloak server - // */ - // @ConfigItem - // Optional truststore; - // - // /** - // * Password for the truststore keystore - // */ - // @ConfigItem - // String truststorePassword; - // - // /** - // * This is the file path to a keystore file. This keystore contains client certificate for two-way SSL when the adapter - // * makes HTTPS requests to the Keycloak server - // */ - // @ConfigItem - // Optional clientKeystore; - // - // /** - // * Password for the client keystore - // */ - // @ConfigItem - // String clientKeystorePassword; - // - // /** - // * Password for the client’s key - // */ - // @ConfigItem - // String clientKeyPassword; - // - // /** - // * Adapters will make separate HTTP invocations to the Keycloak server to turn an access code into an access token. - // * This config option defines how many connections to the Keycloak server should be pooled - // */ - // @ConfigItem(defaultValue = "20") - // int connectionPoolSize; - // - // /** - // * If true, then adapter will send registration request to Keycloak. It’s false by default and useful only when application - // * is clustered - // */ - // @ConfigItem - // boolean registerNodeAtStartup; - // - // /** - // * Period for re-registration adapter to Keycloak. Useful when application is clustered - // */ - // @ConfigItem(defaultValue = "-1") - // int registerNodePeriod; - // - // /** - // * Possible values are session and cookie. Default is session, which means that adapter stores account info in HTTP Session. - // * Alternative cookie means storage of info in cookie - // */ - // @ConfigItem - // Optional tokenStore; - // - // /** - // * When using a cookie store, this option sets the path of the cookie used to store account info. If it’s a relative path, - // * then it is assumed that the application is running in a context root, and is interpreted relative to that context root. - // * If it’s an absolute path, then the absolute path is used to set the cookie path. Defaults to use paths relative to the - // * context root - // */ - // @ConfigItem - // Optional adapterStateCookiePath; - // - // /** - // * OpenID Connect ID Token attribute to populate the UserPrincipal name with. If token attribute is null. Possible values - // * are sub, preferred_username, email, name, nickname, given_name, family_name - // */ - // @ConfigItem(defaultValue = "sub") - // String principalAttribute; - // - // /** - // * The session id is changed by default on a successful login on some platforms to plug a security attack vector. - // * Change this to true if you want to turn this off - // */ - // @ConfigItem - // boolean turnOffChangeSessionIdOnLogin; - // - // /** - // * Amount of time, in seconds, to preemptively refresh an active access token with the Keycloak server before it expires. - // * This is especially useful when the access token is sent to another REST client where it could expire before being - // * evaluated. This value should never exceed the realm’s access token lifespan - // */ - // @ConfigItem - // int tokenMinimumTimeToLive; - // - // /** - // * Amount of time, in seconds, specifying minimum interval between two requests to Keycloak to retrieve new public keys. - // * It is 10 seconds by default. Adapter will always try to download new public key when it recognize token with unknown kid. - // * However it won’t try it more than once per 10 seconds (by default). This is to avoid DoS when attacker sends lots of - // * tokens with bad kid forcing adapter to send lots of requests to Keycloak - // */ - // @ConfigItem(defaultValue = "10") - // int minTimeBetweenJwksRequests; - // - // /** - // * Amount of time, in seconds, specifying maximum interval between two requests to Keycloak to retrieve new public keys. - // * It is 86400 seconds (1 day) by default. Adapter will always try to download new public key when it recognize token - // * with unknown kid . If it recognize token with known kid, it will just use the public key downloaded previously. - // * However at least once per this configured interval (1 day by default) will be new public key always downloaded even if - // * the kid of token is already known - // */ - // @ConfigItem(defaultValue = "86400") - // int publicKeyCacheTtl; - - // /** - // * If set to true, then during authentication with the bearer token, the adapter will verify whether the token contains - // * this client name (resource) as an audience. The option is especially useful for services, which primarily serve - // * requests authenticated by the bearer token. This is set to false by default, however for improved security, it is - // * recommended to enable this. See Audience Support for more details about audience support - // */ - // @ConfigItem - // boolean verifyTokenAudience; - - // /** - // * If set to true will turn off processing of the access_token query parameter for bearer token processing. - // * Users will not be able to authenticate if they only pass in an access_token - // */ - // @ConfigItem(name = "ignore-oauth-query-parameter") - // boolean ignoreOAuthQueryParameter; - - // /** - // * The proxy url to use for requests to the auth-server. - // */ - // @ConfigItem - // Optional proxyUrl; - - // /** - // * If needed, specify the Redirect URI rewrite rule. This is an object notation where the key is the regular expression to - // * which the Redirect URI is to be matched and the value is the replacement String. $ character can be used for - // * backreferences in the replacement String - // */ - // @ConfigItem - // Map redirectRewriteRules; - @ConfigGroup public static class Credentials { @@ -297,17 +53,6 @@ public static class Credentials { @ConfigItem Optional secret; - /** - * The settings for client authentication with signed JWT - */ - @ConfigItem - Map jwt; - - /** - * The settings for client authentication with JWT using client secret - */ - @ConfigItem - Map secretJwt; } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxKeycloakRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxKeycloakRecorder.java index 937706b21be86..50ad0513d9421 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxKeycloakRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxKeycloakRecorder.java @@ -22,39 +22,25 @@ public class VertxKeycloakRecorder { public void setup(OidcConfig config, RuntimeValue vertx, BeanContainer beanContainer) { OAuth2ClientOptions options = new OAuth2ClientOptions(); - if (config.resource.isPresent()) { - options.setClientID(config.resource.get()); + // Base IDP server URL + options.setSite(config.authServerUrl); + // RFC7662 introspection service address + options.setIntrospectionPath(config.introspectionPath); + + if (config.clientId.isPresent()) { + options.setClientID(config.clientId.get()); } if (config.credentials.secret.isPresent()) { options.setClientSecret(config.credentials.secret.get()); } - - if (!config.publicClient) { - options.setUseBasicAuthorizationHeader(true); - } - - final String realm = config.realm; - - String siteUri = config.authServerUrl + "/realms/" + realm; - options.setSite(siteUri); - options.setAuthorizationPath("/realms/" + realm + "/protocol/openid-connect/auth"); - options.setTokenPath("/realms/" + realm + "/protocol/openid-connect/token"); - options.setRevocationPath(null); - options.setLogoutPath("/realms/" + realm + "/protocol/openid-connect/logout"); - options.setUserInfoPath("/realms/" + realm + "/protocol/openid-connect/userinfo"); - // keycloak follows the RFC7662 - options.setIntrospectionPath("/realms/" + realm + "/protocol/openid-connect/token/introspect"); - // keycloak follows the RFC7517 - options.setJwkPath("/realms/" + realm + "/protocol/openid-connect/certs"); - - if (config.realmPublicKey.isPresent()) { + if (config.publicKey.isPresent()) { options.addPubSecKey(new PubSecKeyOptions() .setAlgorithm("RS256") - .setPublicKey(config.realmPublicKey.get())); + .setPublicKey(config.publicKey.get())); } - //TODO: remove this + //TODO: remove this temporary code block byte[] bogus = new byte[512]; new SecureRandom().nextBytes(bogus); @@ -62,6 +48,7 @@ public void setup(OidcConfig config, RuntimeValue vertx, BeanContainer be new PubSecKeyOptions().setSymmetric(true).setPublicKey(Base64.getEncoder().encodeToString(bogus)) .setAlgorithm("HS512")); options.setFlow(OAuth2FlowType.AUTH_JWT); + // End of the temporary code block CompletableFuture cf = new CompletableFuture<>(); KeycloakAuth.discover(vertx.getValue(), options, new Handler>() { @@ -79,7 +66,7 @@ public void handle(AsyncResult event) { beanContainer.instance(VertxOAuth2IdentityProvider.class).setAuth(auth); VertxOAuth2AuthenticationMechanism mechanism = beanContainer.instance(VertxOAuth2AuthenticationMechanism.class); mechanism.setAuth(auth); - mechanism.setAuthServerURI(siteUri); + mechanism.setAuthServerURI(config.authServerUrl); mechanism.setConfig(config); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxOAuth2AuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxOAuth2AuthenticationMechanism.java index d85370be2b711..66dbf8fce554d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxOAuth2AuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/VertxOAuth2AuthenticationMechanism.java @@ -75,7 +75,7 @@ public CompletionStage authenticate(RoutingContext context, } String token = authorization.substring(idx + 1); - return identityProviderManager.authenticate(new TokenAuthenticationRequest(new TokenCredential(token, "oauth2"))); + return identityProviderManager.authenticate(new TokenAuthenticationRequest(new TokenCredential(token, BEARER))); } @Override diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties index 124bd766f7da9..ca949a39308bb 100644 --- a/integration-tests/oidc/src/main/resources/application.properties +++ b/integration-tests/oidc/src/main/resources/application.properties @@ -1,7 +1,5 @@ # Configuration file -quarkus.oidc.auth-server-url=${keycloak.url} -quarkus.oidc.realm=quarkus -quarkus.oidc.resource=quarkus-app -quarkus.oidc.bearer-only=true +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus +quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=secret