Skip to content

Commit ad686bd

Browse files
committed
Initial commit
0 parents  commit ad686bd

9 files changed

Lines changed: 635 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Build and Release JAR
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'src/**'
9+
workflow_dispatch:
10+
inputs:
11+
tag:
12+
description: "Tag name to release (e.g., v1.0.0)"
13+
required: false
14+
create_release:
15+
description: "Create GitHub release"
16+
type: boolean
17+
default: false
18+
19+
permissions:
20+
contents: write
21+
22+
jobs:
23+
build:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Set up JDK 21
30+
uses: actions/setup-java@v4
31+
with:
32+
distribution: temurin
33+
java-version: "21"
34+
cache: maven
35+
36+
- name: Build with Maven
37+
run: mvn -B -DskipTests package
38+
39+
- name: Upload artifact
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: jar
43+
path: target/zgw-token-introspection.jar
44+
45+
release:
46+
needs: build
47+
runs-on: ubuntu-latest
48+
steps:
49+
- name: Checkout
50+
uses: actions/checkout@v4
51+
52+
- name: Determine tag
53+
id: vars
54+
shell: bash
55+
run: |
56+
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
57+
TAG="${GITHUB_REF#refs/tags/}"
58+
else
59+
TAG="${{ inputs.tag }}"
60+
fi
61+
echo "tag=$TAG" >> $GITHUB_OUTPUT
62+
echo "release_name=zgw-token-introspection $TAG" >> $GITHUB_OUTPUT
63+
64+
- name: Download artifact
65+
uses: actions/download-artifact@v4
66+
with:
67+
name: jar
68+
path: dist
69+
70+
- name: Create GitHub Release
71+
if: ${{ steps.vars.outputs.tag != '' }}
72+
env:
73+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
74+
run: |
75+
tag="${{ steps.vars.outputs.tag }}"
76+
title="${{ steps.vars.outputs.release_name }}"
77+
78+
if gh release view "$tag" >/dev/null 2>&1; then
79+
gh release upload "$tag" "dist/zgw-token-introspection.jar" --clobber
80+
else
81+
gh release create "$tag" "dist/zgw-token-introspection.jar" \
82+
--title "$title" \
83+
--notes "Automated release $tag" \
84+
--target "$GITHUB_SHA"
85+
fi
86+
87+

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
### Java ###
2+
# Compiled class file
3+
*.class
4+
5+
# Log file
6+
*.log
7+
8+
# BlueJ files
9+
*.ctxt
10+
11+
# Mobile Tools for Java (J2ME)
12+
.mtj.tmp/
13+
14+
# Package Files #
15+
*.jar
16+
*.war
17+
*.nar
18+
*.ear
19+
*.zip
20+
*.tar.gz
21+
*.rar
22+
### Maven ###
23+
target/
24+
pom.xml.tag
25+
pom.xml.releaseBackup
26+
pom.xml.versionsBackup
27+
pom.xml.next
28+
release.properties
29+
dependency-reduced-pom.xml
30+
buildNumber.properties
31+
.mvn/timing.properties
32+
.mvn/wrapper/maven-wrapper.jar

