1
0
mirror of synced 2026-07-11 05:40:02 +00:00

Compare commits

...

42 Commits

Author SHA1 Message Date
github-actions[bot] e5a379cc91 Release 6.5.7 2025-11-17 17:04:45 +00:00
Rob Winch 7ad28772bc Update to Spring Data 2024.1.12
Closes gh-18182
2025-11-17 10:31:45 -06:00
Rob Winch 2678925c21 Update to Reactor 2024.0.12
This aligns with Spring Framework's version.

Closes gh-18181
2025-11-17 10:30:45 -06:00
Rob Winch 87472c9ab4 Update to Spring Framework 6.2.13
Closes gh-18180
2025-11-17 10:29:49 -06:00
Rob Winch ea036702fc Merge branch '6.4.x' into 6.5.x 2025-11-17 10:27:16 -06:00
Rob Winch 09805317e6 Update Spring Data 2024.1.12
Closes gh-18179
2025-11-17 10:27:01 -06:00
Rob Winch 4ab933803f Update to Reactor 2024.0.12
This aligns with Spring Framework's version of Reactor

Closes gh-18178
2025-11-17 10:26:09 -06:00
Rob Winch d2b1cb572f Update to Spring Framework 6.2.13
Closes gh-18177
2025-11-17 10:25:21 -06:00
Rob Winch 70076188af Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5 2025-11-17 09:34:44 -06:00
Rob Winch 57c9b1365c Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final 2025-11-17 09:34:42 -06:00
Rob Winch 1c3d28f14d Bump io.spring.gradle:spring-security-release-plugin from 1.0.11 to 1.0.13 2025-11-17 09:34:40 -06:00
Rob Winch aaadb43ef8 Bump org-aspectj from 1.9.24 to 1.9.25 2025-11-17 09:34:38 -06:00
Rob Winch a6c1a02afa Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13 2025-11-17 09:34:35 -06:00
Rob Winch baebd04df7 Merge branch '6.4.x' into 6.5.x 2025-11-17 09:34:26 -06:00
Rob Winch d0166004aa Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5 2025-11-17 09:33:40 -06:00
Rob Winch 9f96fbcda0 Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final 2025-11-17 09:33:37 -06:00
Rob Winch ccffb48fd1 Bump org-aspectj from 1.9.24 to 1.9.25 2025-11-17 09:33:35 -06:00
Rob Winch d0fcdebe88 Bump io.spring.gradle:spring-security-release-plugin from 1.0.11 to 1.0.13 2025-11-17 09:33:33 -06:00
dependabot[bot] af47cc2abe Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.12 to 1.14.13.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.12...v1.14.13)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-version: 1.14.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 03:20:24 +00:00
dependabot[bot] f997e22d9d Bump io.micrometer:micrometer-observation from 1.14.12 to 1.14.13
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.12 to 1.14.13.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.12...v1.14.13)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-version: 1.14.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 03:14:59 +00:00
dependabot[bot] c85cb2d1ef Bump org-aspectj from 1.9.24 to 1.9.25
Bumps `org-aspectj` from 1.9.24 to 1.9.25.

Updates `org.aspectj:aspectjrt` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

Updates `org.aspectj:aspectjweaver` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

---
updated-dependencies:
- dependency-name: org.aspectj:aspectjrt
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.aspectj:aspectjweaver
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:19:48 +00:00
dependabot[bot] bf26dd9b33 Bump io.spring.gradle:spring-security-release-plugin
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.11 to 1.0.13.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.11...v1.0.13)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-version: 1.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:19:25 +00:00
dependabot[bot] ff908c4d7c Bump io.spring.gradle:spring-security-release-plugin
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.11 to 1.0.13.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.11...v1.0.13)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-version: 1.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:15:17 +00:00
dependabot[bot] 521f533fc4 Bump org-aspectj from 1.9.24 to 1.9.25
Bumps `org-aspectj` from 1.9.24 to 1.9.25.

Updates `org.aspectj:aspectjrt` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

Updates `org.aspectj:aspectjweaver` from 1.9.24 to 1.9.25
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

