import org.apache.sling.extensions.oidc_rp.*;
@Component(service = { Servlet.class })
@SlingServletPaths(value = "/bin/myservlet")
public class MySlingServlet {
@Reference private OidcTokenStore tokenStore;
@Reference private OidcClient oidcClient;
public void accessRemoteResource(SlingHttpServletRequest request, SlingHttpServletResponse response) {
OidcConnection connection = getConnection();
OidcToken tokenResponse = tokenStore.getAccessToken(connection, request.getResourceResolver());
switch ( tokenResponse.getState() ) {
case VALID:
doStuffWithToken(tokenResponse.getValue());
break;
case MISSING:
response.sendRedirect(oidcClient.getOidcEntryPointUri(connection, request, "/bin/myservlet").toString());
break;
case EXPIRED:
OidcToken refreshToken = tokenStore.getRefreshToken(connection, request.getResourceResolver());
if ( refreshToken.getState() != OidcTokenState.VALID )
response.sendRedirect(oidcClient.getOidcEntryPointUri(connection, request, "/bin/myservlet").toString());
OidcTokens oidcTokens = oidcClient.refreshTokens(connection, refreshToken.getValue());
tokenStore.persistTokens(connection, request.getResourceResolver(), oidcTokens);
doStuffWithToken(tokenResponse.getValue());
break;
}
}
}
Client registration
Client registration is specific to each provider. When registering, note the following:
- the redirect URL must be set to $HOST/system/sling/oidc/callback registered. For development this is typically http://localhost:8080/system/sling/oidc/callback
- write down the client id, client secret obtained from the OIDC provider
- you may need to provide in advance the set of scopes accessible to your client
Validated providers:
Deployment
A set of dependencies required by this bundle, on top of the Sling Starter ones, is available at src/main/features/main.json
. In addition, the following OSGi configuration must be added
"org.apache.sling.servlets.oidc_rp.impl.OidcConnectionImpl~provider": {
"name": "provider",
"baseUrl": "https://.example.com",
"clientId": "$[secret:provider/clientId]",
"clientSecret": "$[secret:provider/clientSecret]",
"scopes": ["openid"]
}
At this point, the OIDC process can be kicked of by navigating to http://localhost:8080/system/sling/oidc/entry-point?c=provider
Token storage
The tokens are stored under the user's home, under the oidc-tokens/$PROVIDER_NAME
node.
Whiteboard graduation TODO
- allow use of refresh tokens
- extract the token exchange code from the OidcCallbackServlet and move it to the OauthClientImpl
- provide a sample content package
- review security best practices
- investigate whether the OIDC entry point servlet is really needed
Local development setup
tl;dr
- run the keycloak container using the instructions for ‘use existing test files’
- build the bundle once with
mvn clean install
- run Sling with
mvn feature-launcher:start feature-launcher:stop -Dfeature-launcher.waitForInput
- create OSGi config with
export CLIENT_SECRET=$(cat src/test/resources/keycloak-import/sling.json | jq --raw-output '.clients[] | select (.clientId == "oidc-test") | .secret')
$ curl -u admin:admin -X POST -d "apply=true" -d "propertylist=name,baseUrl,clientId,clientSecret,scopes" \
-d "name=keycloak-dev" \
-d "baseUrl=http://localhost:8081/realms/sling" \
-d "clientId=oidc-test"\
-d "clientSecret=$CLIENT_SECRET" \
-d "scopes=openid" \
-d "factoryPid=org.apache.sling.extensions.oidc_rp.impl.OidcConnectionImpl" \
http://localhost:8080/system/console/configMgr/org.apache.sling.extensions.oidc_rp.impl.OidcConnectionImpl~keycloak-dev
Now you can
Keycloak
Use existing test files
Note that this imports the test setup with a single user with a redirect_uri set to http://localhost*, which can be a security issue.
$ docker run --rm --volume $(pwd)/src/test/resources/keycloak-import:/opt/keycloak/data/import -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:20.0.3 start-dev --import-realm
Manual setup
- Launch Keycloak locally
$ docker run --rm --volume $(pwd)/keycloak-data:/opt/keycloak/data -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:20.0.3 start-dev
- Create test realm
- access http://localhost:8081/
- go to ‘Administration Console’
- login with admin:admin
- open dropdown from the top left and press ‘Create realm’
- Select the name ‘sling’ and create it
- Create client
- in the left navigation area, press ‘clients’
- press ‘Create client’
- Fill in ‘Client ID’ as ‘oidc-test’ and press ‘Next’
- Enable ‘Client authentication’ and press ‘Save’
- Configure clients
- Add users
- in the left navigation area, press ‘users’
- press ‘create new user’
- fill in username: test and press ‘create’
- go to the ‘details’ tab, clear any required user actions and press ‘save’
- go to the ‘credentials’ tab and press ‘set password’
- in the dialog, use ‘test’ for the password and password confirmation fields and then press ‘save’
- confirm by pressing ‘save password’ in the new dialog
Exporting the test realm
$ docker run --rm --volume (pwd)/keycloak-data:/opt/keycloak/data -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:20.0.3 export --realm sling --users realm_file --file /opt/keycloak/data/export/sling.json