This project is an example implementation of a OpenID Connect Relying Party built using Spring Boot and Spring Security OAuth2 Client.
Keycloak is used as the public Identity Provider for testing purposes
The Spring Boot application exposes its JWKS at https://localhost:8081/oauth2/jwks
Start Keycloak
kc start-dev
Access Keycloak at https://localhost:8080/
- Create an administrative user
- Login to the administrative console
- Create the
test
realm- Click on the
Keycloak
dropdown on the left navigation bar - Select
Create Realm
- Set
test
as theRealm name
and click onCreate
- Click on the
- Create the
keycloak-spring-boot-example
client- Click on
Clients
on the left navigation bar - Click on
Create client
- Set
keycloak-spring-boot-example
as theClient ID
and click onNext
- Toggle
Client authentication
toOn
and click onNext
- Set
https://localhost:8081/*
asValid redirect URIs
,Valid post logout redirect URIs
andWeb origins
and click onSave
- Under
Logout settings
- Toggle
Front channel logout
toOff
- Set
https://localhost:8081/logout/connect/back-channel/keycloak
asBackchannel logout URL
and click onSave
- Toggle
- Click on
Credentials
- Click on the
Client Authenticator
dropdown and selectSigned Jwt
and click onSave
- Click on
Keys
- Toggle
Use JWKS URL
toOn
and enterhttps://localhost:8081/oauth2/jwks
as theJWKS URL
and click onSave
- Click on
- Allow user registrations
- Click on
Realm settings
on the left navigation bar - Click on
Login
- Toggle
User registration
toOn
- Click on
mvn spring-boot:run
Description | Endpoint |
---|---|
Access the application | https://localhost:8081/login-user |
Logout from the application | https://localhost:8081/logout |
View the public keys | https://localhost:8081/oauth2/jwks |
Access Keycloak user account | https://localhost:8080/realms/test/account |
Logout from Keycloak | https://localhost:8080/realms/test/protocol/openid-connect/logout |
Note that this example does not securely store the private keys which are located in src/main/resources/jwks.json
. This should be securely stored and rotated, for instance on AWS this should be stored in AWS Secrets Manager with a Secrets Manager Rotation Lambda. This can be configured in Spring using Spring Cloud AWS Secrets Manager.
Keycloak will call the endpoint https://localhost:8080/oauth2/jwks in order to get the public keys of the application
- The public verification key used for Keycloak to verify the signature for the
private_key_jwt
client assertion
The JWKSet
@Bean
used by the application is produced in WebSecurityConfiguration
and its public keys are exposed using the @RestController
JwksController
. Note that JWKSet.toString()
removes the private components of the keys.
Keycloak was configured to use private_key_jwt
client authentication by setting Signed Jwt
as the Client Authenticator
.
Spring needs to be configured to use this client authentication method, and the token response client needs to be configured to use the appropriate private key to perform the signing.
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: keycloak-spring-boot-example
client-authentication-method: private_key_jwt
authorization-grant-type: authorization_code
scope:
- openid
The DefaultAuthorizationCodeTokenResponseClient
is used with the NimbusJwtClientAuthenticationParametersConverter
configured with the private key and NimbusJwtClientAuthenticationParametersConverter
used to customize the claims and headers on the client assertion.
Spring supports the use of the end_session_endpoint
to inform Keycloak that the user is being logged out. This is done by configuring a OidcClientInitiatedLogoutSuccessHandler
as the logout success handler. Upon logout success this will redirect the browser to the end_session_endpoint
with the id_token_hint
set. Keycloak will then redirect the browser back to the application using the post_logout_redirect_uri
.
Spring supports receiving back channel logout requests from Keycloak if the user logs out from Keycloak.
http.oidcLogout(oidcLogout -> oidcLogout.backChannel(withDefaults()));
The client on Keycloak needs to be configured to
- Toggle
Front channel logout
toOff
- Set
https://localhost:8081/logout/connect/back-channel/keycloak
asBackchannel logout URL
Where keycloak
corresponds to the client registration id.
sequenceDiagram
autonumber
participant User
participant Browser
participant Application Server
participant Keycloak
User->>Browser: Navigate to user information page
Browser->>Application Server: Send request for user information page (/login-user)
Application Server->>Application Server: Not authenticated, determine provider to redirect to
Application Server-->>Browser: Redirect to Application for Keycloak provider
Browser->>Application Server: Send request for Keycloak provider (/oauth2/authorization/keycloak)
Application Server->>Application Server: Generate Authorization Request
Application Server-->>Browser: Redirect to Keycloak with Authorization Request
Browser->>Keycloak: Send Authorization Request (/realms/test/protocol/openid-connect/auth)
Keycloak->>Keycloak: Generate login page
Keycloak-->>Browser: Return login page
User->>Browser: Enter login credentials and sign in
Browser->>Keycloak: Send login credentials
Keycloak->>Keycloak: Process login credentials
Keycloak-->>Browser: Redirect to Application with Authorization Response with code
Browser->>Application Server: Send Authorization Response with code (/login/oauth2/code/keycloak)
Application Server->>Application Server: Generate Token Request and client credentials
Application Server->>Keycloak: Send Token Request with client credentials (/realms/test/protocol/openid-connect/token)
Keycloak->>Application Server: Send request for client json web key set (/oauth2/jwks)
Application Server-->>Keycloak: Return client json web key set
Keycloak->>Keycloak: Process client credentials and create tokens
Keycloak-->>Application Server: Return Token Response with access, refresh and id tokens
Application Server-->>Browser: Redirect to user information page
Browser->>Application Server: Send request for user information page (/login-user)
Application Server->>Application Server: Authenticated
Application Server-->>Browser: Return user information page