Skip to content

Commit 2c87548

Browse files
authored
BAEL-9015 Integrating Passkeys into Spring Security (#18294)
* [BAEL-8844] Article code * [BAEL-8844] Refactoring & Simplification * [BAEL-8844] Code cleanup * [BAEL-9015] Initial Code * [BAEL-9015] Article code * Persistence * [BAEL-9015] Peristence code cleanup * [BAEL-9015] LiveTest
1 parent 92dcc79 commit 2c87548

File tree

17 files changed

+859
-0
lines changed

17 files changed

+859
-0
lines changed

spring-security-modules/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<module>spring-security-authorization</module>
6262
<module>spring-security-dynamic-registration</module>
6363
<module>spring-security-ott</module>
64+
<module>spring-security-passkey</module>
6465
</modules>
6566

6667
<build>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xmlns="http://maven.apache.org/POM/4.0.0"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<artifactId>spring-security-passkey</artifactId>
6+
<description>Spring Security with PassKey/WebAuthN authentication</description>
7+
8+
<parent>
9+
<groupId>com.baeldung</groupId>
10+
<artifactId>parent-boot-3</artifactId>
11+
<version>0.0.1-SNAPSHOT</version>
12+
<relativePath>../../parent-boot-3</relativePath>
13+
</parent>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>org.springframework.boot</groupId>
18+
<artifactId>spring-boot-starter-web</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-security</artifactId>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>com.webauthn4j</groupId>
27+
<artifactId>webauthn4j-core</artifactId>
28+
<version>${webauthn4j.version}</version>
29+
</dependency>
30+
31+
<dependency>
32+
<groupId>org.projectlombok</groupId>
33+
<artifactId>lombok</artifactId>
34+
<version>${lombok.version}</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.springframework.boot</groupId>
38+
<artifactId>spring-boot-devtools</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.springframework.security</groupId>
42+
<artifactId>spring-security-test</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.springframework.boot</groupId>
46+
<artifactId>spring-boot-configuration-processor</artifactId>
47+
<optional>true</optional>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.springframework.boot</groupId>
51+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.thymeleaf.extras</groupId>
55+
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
56+
</dependency>
57+
58+
<dependency>
59+
<groupId>com.h2database</groupId>
60+
<artifactId>h2</artifactId>
61+
<scope>runtime</scope>
62+
</dependency>
63+
<dependency>
64+
<groupId>org.springframework.boot</groupId>
65+
<artifactId>spring-boot-starter-data-jdbc</artifactId>
66+
</dependency>
67+
68+
<dependency>
69+
<groupId>io.github.bonigarcia</groupId>
70+
<artifactId>webdrivermanager</artifactId>
71+
<version>5.9.3</version>
72+
<scope>test</scope>
73+
</dependency>
74+
75+
<dependency>
76+
<groupId>org.seleniumhq.selenium</groupId>
77+
<artifactId>selenium-java</artifactId>
78+
<version>${selenium.version}</version>
79+
<scope>test</scope>
80+
</dependency>
81+
82+
</dependencies>
83+
84+
<properties>
85+
<spring-boot.version>3.4.1</spring-boot.version>
86+
<logback.version>1.5.7</logback.version>
87+
<webauthn4j.version>0.28.4.RELEASE</webauthn4j.version>
88+
<selenium.version>4.29.0</selenium.version>
89+
</properties>
90+
91+
92+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.baeldung.tutorials.passkey;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class PassKeyApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(PassKeyApplication.class, args);
11+
}
12+
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.baeldung.tutorials.passkey.config;
2+
3+
import com.baeldung.tutorials.passkey.repository.DbPublicKeyCredentialUserEntityRepository;
4+
import com.baeldung.tutorials.passkey.repository.DbUserCredentialRepository;
5+
import com.baeldung.tutorials.passkey.repository.PasskeyCredentialRepository;
6+
import com.baeldung.tutorials.passkey.repository.PasskeyUserRepository;
7+
import lombok.Data;
8+
import org.springframework.boot.context.properties.ConfigurationProperties;
9+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.context.annotation.Configuration;
12+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13+
import org.springframework.security.web.SecurityFilterChain;
14+
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
15+
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
16+
17+
import java.util.Set;
18+
19+
import static org.springframework.security.config.Customizer.withDefaults;
20+
21+
@Configuration
22+
@EnableConfigurationProperties(SecurityConfiguration.WebAuthNProperties.class)
23+
public class SecurityConfiguration {
24+
25+
@Bean
26+
SecurityFilterChain webauthnFilterChain(HttpSecurity http, WebAuthNProperties webAuthNProperties) throws Exception {
27+
return http.authorizeHttpRequests( ht -> ht.anyRequest().authenticated())
28+
.formLogin(withDefaults())
29+
.webAuthn( webauth ->
30+
webauth.allowedOrigins(webAuthNProperties.getAllowedOrigins())
31+
.rpId(webAuthNProperties.getRpId())
32+
.rpName(webAuthNProperties.getRpName())
33+
)
34+
.build();
35+
}
36+
37+
@Bean
38+
PublicKeyCredentialUserEntityRepository userEntityRepository(PasskeyUserRepository userRepository) {
39+
return new DbPublicKeyCredentialUserEntityRepository(userRepository);
40+
}
41+
42+
@Bean
43+
UserCredentialRepository userCredentialRepository(PasskeyUserRepository userRepository, PasskeyCredentialRepository credentialRepository) {
44+
return new DbUserCredentialRepository(credentialRepository,userRepository);
45+
}
46+
47+
@ConfigurationProperties(prefix = "spring.security.webauthn")
48+
@Data
49+
static class WebAuthNProperties {
50+
private String rpId;
51+
private String rpName;
52+
private Set<String> allowedOrigins;
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package com.baeldung.tutorials.passkey.domain;
2+
3+
import org.springframework.data.annotation.Id;
4+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
5+
import org.springframework.data.relational.core.mapping.Column;
6+
import org.springframework.data.relational.core.mapping.Table;
7+
8+
import java.time.Instant;
9+
10+
@Table(name = "PASSKEY_CREDENTIALS")
11+
public class PasskeyCredential {
12+
@Id
13+
@Column(value = "ID")
14+
public Long id;
15+
16+
@Column(value = "USER_ID")
17+
public Long userId;
18+
19+
@Column(value = "LABEL")
20+
public String label;
21+
22+
@Column(value = "CREDENTIAL_TYPE")
23+
public String credentialType;
24+
25+
@Column(value = "CREDENTIAL_ID")
26+
public String credentialId;
27+
28+
@Column(value = "PUBLIC_KEY_COSE")
29+
public String publicKeyCose;
30+
31+
@Column(value = "SIGNATURE_COUNT")
32+
public Long signatureCount;
33+
34+
@Column(value = "UV_INITIALIZED")
35+
public Boolean uvInitialized;
36+
37+
@Column(value = "TRANSPORTS")
38+
public String transports;
39+
40+
@Column(value = "BACKUP_ELIGIBLE")
41+
public Boolean backupEligible;
42+
43+
@Column(value = "BACKUP_STATE")
44+
public Boolean backupState;
45+
46+
@Column(value = "ATTESTATION_OBJECT")
47+
public String attestationObject;
48+
49+
@Column(value = "LAST_USED")
50+
public Instant lastUsed;
51+
52+
@Column(value = "CREATED")
53+
public Instant created;
54+
55+
public Long getId() {
56+
return id;
57+
}
58+
59+
public void setId(Long id) {
60+
this.id = id;
61+
}
62+
63+
public AggregateReference<PasskeyUser, Long> getUser() {
64+
return AggregateReference.to(userId);
65+
}
66+
67+
public void setUser(AggregateReference<PasskeyUser, Long> userId) {
68+
this.userId = userId.getId();
69+
}
70+
71+
public String getLabel() {
72+
return label;
73+
}
74+
75+
public void setLabel(String label) {
76+
this.label = label;
77+
}
78+
79+
public String getCredentialType() {
80+
return credentialType;
81+
}
82+
83+
public void setCredentialType(String credentialType) {
84+
this.credentialType = credentialType;
85+
}
86+
87+
public String getCredentialId() {
88+
return credentialId;
89+
}
90+
91+
public void setCredentialId(String credentialId) {
92+
this.credentialId = credentialId;
93+
}
94+
95+
public String getPublicKeyCose() {
96+
return publicKeyCose;
97+
}
98+
99+
public void setPublicKeyCose(String publicKeyCose) {
100+
this.publicKeyCose = publicKeyCose;
101+
}
102+
103+
public Long getSignatureCount() {
104+
return signatureCount;
105+
}
106+
107+
public void setSignatureCount(Long signatureCount) {
108+
this.signatureCount = signatureCount;
109+
}
110+
111+
public Boolean getUvInitialized() {
112+
return uvInitialized;
113+
}
114+
115+
public void setUvInitialized(Boolean uvInitialized) {
116+
this.uvInitialized = uvInitialized;
117+
}
118+
119+
public String getTransports() {
120+
return transports;
121+
}
122+
123+
public void setTransports(String transports) {
124+
this.transports = transports;
125+
}
126+
127+
public Boolean getBackupEligible() {
128+
return backupEligible;
129+
}
130+
131+
public void setBackupEligible(Boolean backupEligible) {
132+
this.backupEligible = backupEligible;
133+
}
134+
135+
public Boolean getBackupState() {
136+
return backupState;
137+
}
138+
139+
public void setBackupState(Boolean backupState) {
140+
this.backupState = backupState;
141+
}
142+
143+
public String getAttestationObject() {
144+
return attestationObject;
145+
}
146+
147+
public void setAttestationObject(String attestationObject) {
148+
this.attestationObject = attestationObject;
149+
}
150+
151+
public Instant getLastUsed() {
152+
return lastUsed;
153+
}
154+
155+
public void setLastUsed(Instant lastUsed) {
156+
this.lastUsed = lastUsed;
157+
}
158+
159+
public Instant getCreated() {
160+
return created;
161+
}
162+
163+
public void setCreated(Instant created) {
164+
this.created = created;
165+
}
166+
167+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.baeldung.tutorials.passkey.domain;
2+
3+
import org.springframework.data.annotation.Id;
4+
import org.springframework.data.relational.core.mapping.Column;
5+
import org.springframework.data.relational.core.mapping.Table;
6+
7+
@Table(name = "PASSKEY_USERS")
8+
public class PasskeyUser {
9+
@Id
10+
@Column(value = "ID")
11+
public Long id;
12+
13+
@Column(value = "EXTERNAL_ID")
14+
public String externalId;
15+
16+
@Column(value = "NAME")
17+
public String name;
18+
19+
@Column(value = "DISPLAY_NAME")
20+
public String displayName;
21+
22+
public Long getId() {
23+
return id;
24+
}
25+
26+
public void setId(Long id) {
27+
this.id = id;
28+
}
29+
30+
public String getExternalId() {
31+
return externalId;
32+
}
33+
34+
public void setExternalId(String externalId) {
35+
this.externalId = externalId;
36+
}
37+
38+
public String getName() {
39+
return name;
40+
}
41+
42+
public void setName(String name) {
43+
this.name = name;
44+
}
45+
46+
public String getDisplayName() {
47+
return displayName;
48+
}
49+
50+
public void setDisplayName(String displayName) {
51+
this.displayName = displayName;
52+
}
53+
54+
}

0 commit comments

Comments
 (0)