---
updated-dependencies:
- dependency-name: org.aspectj:aspectjrt
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.aspectj:aspectjweaver
  dependency-version: 1.9.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-05 03:14:41 +00:00
Josh Cummings f988272fff Merge branch '6.4.x' into 6.5.x 2025-11-04 14:04:29 -07:00
Josh Cummings 532d0bef14 Add Test to Confirm 72-byte BCrypt Password Limit
Closes gh-18133
2025-11-04 14:04:02 -07:00
Rob Winch c1e9e10bf0 Merge branch '6.4.x' into 6.5.x
Closes gh-18131
2025-11-04 11:28:40 -06:00
Daniel Garnier-Moiroux fed6df5167 Default WebAuthnConfigurer#rpName to rpId
In WebAuthn L3 spec, PublicKeyCredentialEntity.name is deprecated:

> This member is deprecated because many clients do not display it,
> but it remains a required dictionary member for backwards compatibility.
> Relying Parties MAY, as a safe default, set this equal to the RP ID.

Source: https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialentity

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
2025-11-04 11:16:22 -06:00
Rob Winch 8fa2fc0e1e Merge branch '6.4.x' into 6.5.x 2025-11-04 10:24:15 -06:00
Daniel Garnier-Moiroux 4feeb0f843 Docs: document effects of disabling CORS configurer
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
2025-11-04 10:23:46 -06:00
namest504 6501e97ece Fix sensitive case in JwtTypeValidator
Closes gh-18092

Signed-off-by: namest504 <namest504@gmail.com>
2025-10-28 12:08:29 -06:00
dependabot[bot] ee49c18ce2 Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.33.Final to 6.6.34.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.34/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.33...6.6.34)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.34.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:20:26 +00:00
dependabot[bot] f0afca7610 Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.18.4.1 to 2.18.5.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.18.4.1...jackson-bom-2.18.5)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.18.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:19:44 +00:00
dependabot[bot] 8b0689cbb8 Bump org.hibernate.orm:hibernate-core from 6.6.33.Final to 6.6.34.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.33.Final to 6.6.34.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.34/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.33...6.6.34)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.34.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:15:58 +00:00
dependabot[bot] 28e158d1cb Bump com.fasterxml.jackson:jackson-bom from 2.18.4.1 to 2.18.5
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.18.4.1 to 2.18.5.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.18.4.1...jackson-bom-2.18.5)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.18.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 03:15:38 +00:00
Josh Cummings f548aaf5c5 Merge branch '6.4.x' into 6.5.x 2025-10-20 17:42:25 -06:00
Josh Cummings 1c112005fa Don't Attempt to Generate Token Without Valid Token Request
Closes gh-18088

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2025-10-20 17:09:43 -06:00
Marcus Hert da Coregio e0a71eb00e Fix GenerateOneTimeTokenRequestResolver ignored if username param not present
Signed-off-by: Marcus Hert da Coregio <marcusdacoregio@gmail.com>
2025-10-20 17:09:43 -06:00
Josh Cummings 42ddaba870 Next Development Version 2025-10-20 17:07:18 -06:00
Himanshu Pareek dcb4e47cd5 Add Include-Code to the Password Storage page
References gh-16226

