[Spring Security] - ๐Ÿ” PasswordEncoder

CodeByHanยท2025๋…„ 4์›” 17์ผ

์Šคํ”„๋ง

๋ชฉ๋ก ๋ณด๊ธฐ
23/33


https://www.boannews.com/media/view.asp?idx=78058&page=1&kind=1

์ด์ฒ˜๋Ÿผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ์ €์žฅํ•˜๋Š” ํ–‰์œ„๋Š” ํ•ดํ‚น์ด๋‚˜ ๋‚ด๋ถ€ ์œ ์ถœ ์‹œ ์‚ฌ์šฉ์ž ๊ณ„์ • ํƒˆ์ทจ ์œ„ํ—˜์„ ๊ทน๋‹จ์ ์œผ๋กœ ๋†’์ผ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์„œ๋น„์Šค ์‹ ๋ขฐ๋„์—๋„ ๋Œ์ดํ‚ฌ ์ˆ˜ ์—†๋Š” ํƒ€๊ฒฉ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.
๋”ฐ๋ผ์„œ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ(Spring Security)๋Š” PasswordEncoder ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™” ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•œ๋‹ค.

PasswordEncoder ํ•„์š”์„ฑ

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ AuthenticationProvider๋Š” ์ธ์ฆ ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ(ํ‰๋ฌธ)์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ(์•”ํ˜ธํ™”/ํ•ด์‹œ๋œ ๊ฐ’)๋ฅผ ๋น„๊ตํ•ด์•ผ ํ•œ๋‹ค. ์ด๋•Œ ๋‹จ์ˆœ ๋ฌธ์ž์—ด ๋น„๊ต๊ฐ€ ์•„๋‹ˆ๋ผ, ์ž…๋ ฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์•”ํ˜ธํ™”(ํ•ด์‹ฑ)ํ•˜์—ฌ ์ €์žฅ๋œ ๊ฐ’๊ณผ ๋น„๊ตํ•ด์•ผ ํ•˜๋ฏ€๋กœ PasswordEncoder๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค.

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ 5.x ์ด์ƒ์—์„œ๋Š” PasswordEncoder๊ฐ€ ๋“ฑ๋ก๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด
There is no PasswordEncoder mapped for the id "null"
์ด๋ผ๋Š” ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

์ด์ „ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๋ฒ„์ „(5.0 ๋ฏธ๋งŒ)์—์„œ๋Š” NoOpPasswordEncoder๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•ด ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ทธ๋Œ€๋กœ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

DB์— ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅ๋˜์–ด ์žˆ๊ณ  PasswordEncoder๊ฐ€ BCrypt ๋“ฑ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค๋ฉด, ํ‰๋ฌธ๊ณผ ์•”ํ˜ธํ™”๋œ ๊ฐ’์„ ๋น„๊ตํ•˜๋ ค๋‹ค "Encoded password does not look like BCrypt"์™€ ๊ฐ™์€ ์˜ˆ์™ธ ๋˜๋Š” ์ธ์ฆ ์‹คํŒจ๊ฐ€ ๋ฐœ์ƒ

PasswordEncoder

public interface PasswordEncoder {

	String encode(CharSequence rawPassword);