LICENSE

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2025, Visma Roxit B.V. (https://roxit.nl)
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
1. Redistributions of source code must retain the above copyright notice, this
9+
list of conditions and the following disclaimer.
10+
11+
2. Redistributions in binary form must reproduce the above copyright notice,
12+
this list of conditions and the following disclaimer in the documentation
13+
and/or other materials provided with the distribution.
14+
15+
3. Neither the name of the copyright holder nor the names of its
16+
contributors may be used to endorse or promote products derived from
17+
this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# ZGW JWT tokens Introspection Provider for Keycloak
2+
3+
This provider adds ZGW token verification and parsing to Keycloak. It exposes a realm endpoint that validates HS256-signed ZGW JWTs by looking up the token's `client_id` in Keycloak and verifying the signature with that client's secret, returning an OAuth2 token introspection response.
4+
5+
## Overview
6+
7+
Introspection endpoint:
8+
- Requires a `client_id` claim
9+
- Resolves the Keycloak client by `client_id` and verifies the HS256 signature using that client's secret stored in Keycloak (no external secret store)
10+
- Includes claims from any OIDC Hardcoded Claim protocol mappers configured in keycloak, on the client
11+
- Enforces token activity (validates `exp`/`nbf` when present)
12+
- Tokens without `exp` claim will be treated as active, but warning will be logged in keycloak. It's recommended to use short lived tokens.
13+
14+
## Build
15+
16+
1. Ensure you have Java 21 installed (download from https://learn.microsoft.com/en-us/java/openjdk/download)
17+
2. Download Maven `Binary zip archive` from https://maven.apache.org/download.cgi
18+
3. Add the bin directory of the created directory apache-maven-* to the PATH environment variable
19+
4. Run the following command to build the JAR:
20+
```bash
21+
mvn clean package
22+
```
23+
5. The compiled JAR will be in the `target/` directory: `zgw-token-introspection.jar`
24+
25+
## Deployment
26+
27+
1. Copy the JAR file to your Keycloak deployment:
28+
- For host install: `{KEYCLOAK_HOME}/providers/`
29+
- For Docker: mount to `/opt/keycloak/providers/`
30+
31+
2. Restart Keycloak. In dev:
32+
- `{KEYCLOAK_HOME}/bin/kc.sh start-dev`
33+
In prod:
34+
- `{KEYCLOAK_HOME}/bin/kc.sh build && {KEYCLOAK_HOME}/bin/kc.sh start`
35+
36+
## Endpoint
37+
38+
- Path: `/realms/{realm}/zgw-token-introspection/introspect`
39+
- Method: `POST`
40+
- Content-Type: `application/x-www-form-urlencoded`
41+
- Parameter: `token=<JWT>`
42+
43+
## Token requirements
44+
45+
The provider expects JWT tokens generated with the following characteristics:
46+
47+
- **Algorithm**: HS256
48+
- **Secret**: Client secret from Keycloak
49+
- **Required claim**: `client_id` (must match a client in the realm)
50+
- **Temporal**: Token must be active; `exp` is recommended
51+
52+
53+
## Usage
54+
55+
Submit the token to the introspection endpoint:
56+
```bash
57+
curl -X POST \
58+
"http://localhost:8080/realms/{realm}/zgw-token-introspection/introspect" \
59+
-H "Content-Type: application/x-www-form-urlencoded" \
60+
--data-urlencode "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
61+
```
62+
Returns an OAuth2 introspection-style payload with selected standard and custom claims.
63+
On success you will receive `{ "active": true, ... }` and claim from token; otherwise `{ "active": false }`.
64+
65+
## Troubleshooting
66+
67+
- Check Keycloak logs for detailed error messages
68+
- Ensure the client exists in Keycloak and has a secret configured
69+
- Verify the token is properly formatted and not expired
70+
- Confirm the signing algorithm is HS256
71+
- Make sure the client secret matches what was used to sign the token

docker-compose.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
services:
2+
keycloak:
3+
image: quay.io/keycloak/keycloak:24.0.5
4+
command: start-dev --http-port=8080
5+
environment:
6+
- KEYCLOAK_ADMIN=admin
7+
- KEYCLOAK_ADMIN_PASSWORD=admin
8+
ports:
9+
- "8080:8080"
10+
volumes:
11+
- ./target/zgw-token-introspection.jar:/opt/keycloak/providers/zgw-token-introspection.jar:ro
12+
restart: unless-stopped

pom.xml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
5+
http://maven.apache.org/xsd/maven-4.0.0.xsd">
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<groupId>com.example</groupId>
9+
<artifactId>zgw-token-introspection</artifactId>
10+
<version>1.0.0</version>
11+
<packaging>jar</packaging>
12+
13+
<properties>
14+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15+
<maven.compiler.source>17</maven.compiler.source>
16+
<maven.compiler.target>17</maven.compiler.target>
17+
<keycloak.version>24.0.5</keycloak.version>
18+
</properties>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>org.keycloak</groupId>
23+
<artifactId>keycloak-server-spi</artifactId>
24+
<version>${keycloak.version}</version>
25+
<scope>provided</scope>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.keycloak</groupId>
29+
<artifactId>keycloak-server-spi-private</artifactId>
30+
<version>${keycloak.version}</version>
31+
<scope>provided</scope>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.keycloak</groupId>
35+
<artifactId>keycloak-services</artifactId>
36+
<version>${keycloak.version}</version>
37+
<scope>provided</scope>
38+
</dependency>
39+
<dependency>
40+
<groupId>jakarta.ws.rs</groupId>
41+
<artifactId>jakarta.ws.rs-api</artifactId>
42+
<version>3.1.0</version>
43+
<scope>provided</scope>
44+
</dependency>
45+
</dependencies>
46+
47+
<build>
48+
<finalName>zgw-token-introspection</finalName>
49+
<plugins>
50+
<plugin>
51+
<groupId>org.apache.maven.plugins</groupId>
52+
<artifactId>maven-compiler-plugin</artifactId>
53+
<version>3.11.0</version>
54+
<configuration>
55+
<source>17</source>
56+
<target>17</target>
57+
</configuration>
58+
</plugin>
59+
</plugins>
60+
</build>
61+
</project>

0 commit comments

Comments
 (0)