Permalink
Fetching contributors…
Cannot retrieve contributors at this time
162 lines (117 sloc) 4.69 KB

CLI / Desktop Applications

{project_name} supports securing desktop (e.g. Swing, JavaFX) or CLI applications via the KeycloakInstalled adapter by performing the authentication step via the system browser.

The KeycloakInstalled adapter supports a desktop and a manual variant. The desktop variant uses the system browser to gather the user credentials. The manual variant reads the user credentials from STDIN.

Tip: Google provides some more information about this approach on at OAuth2InstalledApp.

How it works

To authenticate a user with the desktop variant the KeycloakInstalled adapter opens a desktop browser window where a user uses the regular {project_name} login pages to login when the loginDesktop() method is called on the KeycloakInstalled object.

The login page URL is opened with redirect parameter that points to a local ServerSocket listening on a free ephemeral port on localhost which is started by the adapter.

After a succesful login the KeycloakInstalled receives the authorization code from the incoming HTTP request and performs the authorization code flow. Once the code to token exchange is completed the ServerSocket is shutdown.

Tip
If the user already has an active {project_name} session then the login form is not shown but the code to token exchange is continued, which enables a smooth Web based SSO experience.

The client eventually receives the tokens (access_token, refresh_token, id_token) which can then be used to call backend services.

The KeycloakInstalled adapter provides support for renewal of stale tokens.

Adapter Installation
<dependency>
	<groupId>org.keycloak</groupId>
	<artifactId>keycloak-installed-adapter</artifactId>
	<version>{project_versionMvn}</version>
</dependency>
Client Configuration

The application needs to be configured as a public OpenID Connect client with Standard Flow Enabled and http://localhost:* as an allowed Valid Redirect URI.

Usage

The KeycloakInstalled adapter reads it’s configuration from META-INF/keycloak.json on the classpath. Custom configurations can be supplied with an InputStream or a KeycloakDeployment through the KeycloakInstalled constructor.

In the example below, the client configuration for desktop-app uses the following keycloak.json:

{
  "realm": "desktop-app-auth",
  "auth-server-url": "http://localhost:8081/auth",
  "ssl-required": "external",
  "resource": "desktop-app",
  "public-client": true,
  "use-resource-role-mappings": true
}

the following sketch demonstrates working with the KeycloakInstalled adapter:

// reads the configuration from classpath: META-INF/keycloak.json
KeycloakInstalled keycloak = new KeycloakInstalled();

// opens desktop browser
keycloak.loginDesktop();

AccessToken token = keycloak.getToken();
// use token to send backend request

// ensure token is valid for at least 30 seconds
long minValidity = 30L;
String tokenString = keycloak.getTokenString(minValidity, TimeUnit.SECONDS);


 // when you want to logout the user.
keycloak.logout();
Tip
The KeycloakInstalled class supports customization of the http responses returned by login / logout requests via the loginResponseWriter and logoutResponseWriter attributes.
Example

The following provides an example for the configuration mentioned above.

import java.util.Locale;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.keycloak.adapters.installed.KeycloakInstalled;
import org.keycloak.representations.AccessToken;

public class DesktopApp {

	public static void main(String[] args) throws Exception {

		KeycloakInstalled keycloak = new KeycloakInstalled();
		keycloak.setLocale(Locale.ENGLISH);
		keycloak.loginDesktop();

		AccessToken token = keycloak.getToken();
		Executors.newSingleThreadExecutor().submit(() -> {

			System.out.println("Logged in...");
			System.out.println("Token: " + token.getSubject());
			System.out.println("Username: " + token.getPreferredUsername());
			try {
				System.out.println("AccessToken: " + keycloak.getTokenString());
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			int timeoutSeconds = 20;
			System.out.printf("Logging out in...%d Seconds%n", timeoutSeconds);
			try {
				TimeUnit.SECONDS.sleep(timeoutSeconds);
			} catch (Exception e) {
				e.printStackTrace();
			}

			try {
				keycloak.logout();
			} catch (Exception e) {
				e.printStackTrace();
			}

			System.out.println("Exiting...");
			System.exit(0);
		});
	}
}