Class GemmaLegacyAwarePasswordEncoder
java.lang.Object
ubic.gemma.core.security.authentication.GemmaLegacyAwarePasswordEncoder
- All Implemented Interfaces:
org.springframework.security.crypto.password.PasswordEncoder
public class GemmaLegacyAwarePasswordEncoder
extends Object
implements org.springframework.security.crypto.password.PasswordEncoder
Spring Security 6
PasswordEncoder that understands three formats:
- Legacy SHA-1 + username-as-salt: stored as a bare 40-char hex digest with no
prefix, computed as
SHA-1(rawPassword + "{" + username + "}")(the exact output of Spring Security 3/4'sShaPasswordEncoderconfigured withReflectionSaltSource(userPropertyToUse="username")— see Gemma's pre-Phase-2applicationContext-security.xml). Found insql/init-data.sqland very old prod rows. - Bare BCrypt: starts with
$2a$/$2b$/$2y$, no{bcrypt}prefix. What Gemma 1.x / Spring Security 4-eraBCryptPasswordEncoder.encodeproduced beforeDelegatingPasswordEncoderintroduced the{…}prefix convention in Spring Security 5. The bulk of production Gemma rows are in this format. - {bcrypt}-prefixed BCrypt: Spring Security 5/6's
DelegatingPasswordEncoderconvention. Whatencode(CharSequence)produces for new passwords.
How the username is supplied
Spring Security 6'sPasswordEncoder interface is username-agnostic
(encode(rawPassword) / matches(rawPassword, encodedPassword)). Salting with
the username therefore requires the username to arrive via a side channel. Earlier
Phase-2 versions of this class used a ThreadLocal bound by a custom auth provider,
but that pattern is hostile to async / reactive request flows and to per-request thread
reuse (Phase 3 cloud-readiness goal). The username is now supplied directly, with the
legacy-vs-bcrypt detection still done by inspecting the stored encoded string:
LegacyAwareDaoAuthenticationProvider.additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)detects a legacy hash onuserDetails.getPassword()and verifies it itself using the authoritative username fromuserDetails.getUsername().matches(CharSequence, String)is never called for legacy hashes.upgradeEncoding(String)returnstruefor legacy hashes, so Spring Security'sDaoAuthenticationProviderre-encodes the verified plaintext viaencode(CharSequence)and writes it back viaUserDetailsPasswordService(implemented byUserManagerImpl).matches(CharSequence, String)only handles bcrypt; legacy hashes fail closed here.
- Author:
- Gemma
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringPrefix Spring Security's DelegatingPasswordEncoder uses for bcrypt hashes. -
Constructor Summary
ConstructorsConstructorDescriptionGemmaLegacyAwarePasswordEncoder(org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder bcrypt) -
Method Summary
Modifier and TypeMethodDescriptionstatic booleanConstant-time, case-insensitive comparison of two hex strings of equal length.encode(CharSequence rawPassword) static booleanisBareBcrypt(String encodedPassword) static booleanisLegacySha1Hex(String encodedPassword) booleanmatches(CharSequence rawPassword, String encodedPassword) static Stringsha1HexUsernameSalt(CharSequence rawPassword, String username) Compute the legacy hash.booleanupgradeEncoding(String encodedPassword)
-
Field Details
-
BCRYPT_PREFIX
Prefix Spring Security's DelegatingPasswordEncoder uses for bcrypt hashes.- See Also:
-
-
Constructor Details
-
GemmaLegacyAwarePasswordEncoder
public GemmaLegacyAwarePasswordEncoder() -
GemmaLegacyAwarePasswordEncoder
public GemmaLegacyAwarePasswordEncoder(org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder bcrypt)
-
-
Method Details
-
encode
- Specified by:
encodein interfaceorg.springframework.security.crypto.password.PasswordEncoder
-
matches
- Specified by:
matchesin interfaceorg.springframework.security.crypto.password.PasswordEncoder
-
upgradeEncoding
- Specified by:
upgradeEncodingin interfaceorg.springframework.security.crypto.password.PasswordEncoder
-
isBareBcrypt
- Returns:
trueiffencodedPasswordlooks like a bare BCrypt hash — starts with$2a$,$2b$, or$2y$(the standard BCrypt version tags) and has plausible length. Spring Security 4'sBCryptPasswordEncoder.encodeproduced hashes in this form without the{bcrypt}prefix theDelegatingPasswordEncoderconvention expects — Gemma 1.x rows are stored this way and the production database is full of them.
-
isLegacySha1Hex
- Returns:
trueiffencodedPasswordlooks like a Gemma legacy SHA-1 hash (bare 40-char lowercase/uppercase hex, no prefix). Used byLegacyAwareDaoAuthenticationProviderto route auth checks.
-
sha1HexUsernameSalt
Compute the legacy hash. Format:lowercase-hex(SHA-1(UTF-8(rawPassword + "{" + username + "}"))). -
constantTimeHexEquals
-