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:
  1. 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's ShaPasswordEncoder configured with ReflectionSaltSource(userPropertyToUse="username") — see Gemma's pre-Phase-2 applicationContext-security.xml). Found in sql/init-data.sql and very old prod rows.
  2. Bare BCrypt: starts with $2a$ / $2b$ / $2y$, no {bcrypt} prefix. What Gemma 1.x / Spring Security 4-era BCryptPasswordEncoder.encode produced before DelegatingPasswordEncoder introduced the {…} prefix convention in Spring Security 5. The bulk of production Gemma rows are in this format.
  3. {bcrypt}-prefixed BCrypt: Spring Security 5/6's DelegatingPasswordEncoder convention. What encode(CharSequence) produces for new passwords.

How the username is supplied

Spring Security 6's PasswordEncoder 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:
Author:
Gemma
  • Field Details

    • BCRYPT_PREFIX

      public static final String 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

      public String encode(CharSequence rawPassword)
      Specified by:
      encode in interface org.springframework.security.crypto.password.PasswordEncoder
    • matches

      public boolean matches(CharSequence rawPassword, String encodedPassword)
      Specified by:
      matches in interface org.springframework.security.crypto.password.PasswordEncoder
    • upgradeEncoding

      public boolean upgradeEncoding(String encodedPassword)
      Specified by:
      upgradeEncoding in interface org.springframework.security.crypto.password.PasswordEncoder
    • isBareBcrypt

      public static boolean isBareBcrypt(String encodedPassword)
      Returns:
      true iff encodedPassword looks like a bare BCrypt hash — starts with $2a$, $2b$, or $2y$ (the standard BCrypt version tags) and has plausible length. Spring Security 4's BCryptPasswordEncoder.encode produced hashes in this form without the {bcrypt} prefix the DelegatingPasswordEncoder convention expects — Gemma 1.x rows are stored this way and the production database is full of them.
    • isLegacySha1Hex

      public static boolean isLegacySha1Hex(String encodedPassword)
      Returns:
      true iff encodedPassword looks like a Gemma legacy SHA-1 hash (bare 40-char lowercase/uppercase hex, no prefix). Used by LegacyAwareDaoAuthenticationProvider to route auth checks.
    • sha1HexUsernameSalt

      public static String sha1HexUsernameSalt(CharSequence rawPassword, String username)
      Compute the legacy hash. Format: lowercase-hex(SHA-1(UTF-8(rawPassword + "{" + username + "}"))).
    • constantTimeHexEquals

      public static boolean constantTimeHexEquals(String a, String b)
      Constant-time, case-insensitive comparison of two hex strings of equal length.