Skip to content

Commit 2f9b545

Browse files
committed
Add X.509 + WebAuthn Sample
1 parent 5e60e27 commit 2f9b545

31 files changed

+1120
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
= X.509 + Form Login MFA Sample
2+
3+
This sample demonstrates configuring Spring Security to require both an X.509 Certificate and a Username/Password Login in order to enter the site with full permissions.
4+
5+
== Preparing to Use X.509
6+
7+
This sample is intended to be used in a browser.
8+
As such, you should:
9+
10+
1. Configure your browser to trust the `ca.crt` that accompanies this project
11+
2. Configure your browser with the `josh-keystore.p12` client certificate
12+
13+
Both `api-keystore.p12` and `josh-keystore.p12` use keys signed by `ca.crt`.
14+
This means that after the above steps are performed, you can also use this application without getting a security warning in your browser.
15+
16+
== Using the Sample
17+
18+
To run, please use:
19+
20+
.Java
21+
[source,java,role="primary"]
22+
----
23+
./gradlew :bootRun
24+
----
25+
26+
This will start an application on 8443, meaning you will need to reach it using HTTPS.
27+
28+
You can register a passkey at https://api.127.0.0.1.nip.io:8443/webauthn/register.
29+
30+
With the client certificate (`josh-keystore.p12`) correctly installed in the browser, it will ask you which client certificate you want to you.
31+
Select `josh`.
32+
33+
You will then be redirected to the PassKeys registration page where you can install a passkey.
34+
35+
After that, navigate to https://api.127.0.0.1.nip.io:8443 and you will be redirected to page where you can provide a passkey.
36+
37+
== Exploring the Sample
38+
39+
The key configuration is found in the `HttpSecurity` DSL:
40+
41+
.Java
42+
[source,java,role="primary"]
43+
----
44+
http
45+
.x509(Customizer.withDefaults())
46+
.webAuthn((webauthn) -> webauthn
47+
// ...
48+
.factor((f) -> f.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/webauthn")))
49+
);
50+
----
51+
52+
This reads, "This app requires both X.509 and WebAuthn to fully authorize; redirect to /webauthn to get the WebAuthn authority".
53+
54+
You can instead try another arrangement like the following:
55+
56+
.Java
57+
[source,java,role="primary"]
58+
----
59+
http
60+
.x509(Customizer.withDefaults())
61+
.oneTimeTokenLogin(Customizer.withDefaults())
62+
----
63+
64+
Once `oneTimeTokenLogin` is correctly configured and once a client certificate is accepted, the application will generate a token and send it to the configured destination to continue with the login process.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
plugins {
2+
alias(libs.plugins.io.spring.dependency.management)
3+
alias(libs.plugins.org.springframework.boot)
4+
id "nebula.integtest" version "8.2.0"
5+
id 'java'
6+
}
7+
8+
repositories {
9+
mavenLocal()
10+
mavenCentral()
11+
maven { url "https://repo.spring.io/milestone" }
12+
maven { url "https://repo.spring.io/snapshot" }
13+
}
14+
15+
16+
dependencies {
17+
implementation 'org.springframework.boot:spring-boot-starter-security'
18+
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
19+
implementation 'org.springframework.boot:spring-boot-starter-web'
20+
implementation 'org.springframework.security:spring-security-webauthn'
21+
22+
23+
implementation "com.webauthn4j:webauthn4j-core:0.29.4.RELEASE"
24+
25+
testImplementation 'org.springframework.boot:spring-boot-starter-test'
26+
testImplementation 'org.springframework.security:spring-security-test'
27+
}
28+
29+
tasks.withType(Test).configureEach {
30+
useJUnitPlatform()
31+
32+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
KEYSTORE="${1:-}"
5+
if [[ -z "$KEYSTORE" ]]; then
6+
echo "Usage: $0 <keystore.p12>" >&2
7+
exit 1
8+
fi
9+
10+
PASSWORD="password"
11+
12+
# Set up temp workspace
13+
WORKDIR=$(mktemp -d)
14+
trap "rm -rf $WORKDIR" EXIT
15+
16+
# Read input tar archive from stdin
17+
tar -C "$WORKDIR" -xf -
18+
19+
ALIAS=$(cat "$WORKDIR/alias")
20+
CERT="$WORKDIR/cert.pem"
21+
KEY="$WORKDIR/key.pem"
22+
CHAIN="$WORKDIR/chain.pem"
23+
24+
# Convert to PKCS#12 bundle
25+
PKCS12="$WORKDIR/temp.p12"
26+
openssl pkcs12 -export \
27+
-inkey "$KEY" \
28+
-in "$CERT" \
29+
-certfile "$CHAIN" \
30+
-name "$ALIAS" \
31+
-out "$PKCS12" \
32+
-passout pass:$PASSWORD
33+
34+
# If alias exists, delete it
35+
if [[ -f "$KEYSTORE" ]]; then
36+
keytool -delete -alias "$ALIAS" -keystore "$KEYSTORE" \
37+
-storepass "$PASSWORD" -storetype PKCS12 || true
38+
fi
39+
40+
# Import new entry
41+
keytool -importkeystore \
42+
-destkeystore "$KEYSTORE" -deststoretype PKCS12 -deststorepass "$PASSWORD" \
43+
-srckeystore "$PKCS12" -srcstoretype PKCS12 -srcstorepass "$PASSWORD" \
44+
-alias "$ALIAS"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
TRUSTSTORE="${1:-}"
5+
if [[ -z "$TRUSTSTORE" ]]; then
6+
echo "Usage: $0 <truststore.p12>" >&2
7+
exit 1
8+
fi
9+
10+
PASSWORD="password"
11+
12+
# Temp workspace
13+
WORKDIR=$(mktemp -d)
14+
trap "rm -rf $WORKDIR" EXIT
15+
16+
# Extract from tar input
17+
tar -C "$WORKDIR" -xf -
18+
19+
ALIAS=$(cat "$WORKDIR/alias")
20+
CA_CERT="$WORKDIR/ca.pem"
21+
DER_CERT="$WORKDIR/ca.der"
22+
23+
# Convert to DER format for keytool
24+
openssl x509 -in "$CA_CERT" -outform DER -out "$DER_CERT"
25+
26+
# If alias exists, delete
27+
if [[ -f "$TRUSTSTORE" ]]; then
28+
keytool -delete -alias "$ALIAS" -keystore "$TRUSTSTORE" \
29+
-storepass "$PASSWORD" -storetype PKCS12 || true
30+
fi
31+
32+
# Import into truststore
33+
keytool -importcert -noprompt \
34+
-alias "$ALIAS" \
35+
-file "$DER_CERT" \
36+
-keystore "$TRUSTSTORE" \
37+
-storetype PKCS12 \
38+
-storepass "$PASSWORD"
Binary file not shown.
Binary file not shown.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFDzCCAvegAwIBAgIUHzvf6/d0vmFFEYh5GHA/I8t787YwDQYJKoZIhvcNAQEL
3+
BQAwFzEVMBMGA1UEAwwMTG9jYWwgRGV2IENBMB4XDTI1MDcxMjE4MDM0NFoXDTM1
4+
MDcxMDE4MDM0NFowFzEVMBMGA1UEAwwMTG9jYWwgRGV2IENBMIICIjANBgkqhkiG
5+
9w0BAQEFAAOCAg8AMIICCgKCAgEAxezzB+gvNL5WeuzVvvlv21OvbbJtdP6k08Yz
6+
egIddIckQ2Yguuc5gHoUnNve5BVEQZmFNQihqLlzV0lFb+4QjqiH5BWKql+U7TRG
7+
GCLecF4VQHup5voQv6EUnVOJglc13NAjWqbP09M5aIxPNzfRv3eeZq2jLXu3aQ6Q
8+
+mFq/cIGSlHF6KjqTTXoK1sRngZUeZi1/fhg1/9usH4L5nQ41y3D+51c5Zl+UW3w
9+
7bR70XkH3aX6X4xOOfZBiyp3wxrG7uQBddQAk2zu/hMTYWNAYswT15jIEk4JLH7o
10+
HXrVaoM5vnL0D5SaIc66JnAhwyC3E1jc7OpioFCO0Xi5XZAt3s+E0AwOpAvSomIH
11+
FdryfEHcbhqSUgUVof/c9PmLwtS2fVPm3LqqLjmwagO9DPDCGroNwoOKitfoH3qQ
12+
EtETygsKThMYLc23tLbyYsjPs5H8ro+s41aqcsCiQIhELRXwZVyCWj53o8tE4Ihh
13+
hdTzbzNmrBdv0H9vb3VS9SdfHYFJ409qIu+kkmsAQ8vd6e92UJXbKd/Vchmr2ogs
14+
7oGLIOID9KZMqe4dKKzU+ietLz0IVTWRaPz3ea7NGRIo3/yxGDMRF/Z0UwH53H+E
15+
crn8w/aMWeIqMq5h6sC24D9RkcVN+E5QzxvG2VS4A7CzskIrduAWl5TrBERChbiX
16+
wQusjYcCAwEAAaNTMFEwHQYDVR0OBBYEFLq0yPS9u7Flbh061DkXX79lvNltMB8G
17+
A1UdIwQYMBaAFLq0yPS9u7Flbh061DkXX79lvNltMA8GA1UdEwEB/wQFMAMBAf8w
18+
DQYJKoZIhvcNAQELBQADggIBABMh+cLO2ge0FKNkJriiBXs3ah6w/GGWVgFK6BmU
19+
297fM9FSV/1bQcTUe1gpudabGRsq7TthC/aK3B+79tsfudcrPKJZrK7cFkxY06xT
20+
3el45RQxZNYvaHRsK7nw6gExCLlYFKAatHcdvbk6xe+XZAAr4rLYg1H1n7Ddg3p/
21+
K3l0a/6nAyMND7T1euzijR+40EZdiXzwsgFR3R2YIWiNmLu/z3tg0HfYIgrQaOou
22+
Xl06qXZ1iJW1EFuF/KoxNBxyJQpHywYTvb6UuGV2UOvI3V7SzzgyBvBxOUf4djNM
23+
kEK1+U5lnMqSDgHWkEzNUFMlsIwIiEWSMo+deF+/9FiAQxUX85kH9O8/7t9rqzxK
24+
mgVccTnzvEcYyRYgL1SsCXo1oJfZ8M4OnZ/dDY0y86kngrgkIxFpPPAh0VVpyVGn
25+
A9CyYU7vQxTrgwHtqITMN7kNBWEAZgA82kURbZm/DDoKX5IJdcMdwvFaVk2irSyQ
26+
x0Px+k5eNhSGdKctXESAAUeUADpaYhRZeRI7jJDPCJGvs5or4hiA/3O8ZI5Lum12
27+
fKNSau4m3jbTWrD4x7zlbVES9hRB87f/uST0/yh4NfdjrspzNINWXArxPDsYs7Sf
28+
HDVj3RPNKVGzJEH4+J+Ohti3JTEl8hLBO3ZoMJ2uPQ44wcJAcKVL7O3QraKOfzXu
29+
k1d0
30+
-----END CERTIFICATE-----
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
51+
UZaWgz2MB+hNvTCg+8toreA0o4/SiA==
52+
-----END PRIVATE KEY-----
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFDzCCAvegAwIBAgIUHzvf6/d0vmFFEYh5GHA/I8t787YwDQYJKoZIhvcNAQEL
3+
BQAwFzEVMBMGA1UEAwwMTG9jYWwgRGV2IENBMB4XDTI1MDcxMjE4MDM0NFoXDTM1
4+
MDcxMDE4MDM0NFowFzEVMBMGA1UEAwwMTG9jYWwgRGV2IENBMIICIjANBgkqhkiG
5+
9w0BAQEFAAOCAg8AMIICCgKCAgEAxezzB+gvNL5WeuzVvvlv21OvbbJtdP6k08Yz
6+
egIddIckQ2Yguuc5gHoUnNve5BVEQZmFNQihqLlzV0lFb+4QjqiH5BWKql+U7TRG
7+
GCLecF4VQHup5voQv6EUnVOJglc13NAjWqbP09M5aIxPNzfRv3eeZq2jLXu3aQ6Q
8+
+mFq/cIGSlHF6KjqTTXoK1sRngZUeZi1/fhg1/9usH4L5nQ41y3D+51c5Zl+UW3w
9+
7bR70XkH3aX6X4xOOfZBiyp3wxrG7uQBddQAk2zu/hMTYWNAYswT15jIEk4JLH7o
10+
HXrVaoM5vnL0D5SaIc66JnAhwyC3E1jc7OpioFCO0Xi5XZAt3s+E0AwOpAvSomIH
11+
FdryfEHcbhqSUgUVof/c9PmLwtS2fVPm3LqqLjmwagO9DPDCGroNwoOKitfoH3qQ
12+
EtETygsKThMYLc23tLbyYsjPs5H8ro+s41aqcsCiQIhELRXwZVyCWj53o8tE4Ihh
13+
hdTzbzNmrBdv0H9vb3VS9SdfHYFJ409qIu+kkmsAQ8vd6e92UJXbKd/Vchmr2ogs
14+
7oGLIOID9KZMqe4dKKzU+ietLz0IVTWRaPz3ea7NGRIo3/yxGDMRF/Z0UwH53H+E
15+
crn8w/aMWeIqMq5h6sC24D9RkcVN+E5QzxvG2VS4A7CzskIrduAWl5TrBERChbiX
16+
wQusjYcCAwEAAaNTMFEwHQYDVR0OBBYEFLq0yPS9u7Flbh061DkXX79lvNltMB8G
17+
A1UdIwQYMBaAFLq0yPS9u7Flbh061DkXX79lvNltMA8GA1UdEwEB/wQFMAMBAf8w
18+
DQYJKoZIhvcNAQELBQADggIBABMh+cLO2ge0FKNkJriiBXs3ah6w/GGWVgFK6BmU
19+
297fM9FSV/1bQcTUe1gpudabGRsq7TthC/aK3B+79tsfudcrPKJZrK7cFkxY06xT
20+
3el45RQxZNYvaHRsK7nw6gExCLlYFKAatHcdvbk6xe+XZAAr4rLYg1H1n7Ddg3p/
21+
K3l0a/6nAyMND7T1euzijR+40EZdiXzwsgFR3R2YIWiNmLu/z3tg0HfYIgrQaOou
22+
Xl06qXZ1iJW1EFuF/KoxNBxyJQpHywYTvb6UuGV2UOvI3V7SzzgyBvBxOUf4djNM
23+
kEK1+U5lnMqSDgHWkEzNUFMlsIwIiEWSMo+deF+/9FiAQxUX85kH9O8/7t9rqzxK
24+
mgVccTnzvEcYyRYgL1SsCXo1oJfZ8M4OnZ/dDY0y86kngrgkIxFpPPAh0VVpyVGn
25+
A9CyYU7vQxTrgwHtqITMN7kNBWEAZgA82kURbZm/DDoKX5IJdcMdwvFaVk2irSyQ
26+
x0Px+k5eNhSGdKctXESAAUeUADpaYhRZeRI7jJDPCJGvs5or4hiA/3O8ZI5Lum12
27+
fKNSau4m3jbTWrD4x7zlbVES9hRB87f/uST0/yh4NfdjrspzNINWXArxPDsYs7Sf
28+
HDVj3RPNKVGzJEH4+J+Ohti3JTEl8hLBO3ZoMJ2uPQ44wcJAcKVL7O3QraKOfzXu
29+
k1d0
30+
-----END CERTIFICATE-----
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3232A5577A1F2B5B0AF818CC1E125BA8B9AA228F

0 commit comments

Comments
 (0)