oauth2: add a generic OIDC provider type - #14205
nagaboinaramgopal wants to merge 1 commit into
Conversation
The OAuth2 plugin resolves a login to a UserOAuth2Authenticator through a fixed provider-name to Spring-bean map, and each OIDC vendor is its own bean running the same authorization-code flow with no vendor-specific logic. A new IdP needs a new class, and since provider is both the display name and the routing key, a domain can register only one keycloak. This adds a type column to oauth_provider. OAuth2AuthManagerImpl looks the name up in the bean map first and only on a miss falls back to the registration's type, so provider becomes an admin-chosen label and type selects the implementation. One bean then serves any number of registrations under arbitrary names. Existing google, github and keycloak rows carry a null type and dispatch by name as before. GenericOIDCOAuth2Provider is registered under type oidc and configured with the issuer URL. It reads the token and JWKS endpoints from the issuer's discovery document (the issuer must match), and validates the id_token before trusting it: signature against the JWKS key named by the token kid, then issuer, audience and expiry, using the CXF JOSE library already on the classpath. It holds no token state between logins, and a code verified through verifyOAuthCodeAndGetUser is redeemed once so the following oauthlogin does not re-present it. ListOAuthProvidersCmd and UpdateOAuthProviderCmd derived the response enabled flag from a name-to-bean check, which reported every generically-named registration as disabled; both now also accept a registration whose type resolves to a plugin. The schema change adds type and issuer_url to oauth_provider in the 4.23.0.0 to 24.0.0 upgrade file. Login.vue renders the OAuth buttons from the registered provider list so a generic oidc provider gets a Sign in with <label> button and starts the flow from the issuer's authorize endpoint, resolved from discovery.
|
This is the generic OIDC provider from the discussion on #13854: one It overlaps #13499 (ForgeRock), which @bddvlpr put on hold in favour of doing the generic feature. My @bddvlpr @Damans227 this builds on your design, so please review. If you would rather fold the ForgeRock case in here as an |
Description
The OAuth2 plugin resolves a login to a
UserOAuth2Authenticatorthrough a fixedprovider-name to Spring-bean map. Each OIDC vendor is its own bean
(
GoogleOAuth2Provider,GithubOAuth2Provider,KeycloakOAuth2Provider) running thesame authorization-code flow with no vendor-specific logic in it. Two consequences:
adding an IdP means shipping a new class, and because
provideris simultaneously thedisplay name and the routing key, a domain can register exactly one
keycloak.This PR decouples the two by adding a
typecolumn tooauth_provider:OAuth2AuthManagerImpl.getUserOAuth2AuthenticationProviderlooks the name up in thebean map first, as before, and only on a miss falls back to the registration's
type.providerbecomes an admin-chosen label;typeselects the implementation. One beancan serve any number of registrations under arbitrary names.
getUserOAuth2AuthenticationProvider,verifyUserandverifySecretCodeAndFetchEmailnow carry the registration name, so a shared bean knows which row it is acting for.
google/github/keycloakrows have a nulltypeand continue to dispatchby name, on the same code path as today.
GenericOIDCOAuth2Provideris registered undertype=oidcand configured with the issuerURL alone. It reads
token_endpointandjwks_urifrom the issuer's.well-known/openid-configuration(cached; the document'sissuermust match theconfigured value), and validates the
id_tokenbefore trusting any claim in it: signatureagainst the JWKS key named by the token
kid, then issuer, audience and expiry, via CXF'sJwsJwtCompactConsumer/JwkUtils/JwtUtils. That iscxf-rt-rs-security-jose,already on the plugin classpath, so no new dependency.
Unlike the vendor providers, it holds no token in an instance field. The code is exchanged
per call, and a code verified through
verifyOAuthCodeAndGetUseris cached for 60s andconsumed once, so the
oauthloginthat immediately follows does not re-present it to theIdP.
API / response fix:
ListOAuthProvidersCmdandUpdateOAuthProviderCmdderived theresponse
enabledflag fromauthenticatorPluginNames.contains(provider), aname-to-bean check, which reported every generically-named registration as disabled. Both
now also accept a registration whose
typeresolves to a plugin.typeis settable on register but not on update: changing it would swap theimplementation under an existing row.
Schema: adds
typeandissuer_urltooauth_providerin the 4.23.0.0 to 24.0.0upgrade file.
UI:
Login.vuerenders a button per registeredoidcprovider (in addition to theexisting google/github/keycloak buttons), labelled "Sign in with ". Clicking it
reads the issuer's
authorization_endpointfrom discovery and starts theauthorization-code flow. As with the keycloak provider, the registered
redirecturimustcarry
verifyOauth(for examplehttps://<ui-host>/client?verifyOauth) so the callbacklands on the verify handler.
Fixes: #9609
Types of changes
Feature/Enhancement Scale or Bug Severity
Feature/Enhancement Scale
Screenshots (if appropriate):
How Has This Been Tested?
Unit tests - oauth2 module, 103 tests green.
GenericOIDCOAuth2ProviderTest(27 tests) generates an RSA keypair, publishes thematching JWKS, and asserts that a genuinely signed token is accepted while these are
rejected: a token with altered claims, one signed by a different key, one naming a
kidabsent from the JWKS, and issuer / audience / expiry mismatches. Also covers discovery
document validation (issuer mismatch) and that a verified code is redeemed exactly once.
OAuth2AuthManagerImplTestcovers thetypefallback, an unknowntyperejected atregister time, and a real bean name winning without a DB lookup.
DatabaseUpgradeCheckerTestgreen with the new schema file.Integration - KVM advanced zone against Keycloak 25. Registered an
oidcproviderpointing at a Keycloak realm and logged a real user in through the Keycloak form:
verifyOAuthCodeAndGetUservalidates the RS256 signature against the realm JWKS andreturns the email,
oauthloginwith the same code issues the session, andlistOauthProvidersreports the generically-named provider as enabled.How did you try to break this feature and the system with this change?
user's email - rejected, no session.
google/github/keycloakregistrations are untouched and still log in(name-based dispatch, null
type).issuerdoes not match the configured issuer URL - rejected.