	boolean matches(CharSequence rawPassword, String encodedPassword);

	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}
    
}
  • ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”(ํ•ด์‹ฑ)ํ•ด์„œ ์ €์žฅํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์ €์žฅ๋œ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ์—ญํ• 
  • String encode(CharSequence rawPassword) : ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์•”ํ˜ธํ™”(ํ•ด์‹ฑ)ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ์— ์ €์žฅ
  • boolean matches(CharSequence rawPassword, String encodedPassword) : ๋กœ๊ทธ์ธ ์‹œ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•˜์—ฌ, DB์— ์ €์žฅ๋œ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
  • default boolean upgradeEncoding(String encodedPassword : ์ €์žฅ๋œ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ(encodedPassword)๊ฐ€ ๋” ๊ฐ•๋ ฅํ•œ ์•”ํ˜ธํ™” ๋ฐฉ์‹์œผ๋กœ ๋‹ค์‹œ ์•”ํ˜ธํ™”๋˜์–ด์•ผ ํ•˜๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ์—ญํ• 
  • ๊ธฐ๋ณธ ๊ตฌํ˜„์€ ํ•ญ์ƒ false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ฆ‰, ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” "์žฌ์•”ํ˜ธํ™”๊ฐ€ ํ•„์š” ์—†๋‹ค"๊ณ  ๊ฐ„์ฃผ

PasswordEncoder ๊ตฌํ˜„ ํด๋ž˜์Šค

  • Argon2PasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder
  • BcryptPasswordEncoder

BcryptPasswordEncoder

  • ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ(Spring Security)์—์„œ ์ œ๊ณตํ•˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”(ํ•ด์‹ฑ) ํด๋ž˜์Šค
  • BCrypt ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์•”ํ˜ธํ™”ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์˜ ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ์—ญํ• 
@Configuration
public class SecurityConfig  {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • PasswordEncoder๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Bean์œผ๋กœ ๋“ฑ๋กํ•ด์•ผํ•œ๋‹ค.
  • BCryptPasswordEncoder๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹จ๋ฐฉํ–ฅ ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ธ BCrypt๋กœ ์•”ํ˜ธํ™”ํ•œ๋‹ค.

๐Ÿค” ๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธํ™” : ์›๋ณธ ๋ฉ”์‹œ์ง€๋ฅผ ์•Œ๋ฉด ์•”ํ˜ธํ™” ๋ฉ”์‹œ์ง€๋ฅผ ์•Œ ์ˆ˜ ์žˆ์ง€๋งŒ, ์•”ํ˜ธํ™” ๋ฉ”์‹œ์ง€๋กœ๋Š” ์›๋ณธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ(๋ณตํ˜ธํ™” ๋ถˆ๊ฐ€)์ด
์–‘๋ฐฉํ–ฅ ์•”ํ˜ธํ™” : ์•”ํ˜ธํ™” ๋ฉ”์‹œ์ง€๋กœ๋„ ์›๋ณธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ(๋ณตํ˜ธํ™” ๊ฐ€๋Šฅ)์ด ์–‘๋ฐฉํ–ฅ

๋งŒ์•ฝ DB๊ฐ€ ์œ ์ถœ๋˜๋”๋ผ๋„, DB ๋‚ด๋ถ€์— ์›๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋Œ€์‹  ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ €์žฅํ•ด๋‘๋ฉด ๋ณตํ˜ธํ™”๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•ˆ์ „ํ•˜๋‹ค.

BCrypt๋Š” ๊ฐ™์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•˜๋”๋ผ๋„ ํ•ด์‹œ ๊ฐ’์€ ๋งค๋ฒˆ ๋‹ค๋ฅธ ๊ฐ’์ด ๋„์ถœ๋œ๋‹ค. -> Salt ๋•Œ๋ฌธ!!

Salt

๋กœ๊ทธ์ธ์€ Rainbow table ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•˜๋‹ค. Rainbow table์€ ์ž…๋ ฅ ๊ฐ€๋Šฅํ•œ ๋ฌธ์ž์—ด ์กฐํ•ฉ์„ ํ•ด์‹œํ•จ์ˆ˜์— ๋„ฃ์–ด์„œ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•œ ํ…Œ์ด๋ธ”์ด๋‹ค. ์ด ํ…Œ์ด๋ธ”๋กœ ์ผ์ผ์ด ๋Œ€์กฐํ•˜์—ฌ ๋ธŒ๋ฃจํŠธ ํฌ์Šค ๋ฐฉ์‹์œผ๋กœ ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅ -> ํ•˜๋‚˜๋งŒ ๊ฑธ๋ ค๋ผ -> Salt๋กœ ํ•ด๊ฒฐ

  • ์†”ํŠธ๋Š” ํ•ด์‹ฑ ์ „์— ๋น„๋ฐ€๋ฒˆํ˜ธ์— ๋ฌด์ž‘์œ„ ๋ฌธ์ž์—ด์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฐ’
  • BCrypt๋Š” ์†”ํŠธ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ , ํ•ด์‹œ๊ฐ’์— ํฌํ•จ์‹œํ‚จ๋‹ค.

๋กœ๊ทธ๋ผ์šด๋“œ(log rounds)

BCryptPasswordEncoder์—์„œ ๊ฐ•๋„(strength, log rounds)๋Š” ํ•ด์‹œ ์—ฐ์‚ฐ์˜ ๋ฐ˜๋ณต ํšŸ์ˆ˜๋ฅผ ๊ฒฐ์ •ํ•˜๋ฉฐ, ๊ฐ’์ด ํด์ˆ˜๋ก ์•”ํ˜ธํ™”์— ๋“œ๋Š” ์‹œ๊ฐ„์ด ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ์ฆ๊ฐ€ํ•ด ๋ณด์•ˆ์ด ๋”์šฑ ๊ฐ•ํ™”ํ•œ๋‹ค.

๊ธฐ๋ณธ๊ฐ’์€ 10์ด๋ฉฐ 4 ~ 31 ์‚ฌ์ด์˜ ๊ฐ’์„ ์„ค์ •

@Configuration
public class SecurityConfig  {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(5);
    }
}

๋น„๋ฐ€๋ฒˆํ˜ธ ์˜ˆ์‹œ

$2a$10$VZQap7FCOxI7WZpsxBMEHuG2f..VjPk3Vaj5441s.Ud0YrgwCKwh6 
  • $2a$ : BCrypt ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฒ„์ „ ์ •๋ณด, $2a$๋Š” ๊ฐ€์žฅ ๋„๋ฆฌ ์“ฐ์ด๋Š” ํ‘œ์ค€ ๋ฒ„์ „
  • 10 : ๋กœ๊ทธ๋ผ์šด๋“œ(log rounds) -> ์ด ๊ฐ’์ด ์ปค์งˆ์ˆ˜๋ก ํ•ด์‹œ๊ฐ’์„ ๊ตฌํ•˜๋Š” ์†๋„๊ฐ€ ๋А๋ ค์ง„๋‹ค.
  • VZQap7FCOxI7WZpsxBMEHu : 22์ž๋ฆฌ ์†”ํŠธ(salt) -> ์†”ํŠธ๋Š” ํ•ด์‹œ๊ฐ’๊ณผ ํ•จ๊ป˜ ์ €์žฅ๋˜๋ฏ€๋กœ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค
  • ๊ทธ ๋’ค ๋ฌธ์ž์—ด๋“ค : ์†”ํŠธ์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ, ๋กœ๊ทธ๋ผ์šด๋“œ(log rounds)๋ฅผ ์กฐํ•ฉํ•ด ์ƒ์„ฑ๋œ ์‹ค์ œ ํ•ด์‹œ๊ฐ’

์•”ํ˜ธํ™”ํ•  ๋• ๋งค๋ฒˆ ๋‹ค๋ฅธ ์†”ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ๊ฒ€์ฆํ•  ๋•Œ๋Š” ์ด๋ฏธ ๋น„๋ฐ€๋ฒˆํ˜ธ์— ์ €์žฅ๋œ ์†”ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ •ํ™•ํ•œ ๊ฒ€์ฆ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์ฐธ๊ณ  : Spring Security - PasswordEncoder

profile
๋…ธ๋ ฅ์€ ๋ฐฐ์‹ ํ•˜์ง€ ์•Š์•„ ๐Ÿ”ฅ

0๊ฐœ์˜ ๋Œ“๊ธ€