Skip to content

Avoid spring-core in password encoder validation#19341

Open
seonwooj0810 wants to merge 1 commit into
spring-projects:mainfrom
seonwooj0810:gh-19317
Open

Avoid spring-core in password encoder validation#19341
seonwooj0810 wants to merge 1 commit into
spring-projects:mainfrom
seonwooj0810:gh-19317

Conversation

@seonwooj0810

Copy link
Copy Markdown

Closes gh-19317

Root cause

spring-security-crypto declares org.springframework:spring-core as an optional dependency in crypto/spring-security-crypto.gradle, so it is not propagated through the published POM. The module is intentionally usable standalone — historically the runtime code paths only relied on JDK types.

In 7.1.0 the validation block in AbstractValidatingPasswordEncoder#matches and #upgradeEncoding was refactored to use org.springframework.util.StringUtils#hasLength (commit 9323775, "Update javadoc and apply StringUtils#hasLength"). That introduced a hard reference from a hot path to a spring-core class. Standalone consumers of spring-security-crypto (e.g. anyone calling BCryptPasswordEncoder#matches) now hit:

java.lang.NoClassDefFoundError: org/springframework/util/StringUtils

The invariant being violated is the module-level one expressed by the gradle file: the runtime hot path of spring-security-crypto must not depend on spring-core. @org.springframework.lang.Contract is also referenced in this class but has RetentionPolicy.CLASS, so it is not loaded at runtime and is not affected.

Change

Replace the two StringUtils.hasLength(...) calls with the inline == null || isEmpty() checks that were in place before 9323775, and drop the now-unused import. The semantics are identical (StringUtils.hasLength(cs) is defined as cs != null && cs.length() > 0).

KeyStoreKeyFactory also uses StringUtils.getFilenameExtension, but its constructor already takes org.springframework.core.io.Resource, so users of that class necessarily have spring-core on the classpath and it is not a regression. This PR leaves it untouched.

Test evidence

The existing AbstractPasswordEncoderValidationTests (extended by AbstractValidatingPasswordEncoderTests, BCryptPasswordEncoderTests, etc.) already exercises the null/empty rawPassword and encodedPassword cases on the affected branch, and continues to pass:

./gradlew :spring-security-crypto:compileJava :spring-security-crypto:test --rerun-tasks \
  --tests "*AbstractValidatingPasswordEncoderTests" \
  --tests "*BCryptPasswordEncoderTests" \
  --tests "*AbstractPasswordEncoderTests"
=> BUILD SUCCESSFUL

./gradlew :spring-security-crypto:format :spring-security-crypto:check -x test
=> BUILD SUCCESSFUL  (checkstyleMain, checkstyleTest, checkFormat all pass)

I considered adding a dedicated regression test that asserts the class can load without spring-core, but driving an isolated classloader from a test that itself runs under the project's full classpath is more invasive than the fix and harder to maintain — happy to add one if maintainers prefer.

`spring-core` is an `optional` dependency of `spring-security-crypto`,
so it is not published in the POM and is not guaranteed to be on the
classpath when the module is used standalone.

7.1.0 refactored `AbstractValidatingPasswordEncoder` to use
`org.springframework.util.StringUtils#hasLength`, which causes
`NoClassDefFoundError` on `BCryptPasswordEncoder#matches` (and other
encoders extending it) for standalone users.

Restore the inline null/empty checks the encoder used prior to that
refactor so that the runtime hot path no longer reaches into
`spring-core`.

Closes spring-projectsgh-19317

Signed-off-by: seonwoo_jung <laborlawseon@kap.kr>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NoClassDefFound with version 7.1.0

2 participants