Signed-off-by: Himanshu Pareek <himanshupareekiit01@gmail.com>
2025-10-20 16:35:23 -06:00
Rob Winch 82f87cf2b6 Next Development Version 2025-10-20 16:55:17 -05:00
github-actions[bot] dc5aed9b5f Release 6.4.12 2025-10-20 17:17:37 +00:00
28 changed files with 543 additions and 319 deletions
@@ -3758,7 +3758,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* http
* // ...
* .webAuthn((webAuthn) -&gt; webAuthn
* .rpName("Spring Security Relying Party")
* .rpId("example.com")
* .allowedOrigins("https://example.com")
* );
@@ -243,9 +243,10 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
WebAuthnRelyingPartyOperations.class);
return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
this.allowedOrigins));
String rpName = (this.rpName != null) ? this.rpName : this.rpId;
return webauthnOperationsBean
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
}
}
@@ -19,6 +19,8 @@ package org.springframework.security.config.annotation.web.configurers;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -52,6 +54,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -127,6 +131,42 @@ public class WebAuthnConfigurerTests {
.hasSize(1);
}
@Test
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
}
@Test
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
ObjectMapper mapper = new ObjectMapper();
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
String response = this.mvc
.perform(post("/webauthn/register/options").with(csrf())
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
JsonNode parsedResponse = mapper.readTree(response);
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
}
@Test
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
@@ -300,7 +340,27 @@ public class WebAuthnConfigurerTests {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin(Customizer.withDefaults()).webAuthn(Customizer.withDefaults()).build();
return http.formLogin(Customizer.withDefaults())
.webAuthn((webauthn) -> webauthn.rpId("example.com"))
.build();
}
}
@Configuration
@EnableWebSecurity
static class CustomRpNameWebauthnConfiguration {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.formLogin(Customizer.withDefaults())
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
.build();
}
}
@@ -16,12 +16,14 @@
package org.springframework.security.crypto.bcrypt;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* @author Dave Syer
@@ -253,4 +255,23 @@ public class BCryptPasswordEncoderTests {
assertThat(encoder.matches(password73chars, encodedPassword73chars)).isTrue();
}
/**
* Fixes gh-18133
* @author StringManolo
*/
@Test
void passwordLargerThan72BytesShouldThrowIllegalArgumentException() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String singleByteChars = "a".repeat(68);
String password72Bytes = singleByteChars + "😀";
assertThat(password72Bytes.length()).isEqualTo(70);
assertThat(password72Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(72);
assertThatNoException().isThrownBy(() -> encoder.encode(password72Bytes));
String singleByteCharsTooLong = "a".repeat(69);
String password73Bytes = singleByteCharsTooLong + "😀";
assertThat(password73Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(73);
assertThatIllegalArgumentException().isThrownBy(() -> encoder.encode(password73Bytes))
.withMessageContaining("password cannot be more than 72 bytes");
}
}
@@ -67,68 +67,12 @@ Instead Spring Security introduces `DelegatingPasswordEncoder`, which solves all
You can easily construct an instance of `DelegatingPasswordEncoder` by using `PasswordEncoderFactories`:
.Create Default DelegatingPasswordEncoder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
----
======
include-code::./DelegatingPasswordEncoderUsage[tag=createDefaultPasswordEncoder,indent=0]
Alternatively, you can create your own custom instance:
.Create Custom DelegatingPasswordEncoder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
val idForEncode = "bcrypt"
val encoders: MutableMap<String, PasswordEncoder> = mutableMapOf()
encoders[idForEncode] = BCryptPasswordEncoder()
encoders["noop"] = NoOpPasswordEncoder.getInstance()
encoders["pbkdf2"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5()
encoders["pbkdf2@SpringSecurity_v5_8"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["scrypt"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()
encoders["scrypt@SpringSecurity_v5_8"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["argon2"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2()
encoders["argon2@SpringSecurity_v5_8"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["sha256"] = StandardPasswordEncoder()
val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)
----
======
include-code::./DelegatingPasswordEncoderUsage[tag=createCustomPasswordEncoder,indent=0]
[[authentication-password-storage-dpe-format]]
=== Password Storage Format
@@ -209,74 +153,12 @@ If you are putting together a demo or a sample, it is a bit cumbersome to take t
There are convenience mechanisms to make this easier, but this is still not intended for production.
.withDefaultPasswordEncoder Example
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build()
println(user.password)
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
======
include-code::./WithDefaultPasswordEncoderUsage[tag=createSingleUser,indent=0]
If you are creating multiple users, you can also reuse the builder:
.withDefaultPasswordEncoder Reusing the Builder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = users
.username("admin")
.password("password")
.roles("USER","ADMIN")
.build();
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
val users = User.withDefaultPasswordEncoder()
val user = users
.username("user")
.password("password")
.roles("USER")
.build()
val admin = users
.username("admin")
.password("password")
.roles("USER", "ADMIN")
.build()
----
======
include-code::./WithDefaultPasswordEncoderUsage[tag=createMultipleUsers,indent=0]
This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code.
Therefore, it is still not considered secure for a production environment.
@@ -337,28 +219,7 @@ The default implementation of `BCryptPasswordEncoder` uses strength 10 as mentio
tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.
.BCryptPasswordEncoder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with strength 16
val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
======
include-code::./BCryptPasswordEncoderUsage[tag=bcryptPasswordEncoder,indent=0]
[[authentication-password-storage-argon2]]
== Argon2PasswordEncoder
@@ -370,28 +231,7 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
The current implementation of the `Argon2PasswordEncoder` requires BouncyCastle.
.Argon2PasswordEncoder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
======
include-code::./Argon2PasswordEncoderUsage[tag=argon2PasswordEncoder,indent=0]
[[authentication-password-storage-pbkdf2]]
== Pbkdf2PasswordEncoder
@@ -402,28 +242,7 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
This algorithm is a good choice when FIPS certification is required.
.Pbkdf2PasswordEncoder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
======
include-code::./Pbkdf2PasswordEncoderUsage[tag=pbkdf2PasswordEncoder,indent=0]
[[authentication-password-storage-scrypt]]
== SCryptPasswordEncoder
@@ -433,28 +252,7 @@ To defeat password cracking on custom hardware, scrypt is a deliberately slow al
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
.SCryptPasswordEncoder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
======
include-code::./SCryptPasswordEncoderUsage[tag=sCryptPasswordEncoder,indent=0]
[[authentication-password-storage-other]]
== Other ``PasswordEncoder``s
@@ -606,86 +404,4 @@ However, just a 401 or the redirect is not so useful in that case, it will cause
In such cases, you can handle the `CompromisedPasswordException` via the `AuthenticationFailureHandler` to perform your desired logic, like redirecting the user-agent to `/reset-password`, for example:
.Using CompromisedPasswordChecker
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin((login) -> login
.failureHandler(new CompromisedPasswordAuthenticationFailureHandler())
);
return http.build();
}
@Bean
public CompromisedPasswordChecker compromisedPasswordChecker() {
return new HaveIBeenPwnedRestApiPasswordChecker();
}
static class CompromisedPasswordAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final SimpleUrlAuthenticationFailureHandler defaultFailureHandler = new SimpleUrlAuthenticationFailureHandler(
"/login?error");
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof CompromisedPasswordException) {
this.redirectStrategy.sendRedirect(request, response, "/reset-password");
return;
}
this.defaultFailureHandler.onAuthenticationFailure(request, response, exception);
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
open fun filterChain(http:HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin {
failureHandler = CompromisedPasswordAuthenticationFailureHandler()
}
}
return http.build()
}
@Bean
open fun compromisedPasswordChecker(): CompromisedPasswordChecker {
return HaveIBeenPwnedRestApiPasswordChecker()
}
class CompromisedPasswordAuthenticationFailureHandler : AuthenticationFailureHandler {
private val defaultFailureHandler = SimpleUrlAuthenticationFailureHandler("/login?error")
private val redirectStrategy = DefaultRedirectStrategy()
override fun onAuthenticationFailure(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AuthenticationException
) {
if (exception is CompromisedPasswordException) {
redirectStrategy.sendRedirect(request, response, "/reset-password")
return
}
defaultFailureHandler.onAuthenticationFailure(request, response, exception)
}
}
----
======
include-code::./CompromisedPasswordCheckerUsage[tag=configuration,indent=0]
@@ -1,4 +1,3 @@
[[webflux-cors]]
= CORS
@@ -75,3 +74,11 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
----
======
[WARNING]
====
CORS is a browser-based security feature.
By disabling CORS in Spring Security, you are not removing CORS protection from your browser.
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
====
@@ -65,7 +65,6 @@ SecurityFilterChain filterChain(HttpSecurity http) {
// ...
.formLogin(withDefaults())
.webAuthn((webAuthn) -> webAuthn
.rpName("Spring Security Relying Party")
.rpId("example.com")
.allowedOrigins("https://example.com")
// optional properties
@@ -96,7 +95,6 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
// ...
http {
webAuthn {
rpName = "Spring Security Relying Party"
rpId = "example.com"
allowedOrigins = setOf("https://example.com")
// optional properties
@@ -183,3 +183,11 @@ fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
}
----
======
[WARNING]
====
CORS is a browser-based security feature.
By disabling CORS in Spring Security with `.cors(CorsConfigurer::disable)`, you are not removing CORS protection from your browser.
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
====
@@ -0,0 +1,57 @@
package org.springframework.security.docs.features.authentication.authenticationcompromisedpasswordcheck;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker;
public class CompromisedPasswordCheckerUsage {
// tag::configuration[]
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin((login) -> login
.failureHandler(new CompromisedPasswordAuthenticationFailureHandler())
);
return http.build();
}
@Bean
public CompromisedPasswordChecker compromisedPasswordChecker() {
return new HaveIBeenPwnedRestApiPasswordChecker();
}
static class CompromisedPasswordAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final SimpleUrlAuthenticationFailureHandler defaultFailureHandler = new SimpleUrlAuthenticationFailureHandler(
"/login?error");
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof CompromisedPasswordException) {
this.redirectStrategy.sendRedirect(request, response, "/reset-password");
return;
}
this.defaultFailureHandler.onAuthenticationFailure(request, response, exception);
}
}
// end::configuration[]
}
@@ -0,0 +1,16 @@
package org.springframework.security.docs.features.authentication.authenticationpasswordstorageargon2;
import static org.junit.Assert.assertTrue;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
public class Argon2PasswordEncoderUsage {
public void testArgon2PasswordEncoder() {
// tag::argon2PasswordEncoder[]
// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// end::argon2PasswordEncoder[]
}
}
@@ -0,0 +1,16 @@
package org.springframework.security.docs.features.authentication.authenticationpasswordstoragebcrypt;
import static org.junit.Assert.assertTrue;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptPasswordEncoderUsage {
public void testBCryptPasswordEncoder() {
// tag::bcryptPasswordEncoder[]
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// end::bcryptPasswordEncoder[]
}
}
@@ -0,0 +1,38 @@
package org.springframework.security.docs.features.authentication.authenticationpasswordstoragedepgettingstarted;
import java.util.List;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import static org.springframework.security.core.userdetails.User.UserBuilder;
public class WithDefaultPasswordEncoderUsage {
public UserDetails createSingleUser() {
// tag::createSingleUser[]
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
// end::createSingleUser[]
return user;
}
public List<UserDetails> createMultipleUsers() {
// tag::createMultipleUsers[]
UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = users
.username("admin")
.password("password")
.roles("USER","ADMIN")
.build();
// end::createMultipleUsers[]
return List.of(user, admin);
}
}
@@ -0,0 +1,43 @@
package org.springframework.security.docs.features.authentication.authenticationpasswordstoragedpe;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
public class DelegatingPasswordEncoderUsage {
PasswordEncoder defaultDelegatingPasswordEncoder() {
// tag::createDefaultPasswordEncoder[]
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
// end::createDefaultPasswordEncoder[]
return passwordEncoder;
}
PasswordEncoder customDelegatingPasswordEncoder() {
// tag::createCustomPasswordEncoder[]
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
// end::createCustomPasswordEncoder[]
return passwordEncoder;
}
}
@@ -0,0 +1,16 @@
package org.springframework.security.docs.features.authentication.authenticationpasswordstoragepbkdf2;
import static org.junit.Assert.assertTrue;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
public class Pbkdf2PasswordEncoderUsage {
void testPbkdf2PasswordEncoder() {
// tag::pbkdf2PasswordEncoder[]
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// end::pbkdf2PasswordEncoder[]
}
}
@@ -0,0 +1,16 @@
package org.springframework.security.docs.features.authentication.authenticationpasswordstoragescrypt;
import static org.junit.Assert.assertTrue;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
public class SCryptPasswordEncoderUsage {
void testSCryptPasswordEncoder() {
// tag::sCryptPasswordEncoder[]
// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// end::sCryptPasswordEncoder[]
}
}
@@ -0,0 +1,55 @@
package org.springframework.security.kt.docs.features.authentication.authenticationcompromisedpasswordcheck
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Bean
import org.springframework.security.authentication.password.CompromisedPasswordChecker
import org.springframework.security.authentication.password.CompromisedPasswordException
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.DefaultRedirectStrategy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker
class CompromisedPasswordCheckerUsage {
// tag::configuration[]
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin {
authenticationFailureHandler = CompromisedPasswordAuthenticationFailureHandler()
}
}
return http.build()
}
@Bean
open fun compromisedPasswordChecker(): CompromisedPasswordChecker {
return HaveIBeenPwnedRestApiPasswordChecker()
}
class CompromisedPasswordAuthenticationFailureHandler : AuthenticationFailureHandler {
private val defaultFailureHandler = SimpleUrlAuthenticationFailureHandler("/login?error")
private val redirectStrategy = DefaultRedirectStrategy()
override fun onAuthenticationFailure(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AuthenticationException
) {
if (exception is CompromisedPasswordException) {
redirectStrategy.sendRedirect(request, response, "/reset-password")
return
}
defaultFailureHandler.onAuthenticationFailure(request, response, exception)
}
}
// end::configuration[]
}
@@ -0,0 +1,15 @@
package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstorageargon2
import org.junit.Assert.assertTrue
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
class Argon2PasswordEncoderUsage {
fun testArgon2PasswordEncoder() {
// tag::argon2PasswordEncoder[]
// Create an encoder with all the defaults
val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
// end::argon2PasswordEncoder[]
}
}
@@ -0,0 +1,15 @@
package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragebcrypt
import org.junit.Assert.assertTrue
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
class BCryptPasswordEncoderUsage {
fun testBCryptPasswordEncoder() {
// tag::bcryptPasswordEncoder[]
// Create an encoder with strength 16
val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
// end::bcryptPasswordEncoder[]
}
}
@@ -0,0 +1,36 @@
package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragedepgettingstarted
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
class WithDefaultPasswordEncoderUsage {
fun createSingleUser(): UserDetails {
// tag::createSingleUser[]
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build()
println(user.password)
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
// end::createSingleUser[]
return user
}
fun createMultipleUsers(): List<UserDetails> {
// tag::createMultipleUsers[]
val users = User.withDefaultPasswordEncoder()
val user = users
.username("user")
.password("password")
.roles("USER")
.build()
val admin = users
.username("admin")
.password("password")
.roles("USER", "ADMIN")
.build()
// end::createMultipleUsers[]
return listOf(user, admin)
}
}
@@ -0,0 +1,39 @@
package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragedpe
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.DelegatingPasswordEncoder
import org.springframework.security.crypto.password.NoOpPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder
import org.springframework.security.crypto.password.StandardPasswordEncoder
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder
class DelegatingPasswordEncoderUsage {
fun defaultDelegatingPasswordEncoder(): PasswordEncoder {
// tag::createDefaultPasswordEncoder[]
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
// end::createDefaultPasswordEncoder[]
return passwordEncoder
}
fun customDelegatingPasswordEncoder(): PasswordEncoder {
// tag::createCustomPasswordEncoder[]
val idForEncode = "bcrypt"
val encoders: MutableMap<String, PasswordEncoder> = mutableMapOf()
encoders[idForEncode] = BCryptPasswordEncoder()
encoders["noop"] = NoOpPasswordEncoder.getInstance()
encoders["pbkdf2"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5()
encoders["pbkdf2@SpringSecurity_v5_8"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["scrypt"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()
encoders["scrypt@SpringSecurity_v5_8"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["argon2"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2()
encoders["argon2@SpringSecurity_v5_8"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["sha256"] = StandardPasswordEncoder()
val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)
// end::createCustomPasswordEncoder[]
return passwordEncoder
}
}
@@ -0,0 +1,15 @@
package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragepbkdf2
import org.junit.Assert.assertTrue
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder
class Pbkdf2PasswordEncoderUsage {
fun testPbkdf2PasswordEncoder() {
// tag::pbkdf2PasswordEncoder[]
// Create an encoder with all the defaults
val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
// end::pbkdf2PasswordEncoder[]
}
}
@@ -0,0 +1,15 @@
package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragescrypt
import org.junit.Assert.assertTrue
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder
class SCryptPasswordEncoderUsage {
fun testSCryptPasswordEncoder() {
// tag::sCryptPasswordEncoder[]
// Create an encoder with all the defaults
val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
// end::sCryptPasswordEncoder[]
}
}
+1 -1
View File
@@ -14,7 +14,7 @@
# limitations under the License.
#
springBootVersion=3.3.3
version=6.5.6
version=6.5.7
samplesBranch=6.5.x
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+8 -8
View File
@@ -6,7 +6,7 @@ io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.2.0"
org-apache-directory-server = "1.5.5"
org-apache-maven-resolver = "1.9.24"
org-aspectj = "1.9.24"
org-aspectj = "1.9.25"
org-bouncycastle = "1.80"
org-eclipse-jetty = "11.0.26"
org-jetbrains-kotlin = "1.9.25"
@@ -14,11 +14,11 @@ org-jetbrains-kotlinx = "1.10.2"
org-mockito = "5.17.0"
org-opensaml = "4.3.2"
org-opensaml5 = "5.1.2"
org-springframework = "6.2.12"
org-springframework = "6.2.13"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.20"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.4.1"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.5"
com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.4"
@@ -29,15 +29,15 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.12"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.13"
io-mockk = "io.mockk:mockk:1.14.6"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.19"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2024.0.12"
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.11"
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:2.1.1"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0"
@@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on",
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" }
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.33.Final"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.34.Final"
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
@@ -89,7 +89,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.11"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.12"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.15"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
@@ -72,8 +72,10 @@ public final class JwtTypeValidator implements OAuth2TokenValidator<Jwt> {
if (this.allowEmpty && !StringUtils.hasText(typ)) {
return OAuth2TokenValidatorResult.success();
}
if (this.validTypes.contains(typ)) {
return OAuth2TokenValidatorResult.success();
for (String validType : this.validTypes) {
if (validType.equalsIgnoreCase(typ)) {
return OAuth2TokenValidatorResult.success();
}
}
return OAuth2TokenValidatorResult.failure(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN,
"the given typ value needs to be one of " + this.validTypes,
@@ -44,4 +44,12 @@ class JwtTypeValidatorTests {
assertThat(validator.validate(jwt.build()).hasErrors()).isFalse();
}
@Test
void validateWhenTypHeaderHasDifferentCaseThenSuccess() {
Jwt.Builder jwt = TestJwts.jwt();
JwtTypeValidator validator = new JwtTypeValidator("at+jwt");
jwt.header(JoseHeaderNames.TYP, "AT+JWT");
assertThat(validator.validate(jwt.build()).hasErrors()).isFalse();
}
}
@@ -30,7 +30,6 @@ import org.springframework.security.authentication.ott.OneTimeTokenService;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
@@ -68,17 +67,12 @@ public final class GenerateOneTimeTokenFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
return;
}
String username = request.getParameter("username");
if (!StringUtils.hasText(username)) {
filterChain.doFilter(request, response);
return;
}
GenerateOneTimeTokenRequest generateRequest = this.requestResolver.resolve(request);
OneTimeToken ott = this.tokenService.generate(generateRequest);
if (generateRequest == null) {
filterChain.doFilter(request, response);
return;
}
OneTimeToken ott = this.tokenService.generate(generateRequest);
this.tokenGenerationSuccessHandler.handle(request, response, ott);
}
@@ -113,4 +113,22 @@ public class GenerateOneTimeTokenFilterTests {
// @formatter:on
}
@Test
void filterWhenUsernameFormParamIsEmptyButRequestResolverCanResolveThenSuccess()
throws ServletException, IOException {
GenerateOneTimeTokenRequestResolver requestResolver = mock();
given(this.oneTimeTokenService.generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class)))
.willReturn((new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now())));
given(requestResolver.resolve(this.request)).willReturn(new GenerateOneTimeTokenRequest(USERNAME));
GenerateOneTimeTokenFilter filter = new GenerateOneTimeTokenFilter(this.oneTimeTokenService,
this.successHandler);
filter.setRequestResolver(requestResolver);
filter.doFilter(this.request, this.response, this.filterChain);
verify(this.oneTimeTokenService).generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class));
assertThat(this.response.getRedirectedUrl()).isEqualTo("/login/ott");
}
}