Backend๐Ÿ–ฅ๏ธ/Javaโ˜•(Spring๐Ÿƒ)

[Spring] JWT ์ด์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๋งŒ๋“ค๊ธฐ

JanuDev 2025. 12. 31. 16:30

JWT = ์–‘ํŒŒ

์ž๊พธ ๊ณต๋ถ€ํ•˜๋ฉด ๊ณต๋ถ€ํ• ์ˆ˜๋ก ๋ญ” ์–‘ํŒŒ๋งˆ๋ƒฅ ์•Œ์•„์•ผ ํ• ๊ฒŒ ์ž๊พธ ๋‚˜์˜จ๋‹ค.. ๋ฏธ์ณ๋ฒ„๋ฆด์ง€๊ฒฝ

์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ• ๊ฑฐ๋ƒ๋ฉด

  1. ์‚ฌ์šฉ์ž ๊ฒ€์ฆ UserDetailsService
  2. ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต PasswordEncoder
  3. Authentication ๊ฐ์ฒด ์ƒ์„ฑ
  4. JWT ๋ฐœ๊ธ‰
  5. ํด๋ผ์ด์–ธํŠธ์— ํ† ํฐ ์ „๋‹ฌ
  6. Filter ๊ตฌํ˜„

์ˆœ์„œ๋Œ€๋กœ ์ž‘์„ฑํ•  ์˜ˆ์ •์ด๋‹ค.

์ž‘์„ฑ ์ˆœ์„œ

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthService {

    private final AuthenticateHandler authenticateHandler;
    private final JwtProvider jwtProvider;
    private final UserDetailsServiceImpl userDetailsServiceImpl;

    public TokenDTO login(LoginRequestDTO request) {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        /*
        * (1) ์‚ฌ์šฉ์ž ๊ฒ€์ฆ(UserDetailsService)
        * */
        UserDetailsDTO userDetails = (UserDetailsDTO) userDetailsServiceImpl.loadUserByUsername(request.getUserId());

        /*
        * (2) ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต(PasswordEncoder)
        * */
        if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
            throw new BadCredentialsException("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„!(T_T)");
        }

        /*
        * (3) Authentication ๊ฐ์ฒด ์ƒ์„ฑ
        * */
        Authentication authentication = authenticateHandler.authenticate(request);

        // (4) JWT ๋ฐœ๊ธ‰
        String accessToken = jwtProvider.createAccessToken(authentication);

        // (5) ํด๋ผ์ด์–ธํŠธ์— ํ† ํฐ ์ „๋‹ฌ
        return new TokenDTO(accessToken);
    }

}

1. ์‚ฌ์šฉ์ž ๊ฒ€์ฆ

DB์—์„œ ์œ ์ € ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๊ณ  UserDetails ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค. ex) ์ด username์„ ๊ฐ€์ง„ ์œ ์ €๊ฐ€ ์žˆ๋‚˜์š”? → UserDetailsImpl ๋งŒ๋“ค์–ด์„œ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์— ์ œ๊ณตํ•œ๋‹ค. 

๋กœ๊ทธ์ธ์€ 2๋‹จ๊ณ„๋กœ ์ด๋ฃจ์–ด์ง€๋Š”๋ฐ, ์ฒซ๋ฒˆ์งธ๋Š” ์‚ฌ์šฉ์ž ๊ฒ€์ฆ(Authentication ์ค€๋น„), ๋‘๋ฒˆ์งธ๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ(Authentication ์™„๋ฃŒ) ๋‹จ๊ณ„์ด๋‹ค. 

์‚ฌ์šฉ์ž ๊ฒ€์ฆ์€ "๋„ˆ๊ฐ€ ๋ˆ„๊ตฐ์ง€", ์ฆ‰ ์ด userId๋ฅผ ๊ฐ€์ง„ ์‚ฌ๋žŒ์ด ์‹ค์ œ๋กœ ์กด์žฌํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  DB์— ๋“ฑ๋ก๋œ ์‚ฌ์šฉ์ž์ธ์ง€ ๊ฒ€์ฆ์„ ํ•œ๋‹ค.

๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ์€ "์ง„์งœ ๋„ˆ๊ฐ€ ๋งž๋Š”์ง€"๋ฅผ ํ™•์ธํ•˜๋Š” ๋‹จ๊ณ„์ด๋‹ค.

์ด 2๋‹จ๊ณ„๊ฐ€ ์„ž์ด๋ฉด ์œ„ํ—˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹œํ๋ฆฌํ‹ฐ๋Š” ์ผ๋ถ€๋Ÿฌ ๋‚˜๋ˆ ๋†จ๋‹ค.

์—ญํ•  ์ฑ…์ž„
UserDetailsService ์ด ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€
UserDetails ์ด ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋Š” ๋ฌด์—‡์ธ์ง€
AuthenticationProvider ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งž๋Š”์ง€
SecurityContext ๋กœ๊ทธ์ธ ์™„๋ฃŒ ํ›„ ์ƒํƒœ ์ €์žฅ

 

๊ทธ๋ฆฌํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ฒ€์ฆ ๋‹จ๊ณ„์—์„ 

 

  • โŒ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์ฒ˜๋ฆฌ ์•ˆ ํ•จ
  • โŒ ์„ธ์…˜/JWT ์•ˆ ๋งŒ๋“ฆ
  • โญ• “์ด ์‚ฌ๋žŒ์ด ๋ˆ„๊ตฌ์ธ์ง€ ์„ค๋ช…์„œ๋งŒ ์ „๋‹ฌ”

๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋œ๋‹ค.

์ฝ”๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์€ ๋„ˆ๋ฌด ๊ธธ์–ด์ ธ์„œ ๋”ฐ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค.

2. ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต

์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ–ˆ์„ ๋•Œ DB์˜ ์•”ํ˜ธ๊ฐ’๊ณ  ๋™์ผํ•œ์ง€ ํ™•์ธํ•œ๋‹ค.

์‚ฌ์‹ค ์ด ๊ณผ์ •์€ Authentication ๊ณผ์ •์ด ์žˆ์œผ๋ฉด ๊ตณ์ด ํ•„์š”ํ•˜์ง„ ์•Š๋‹ค๊ณ  ํ•˜๋Š”๋ฐ.. ๊ทธ๋ƒฅ ๊ณต๋ถ€์ฐจ ์ž‘์„ฑํ•œ๋‹ค.

PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
  throw new BadCredentialsException("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„!");
}

(1) PasswordEncoder 

๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์•”ํ˜ธํ™”ํ•˜๊ณ , ์ž…๋ ฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์ €์žฅ๋œ ์•”ํ˜ธํšŒ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋น„๊ตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค

public interface PasswordEncoder {
	
ใ€€ใ€€// ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธํ™”
ใ€€ใ€€String encode(CharSequence rawPassword);
	
ใ€€ใ€€// ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€ ๋น„๋ฐ€๋ฒˆํ˜ธ(raw-)์™€ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ(encoded-)๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ๋น„๊ต
ใ€€ใ€€boolean matches(CharSequence rawPassword, String encodedPassword);
	
ใ€€ใ€€// ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ์•”ํ˜ธํ™”ํ•˜๊ณ ์ž ํ•  ๊ฒฝ์šฐ true๋ฅผ returnํ•˜๊ฒŒ ์„ค์ •
ใ€€ใ€€default boolean upgradeEncoding(String encodedPassword) { return false; };
}

์ด๋Ÿฐ์‹์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๊ณ , PasswordEncoder ๊ตฌํ˜„ ํด๋ž˜์Šค๋Š” 4๊ฐœ๊ฐ€ ์žˆ๋‹ค.

  • BcryptPasswordEncoder : BCrypt ํ•ด์‹œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”
  • Argon2PasswordEncoder: Argon2 ํ•ด์‹œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”
  • Pbkdf2PasswordEncoder : PBKDF2 ํ•ด์‹œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”
  • SCryptPasswordEncoder : SCrypt ํ•ด์‹œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”

์—ฌ๊ธฐ์„œ BCryptPasswordEncoder๋ฅผ ์ด์šฉํ–ˆ๋‹ค. ๋‹จ์ˆœํ•œ ํ•ด์‹ฑ์ด ์•„๋‹Œ ์†”ํŠธ(Salt)๋ผ๋Š” ๋ Œ๋ค ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณด์•ˆ์„ฑ์„ ๋†’์ธ๋‹ค. ๋™์ผํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋„ ์•”ํ˜ธํ™”์— ๋”ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ’์ด ๋‹ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์— ์›๋ž˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•Œ์•„๋‚ด๊ธฐ ๋งค์šฐ ์–ด๋ ต๋‹ค. 

 

(2) matches(ํ‰๋ฌธ, ์•”ํ˜ธ๋ฌธ)

์ €์žฅ๋œ ์•”ํ˜ธ๋ฌธ(userDetails.getPassword())์—์„œ ์‚ฌ์šฉ๋ฌ๋˜ ์†”ํŠธ(Salt)๊ฐ’์„ ์ถ”์ถœํ•œ๋‹ค. ์ถ”์ถœ๋œ ์†”ํŠธ์™€ ์ž…๋ ฅ๋ฐ›์€ ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋‹ค์‹œ ํ•ด์‹ฑํ•œ ํ›„, ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ ์ €์žฅ๋œ ์•”ํ˜ธ๋ฌธ๊ณผ ๊ฐ™์€์ง€ ํ™•์ธํ•œ๋‹ค.

 

(3) BadCredentialsException

Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ํ‘œ์ค€ ์˜ˆ์™ธ๋กœ, ์ž๊ฒฉ์ฆ๋ช…(id/pw)์ด ์œ ํšจํ•˜์ง€ ์•Š์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค. ์‚ฌ์šฉ์ž์˜ id์— ํ•ด๋‹นํ•˜๋Š” ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๋•Œ, ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์„ ๋•Œ ๋“ฑ๋“ฑ!

์ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ณดํ†ต Spring Security์˜ ์ธ์ฆ ํ•„ํ„ฐ๊ฐ€ ์ด๋ฅผ ๊ฐ€๋กœ์ฑ„์„œ ๋กœ๊ทธ์ธ ์‹คํŒจ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๊ฑฐ๋‚˜, 401 unauthorized ์—๋Ÿฌ ์‘๋‹ต์„ ๋ณด๋‚ด๊ฒŒ ๋œ๋‹ค

 

3. Authentication ๊ฐ์ฒด ์ƒ์„ฑ

private final AuthenticateHandler authenticateHandler;
Authentication authentication = authenticateHandler.authenticate(request);

Authentication ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ณผ์ •์€ "์ด ์‚ฌ์šฉ์ž๊ฐ€ ์ •๋ง ์šฐ๋ฆฌ ํšŒ์›์ด ๋งž๋Š”์ง€ ์‹œ์Šคํ…œ์ ์œผ๋กœ ์ตœ์ข… ์Šน์ธ ๋„์žฅ์„ ์ฐ๋Š” ๊ณผ์ •"์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

 

Authentication ๊ฐ์ฒด๋ž€?

Spring Security์—์„œ Authentication์€ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์‹ ์› ๋ณด์ฆ์„œ์ด๋‹ค.

  • Principal: ์‚ฌ์šฉ์ž์˜ ์•„์ด๋”” ํ˜น์€ ์‚ฌ์šฉ์ž ๊ฐ์ฒด
  • Credentials: ๋น„๋ฐ€๋ฒˆํ˜ธ(์ธ์ฆ์— ์‚ฌ์šฉ๋œ ์ฆ๊ฑฐ)
  • Authorities: ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์ง„ ๊ถŒํ•œ
  • Authenticated: ์ธ์ฆ ์—ฌ๋ถ€(T/F)

(1) AuthenticationManager ์ˆ˜๋™ ์ƒ์„ฑ

@Bean
public AuthenticationManager authenticationManager() throws Exception {
  return authenticationConfiguration.getAuthenticationManager();
}

AuthenticationManager์€ "์ธ์ฆ ์„ผํ„ฐ์˜ ์ด์ฑ…์ž„์ž"์ด๋‹ค. ๋ง ๊ทธ๋Œ€๋กœ ์ธ์ฆ(Authentication)์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ด€๋ฆฌ์ž์ธ๋ฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•˜๋ฉด ๊ทธ ์ •๋ณด๊ฐ€ ๋งž๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

 

์˜ˆ์ „ Spring Security๋Š” WebSecurityConfigurerAdapter์ด๋ผ๋Š”๊ฑธ ์ƒ์†๋ฐ›์œผ๋ฉด ์ž๋™์œผ๋กœ AuthenticationManager์„ ์ƒ์„ฑํ•ด์คฌ๋Š”๋ฐ, ์š”์ฆ˜์€(5.7๋ฒ„์ „ ์ด์ƒ)์€ ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ์„ค์ •์œผ๋กœ ๋ฐ”๋€Œ๋ฉด์„œ ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ์ž๋™ Bean ์ƒ์„ฑ์€ ์•ˆํ•ด์ฃผ๊ณ  ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ช…์‹œํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.............

 

๊ทธ๋ž˜์„œ Bean์œผ๋กœ ๋“ฑ๋ก์„ ํ•ด์คฌ๋‹ค. 

  • AuthenticationConfiguration : ์Šคํ”„๋ง์ด ๋‚ด๋ถ€์ ์œผ๋กœ ๋งŒ๋“ค์–ด๋‘” ์ธ์ฆ ์„ค์ • ์ •๋ณด
  • .getAuthenticationManager() : ์ด๋ฏธ ์„ค์ •๋œ ์ •๋ณด(UserDetailsService, PasswordEncoder ๋“ฑ)๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์™„์„ฑํ•œ "์ด์ฑ…์ž„์ž(Manager)"๋ฅผ ๊บผ๋‚ด์˜ค๋Š” ๊ณผ์ •

(2) AuthenticationHandler 

@Service  
@Slf4j
@RequiredArgsConstructor
public class AuthenticateHandler {

    private final AuthenticationManager authenticationManager;

    public Authentication authenticate (LoginRequestDTO request) {
        try {
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            request.getUserId(),
                            request.getPassword()
                    )
            );
        } catch (BadCredentialsException e) {
            log.warn("์ธ์ฆ ์‹คํŒจ - ์‚ฌ์šฉ์ž: {} (์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ)", request.getUserId());
            throw e;
        } catch (UsernameNotFoundException e) {
            log.warn("์ธ์ฆ ์‹คํŒจ - ์‚ฌ์šฉ์ž: {} (์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ณ„์ •)", request.getUserId());
            throw e;
        } catch (Exception e) {
            log.error("์ธ์ฆ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ - ์‚ฌ์šฉ์ž: {}", request.getUserId(), e);
            throw e;
        }

    } // authenticate

}

1๏ธโƒฃ UsernamePasswordAuthenticationToken() ์ƒ์„ฑ

์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋‚ธ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ด์€ ์ธ์ฆ ์š”์ฒญ์šฉ ํ† ํฐ์„ ๋จผ์ € ๋งŒ๋“ ๋‹ค.

์ด๋•Œ๋Š” ์•„์ง ๊ฒ€์ฆ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— setAuthenticated(false) ์ƒํƒœ์ธ ์ผ์ข…์˜ ๋กœ๊ทธ์ธ ์‹ ์ฒญ์„œ์™€๊ฐ™๋‹ค. 

 

2๏ธโƒฃ AuthenticationManager.authenticate() ํ˜ธ์ถœ

์ด "์‹ ์ฒญ์„œ"๋ฅผ AuthenticationManager์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค.

๊ด€๋ฆฌ์ž๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๋“ฑ๋ก๋œ AuthenticationProvider๋“ค์„ ์ฐพ์•„ ์ด ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ง„์งœ ๋งž๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

 

3๏ธโƒฃ ์ตœ์ข… Authentication ๊ฐ์ฒด ๋ฐ˜ํ™˜(์ธ์ฆ ์™„๋ฃŒ)

๋‚ด๋ถ€ ๊ฒ€์ฆ(๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต ๋“ฑ)์ด ์„ฑ๊ณตํ•˜๋ฉด, ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ(Roles)๊ณผ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ์ƒˆ๋กœ์šด Authentication ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด ๊ฐ์ฒด๋Š” setAuthenticatted(true)์ƒํƒœ์ด๋ฉฐ, ์ด์ œ ์‹œ์Šคํ…œ ์–ด๋””์„œ๋“  "์ด ์‚ฌ๋žŒ์€ ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž๋‹ค" ๋ผ๊ณ  ์ธ์ •๋ฐ›๊ฒŒ ๋œ๋‹ค.

 

4. JWT ๋ฐœ๊ธ‰

@Component
public class JwtProvider {

    private Long ACCESS_TOKEN;

    private Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);

    public String createAccessToken (Authentication authentication) {

        UserDetailsDTO principal = (UserDetailsDTO) authentication.getPrincipal();
        String userId = principal.getUserId();
        List<String> roles = principal.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();

        Date now = new Date();
        Date expiry = new Date(now.getTime() + ACCESS_TOKEN);

        return Jwts.builder()
                .setSubject(userId)
                .claim("role", roles)
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();

    }

}

createAccessToken : ์ƒˆ๋กœ์šด ํ† ํฐ์„ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์†Œ๋“œ

  • ACCESS_TOKEN : application.yml ํ˜น์€ properties์—์„œ ์„ค์ •๋œ ๋งŒ๋ฃŒ์‹œ๊ฐ„ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค
  • secretKey : ์„œ๋ช…(Signature)์„ ์œ„ํ•œ ํ‚ค. HS256์•Œ๊ณ ๋ฆฌ์ฆ˜์— ์ ํ•ฉํ•œ ์•ˆ์ „ ํ‚ค๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑ. 
  • Keys.secretKeyFor๋Š” ์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋  ๋•Œ ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ํ‚ค๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

(1) ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”์ถœ(Payload ์ค€๋น„)

  • authentication.getPrincipal() : ์•ž์„  ๋‹จ๊ณ„์—์„œ ์ƒ์„ฑํ–ˆ๋˜ ์ธ์ฆ ๊ฐ์ฒด์—์„œ ์‚ฌ์šฉ์ž ์ƒ์„ธ์ •๋ณด๋ฅผ ๊บผ๋ƒ„(UserDetailsDTO). ํ† ํฐ ๋‚ด๋ถ€์— ์ €์žฅํ•  ์‚ฌ์šฉ์ž ID์™€ ๊ถŒํ•œ๋ชฉ๋ก์„ ์ค€๋น„

(2) JWT ํ† ํฐ ์ƒ์„ฑ

  • setSubject : ํ† ํฐ์˜ ์‹๋ณ„์ž. ๋ณดํ†ต ์‚ฌ์šฉ์ž ๊ณ ์œ  ์•„์ด๋””๋ฅผ ๋„ฃ์Œ
  • claim("role", roles) : ํ‘œ์ค€ ํ•„๋“œ ์™ธ์— ๊ฐœ๋ฐœ์ž๊ฐ€ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์€ ์ •๋ณด๋ฅผ ๋„ฃ์Œ
  • setIssuedAt(now) : ํ† ํฐ ๋ฐœํ–‰ ์‹œ๊ฐ„
  • setExpiration(expiry) : ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„
  • signWith(secretKey, SignatureAlgorithm.HS256) : ์•”ํ˜ธํ™” ์„œ๋ช…. ์ค€๋น„ํ•œ secretKey์™€ HS256์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ด ํ† ํฐ์ด ์œ„์กฐ๋˜์ง€ ์•Š์Œ์„ ์ฆ๋ช…ํ•˜๋Š” ์„œ๋ช…. ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ณด์•ˆ ๋‹จ๊ณ„
  • compact() : ๋ชจ๋“  ์„ค์ •์„ ๋งˆ์น˜๊ณ  ์ตœ์ข…์ ์œผ๋กœ header.payload.signature ํ˜•ํƒœ์˜ ๊ธด ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜

5. ํด๋ผ์ด์–ธํŠธ์— ํ† ํฐ ์ „๋‹ฌ

return new TokenDTO(accessToken);

6. Filter ๊ตฌํ˜„

์ด๊ฒƒ๋„ ์„ค๋ช… ๊ธธ์–ด์งˆ ๊ฒƒ ๊ฐ™์•„์„œ ๋”ฐ๋กœ ๊ฒŒ์‹œ๊ธ€ ๋งŒ๋“ค์—ˆ๋‹ค.

๋งŒ๋‚ฌ๋˜ ์˜ค๋ฅ˜ ์‚ฌํ•ญ

1. java.lang.StackOverflowError

๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ์ด ๋„ˆ๋ฌด ๊นŠ์–ด์ ธ์„œ ์Šคํƒ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ฐ€๋“ ์ฐผ์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ด๋‹ค.

at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.1.10.jar:6.1.10]
	at jdk.proxy3/jdk.proxy3.$Proxy69.authenticate(Unknown Source) ~[na:na]
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]

์ด๊ฒŒ ๋ฌดํ•œ๋ฐ˜๋ณต๋˜๋Š”๊ฑธ ํ™•์ธํ–ˆ๋‹ค.

AuthenticateHandler์™€ ๊ฐ™์ด Authentication๊ณผ ๊ด€๋ จ๋œ ๊ณผ์ •์—์„œ ๋ฌด์–ธ๊ฐ€๊ฐ€ ๋ฌดํ•œ๋ฐ˜๋ณตํ•ด์„œ ํ˜ธ์ถœํ•˜๋Š”๊ฑฐ๋ผ๋Š”๊ฑด ์–ด๋ฆผ์ง์ž‘ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. 

Spring AOP ํ”„๋ก์‹œ์™€ ๊ด€๋ จ๋œ ์žฌ๊ท€๋ฌธ์ œ๋ผ๊ณ  ํ•œ๋‹ค.

๋”๋ณด๊ธฐ

GPT์˜ ์ถ”๊ฐ€์„ค๋ช… ๐Ÿ‘ฝ 

 

1. ํ”„๋ก์‹œ ๊ฐ์ฒด ($Proxy69)

  • $Proxy69.authenticate๋Š” @Service๋‚˜ @Component์™€ ๊ฐ™์€ Spring Bean์— ํŠธ๋žœ์žญ์…˜(@Transactional)์ด๋‚˜ ๋ณด์•ˆ(AOP)๊ณผ ๊ฐ™์€ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ์ ์šฉ๋  ๋•Œ, **Spring์ด ์›๋ณธ ๊ฐ์ฒด ๋Œ€์‹  ์ƒ์„ฑํ•˜์—ฌ ์ฃผ์ž…ํ•˜๋Š” ๋Œ€๋ฆฌ ๊ฐ์ฒด(Proxy)**์ž…๋‹ˆ๋‹ค.
  • ๋‹น์‹ ์˜ ์ฝ”๋“œ์—์„œ๋Š” AuthenticationManager ๋นˆ์ด ์ด ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ์ฃผ์ž…๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

2. ํ”„๋ก์‹œ์˜ invoke

  • JdkDynamicAopProxy.invoke๋Š” ์ด ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ(์—ฌ๊ธฐ์„œ๋Š” authenticate)๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ํ•ญ์ƒ ๋จผ์ € ์‹คํ–‰๋˜๋Š” Spring AOP์˜ ํ•ต์‹ฌ ๋กœ์ง์ž…๋‹ˆ๋‹ค. ์ด ๋กœ์ง์€ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์„ ์ฒ˜๋ฆฌํ•œ ํ›„ ์›๋ณธ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

3. ๋ฌดํ•œ ์žฌ๊ท€ (Stack Overflow)

StackOverflowError๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋Š” ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ˆœํ™˜ ํ˜ธ์ถœ ๊ตฌ์กฐ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

  1. ์‹œ์ž‘: AuthService (๋˜๋Š” ์™ธ๋ถ€) $\rightarrow$ ํ”„๋ก์‹œ AuthenticationManager์˜ authenticate() ํ˜ธ์ถœ
  2. ํ”„๋ก์‹œ: JdkDynamicAopProxy.invoke ์‹คํ–‰
  3. ๋ฌธ์ œ: invoke ๋กœ์ง์ด **์›๋ณธ AuthenticationManager**๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹ , ์–ด๋–ค ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด ๋‹ค์‹œ 1๋ฒˆ ๋‹จ๊ณ„๋กœ ๋Œ์•„๊ฐ€ ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ authenticate()๋ฅผ ์žฌํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌดํ•œ ๋ฃจํ”„์˜ ์›์ธ์€ ์•ž์„  ๋‹ต๋ณ€์—์„œ ์„ค๋ช…๋“œ๋ ธ๋“ฏ์ด, AuthenticationManager ๋นˆ์„ ์ •์˜ํ•˜๋Š” ๊ณผ์ •(SecurityConfig)๊ณผ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปค์Šคํ…€ ์ธ์ฆ ๋กœ์ง(AuthenticateHandler ๋˜๋Š” AuthService) ๊ฐ„์˜ ์˜์กด์„ฑ ์ฃผ์ž…(DI) ์ถฉ๋Œ์ด๋‚˜ ์„ค์ •์˜ ์ค‘๋ณต ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๊ฒฐ๋ก  : 

์˜ค๋ฅ˜ ๋ฐœ์ƒ ์ง€์ ์€ AuthenticationManager์˜ authenticate()์˜ ํ˜ธ์ถœ ๊ณผ์ •

์›์ธ์€ Spring Security์˜ ์„ค์ •๊ณผ ์ปค์Šคํ…€ ๋กœ์ง ๊ฐ„์˜ ์ˆœํ™˜ ์ฐธ์กฐ(Circular Dependency)

  • DB์—์„œ ์ง์ ‘ ์‚ฌ์šฉ์ž์ •๋ณด ์กฐํšŒ(getUserDetails)
  • ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ง์ ‘ ๋น„๊ต(passwordEncoder.matches)
  • authenticatHandler.authenticate(request)๋ฅผ ํ†ตํ•ด ๋‹ค์‹œ ์ธ์ฆ ์‹œ๋„
  • ์ฆ‰, ๋กœ๊ทธ์ธ ๋กœ์ง ์•ˆ์—์„œ Spring Security์˜ ์ธ์ฆ์„ '์ˆ˜๋™' + '์ž๋™'์œผ๋กœ ๋™์‹œ์— ๋Œ๋ฆฌ๋ฉด์„œ ์ธ์ฆ ๊ณผ์ •์ด ์ž๊ธฐ ์ž์‹ ์„ ๊ณ„์† ํ˜ธ์ถœํ•˜์—ฌ stackOverflow ๋ฐœ์ƒ (๊ธธ์–ด์ ธ์„œ ์ ‘์€๊ธ€ ์ž‘์„ฑ)
๋”๋ณด๊ธฐ

authenticateHandler.authenticate() ์•ˆ์—์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚ฌ๋‚˜?

AuthenticateHandler๋Š” ๋‚ด๋ถ€์—์„œ ๋ณดํ†ต ์ด๋Ÿฐ ๊ฑธ ํ•˜๊ณ  ์žˆ์–ด:

 
authenticationManager.authenticate(authenticationToken);

 ์ฆ‰,๐Ÿ‘‰ Spring Security์˜ ํ‘œ์ค€ ์ธ์ฆ ํ”„๋กœ์„ธ์Šค(AuthenticationManager) ๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•จ


Spring Security๋Š” ์ด ์ธ์ฆ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋‚˜?

Spring Security๋Š” ์ธ์ฆ์„ ์‹œ์ž‘ํ•˜๋ฉด:

  1. AuthenticationManager ์‹คํ–‰
  2. AuthenticationProvider ํ˜ธ์ถœ
  3. UserDetailsService.loadUserByUsername() ํ˜ธ์ถœ
  4. (AOP / Filter / Security ์„ค์ •์— ๋”ฐ๋ผ)
  5. ์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ๋กœ์ง(AuthService.login())์„ ๋‹ค์‹œ ํƒ€๊ฒŒ ๋จ

๊ทธ๋ž˜์„œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด์กŒ๋‚˜? (๋ฌดํ•œ ๋ฃจํ”„)

AuthService.login()
 โ”” authenticateHandler.authenticate()
     โ”” AuthenticationManager.authenticate()
         โ”” Spring Security ์ธ์ฆ ์‹œ์ž‘
             โ”” AuthService.login() ๋‹ค์‹œ ํ˜ธ์ถœ
                 โ”” authenticateHandler.authenticate()
                     โ”” AuthenticationManager.authenticate()
                         โ”” ...

๐ŸŽข ๋์—†๋Š” ํšŒ์ „๋ชฉ๋งˆ
โžก๏ธ StackOverflowError ๋ฐœ์ƒ

 

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„  AuthenticationManager์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ์ด๋ฏธ ์ฝ”๋“œ์—์„  ์ง์ ‘ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ์„ ํ•˜๊ณ  ์žˆ๊ธฐ๋•Œ๋ฌธ์— ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋ผ๊ณ  ํ•˜๋Š”๋ฐ...

public class AuthService {

    private final JdbcTemplate jdbcTemplate;
    private final AuthenticateHandler authenticateHandler;
    private final JwtProvider jwtProvider;

    public TokenDTO login(LoginRequestDTO request) {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        UserDetailsImpl userDetails = getUserDetails(request);

      if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
           // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋‹ค๋ฉด
           throw new BadCredentialsException("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„!(T_T)");
        }
        Authentication authentication = authenticateHandler.authenticate(request);
        String accessToken = jwtProvider.createAccessToken(authentication);
        return new TokenDTO(accessToken);
    }

๊ทผ๋ฐ ๋‚œ ๊ณต๋ถ€์ค‘์ด๋ฏ€๋กœ AuthenticationManager์„ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ๋‹ค.

 

1๏ธโƒฃ UserDetailsService ๊ตฌํ˜„ ํด๋ž˜์Šค ์ƒ์„ฑ

@Service
@RequiredArgsConstructor
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

    public final JdbcTemplate jdbcTemplate;

    @Override
    public UserDetails loadUserByUsername(String userId) {
        String sql = "SELECT * FROM dalmuri.\"USER\" WHERE \"USER_ID\" = ?"; // ์—ฌ๊ธฐ ์—๋Ÿฌ๋Š” ๋ฌด์‹œ ๊ฐ€๋Šฅ
        UserDetailsImplDTO userImpl = new UserDetailsImplDTO();

        try {
            UserDTO user = jdbcTemplate.queryForObject(
                    sql,
                    new Object[]{userId},
                    (rs, rowNum) -> {   // Mapper
                        UserDTO u = new UserDTO();
                        u.setUserCd(rs.getString("USER_CD"));
                        u.setUserId(rs.getString("USER_ID"));
                        u.setUserNm(rs.getString("USER_NM"));
                        u.setPassword(rs.getString("PASSWORD"));
                        u.setBgColor(rs.getString("BG_COLOR"));
                        u.setTextColor(rs.getString("TEXT_COLOR"));
                        u.setInsertDatetime(rs.getString("INSERT_DATETIME"));
                        return u;
                    }
            );
            if (user == null) {
                throw new UsernameNotFoundException("์‚ฌ์šฉ์ž ์ •๋ณด ์—†์Œ: " + userId);
            }
            userImpl.setUserCd(user.getUserCd());
            userImpl.setUserId(user.getUserId());
            userImpl.setUserNm(user.getUserNm());
            userImpl.setPassword(user.getPassword());
            userImpl.setBgColor(user.getBgColor());
            userImpl.setTextColor(user.getTextColor());
            userImpl.setInsertDatetime(user.getInsertDatetime());
            return userImpl;
        } catch (Exception e) {
            e.printStackTrace();
            throw new UsernameNotFoundException("์‚ฌ์šฉ์ž ์ •๋ณด ์—†์Œ: " + userId);
        }
    }
}

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด AuthService.java์— ์žˆ๋˜ DBํ˜ธ์ถœ๋กœ์ง์€ ์‚ญ์ œ

//    private UserDetailsImpl getUserDetails (LoginRequestDTO request) {
//        String sql = "SELECT * FROM dalmuri.\"USER\" WHERE \"USER_ID\" = ?"; // ์—ฌ๊ธฐ ์—๋Ÿฌ๋Š” ๋ฌด์‹œ ๊ฐ€๋Šฅ
//
//        User user = jdbcTemplate.queryForObject(
//                sql,
//                new Object[]{request.getUserId()},
//                (rs, rowNum) -> {   // Mapper
//                    User u = new User();
//                    u.setUserCd(rs.getString("USER_CD"));
//                    u.setUserId(rs.getString("USER_ID"));
//                    u.setUserNm(rs.getString("USER_NM"));
//                    u.setPassword(rs.getString("PASSWORD"));
//                    u.setBgColor(rs.getString("BG_COLOR"));
//                    u.setTextColor(rs.getString("TEXT_COLOR"));
//                    u.setInsertDatetime(rs.getString("INSERT_DATETIME"));
//                    return u;
//                }
//        );
//
//        if (user == null) {
//            throw new UsernameNotFoundException("์‚ฌ์šฉ์ž ์ •๋ณด ์—†์Œ: " + request.getUserId());
//        }
//
//        return new UserDetailsImpl(user);
//    }

์™œ ์‚ญ์ œํ•˜๋ƒ๋ฉด, AuthService์—์„œ ๊ฐ™์ด ์ƒ์„ฑํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— getUserDetails ๋ฉ”์†Œ๋“œ๋Š” UserDetailsService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์ง€ ์•Š๋Š”๋‹ค(AuthService๊ฐ€ UserDetailsService๋ฅผ ์ƒ์†๋ฐ›์ง€ ์•Š์Œ)

 

2๏ธโƒฃ User์„ UserDetails๋กœ ๋ณ€ํ™˜

@Getter
@Setter
@ToString
public class UserDetailsDTO implements UserDetails {

    private String userCd;
    private String userId;
    private String userNm;
    private String password;

    private String bgColor;
    private String textColor;
    private String insertDatetime;

    /*
    * 1. Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด๋„ UserDetails์—์„œ ์š”๊ตฌํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
    * */

    private final String role = "ROLE_USER";

    @Override
    public String getUsername() {
        return userId;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // ๊ณ ์ • ๋ฐ˜ํ™˜
        return List.of(new SimpleGrantedAuthority(this.role));
    }

    /**
     * ํ˜•์‹์ ์ธ ์š”์†Œ๋“ค : true๋กœ ๊ณ ์ •
     * */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

๊ธฐ์กด์— ๋งŒ๋“ค์—ˆ๋˜ UserDTO

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {

    private String userCd;
    private String userId;
    private String userNm;
    private String password;

    // ์žˆ์œผ๋‚˜ ์—†์œผ๋‚˜
    private String bgColor;
    private String textColor;
    private String insertDatetime;

}

 

3๏ธโƒฃ AuthService.java ๋กœ์ง ์ˆ˜์ •

๊ธฐ์กด ๋กœ์ง

public class AuthService {

    private final JdbcTemplate jdbcTemplate;
    private final AuthenticateHandler authenticateHandler;
    private final JwtProvider jwtProvider;

    public TokenDTO login(LoginRequestDTO request) {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        UserDetailsImpl userDetails = getUserDetails(request);

        if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
            // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋‹ค๋ฉด
            throw new BadCredentialsException("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„!(T_T)");
        }

        Authentication authentication = authenticateHandler.authenticate(request);

        String accessToken = jwtProvider.createAccessToken(authentication);

        return new TokenDTO(accessToken);
    }

    private UserDetailsImpl getUserDetails (LoginRequestDTO request) {
        String sql = "SELECT * FROM dalmuri.\"USER\" WHERE \"USER_ID\" = ?"; // ์—ฌ๊ธฐ ์—๋Ÿฌ๋Š” ๋ฌด์‹œ ๊ฐ€๋Šฅ
        User user = jdbcTemplate.queryForObject(
                sql,
                new Object[]{request.getUserId()},
                (rs, rowNum) -> {   // Mapper
                    User u = new User();
                    u.setUserCd(rs.getString("USER_CD"));
                    u.setUserId(rs.getString("USER_ID"));
                    u.setUserNm(rs.getString("USER_NM"));
                    u.setPassword(rs.getString("PASSWORD"));
                    u.setBgColor(rs.getString("BG_COLOR"));
                    u.setTextColor(rs.getString("TEXT_COLOR"));
                    u.setInsertDatetime(rs.getString("INSERT_DATETIME"));
                    return u;
                }
        );

        if (user == null) {
            throw new UsernameNotFoundException("์‚ฌ์šฉ์ž ์ •๋ณด ์—†์Œ: " + request.getUserId());
        }

        return new UserDetailsImpl(user);
    }

}

์ƒˆ๋กœ ๊ณ ์นœ ๋กœ์ง

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthService {

    private final AuthenticateHandler authenticateHandler;
    private final JwtProvider jwtProvider;
    private final UserDetailsServiceImpl userDetailsServiceImpl;

    public TokenDTO login(LoginRequestDTO request) {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        UserDetailsDTO userDetails = (UserDetailsDTO) userDetailsServiceImpl.loadUserByUsername(request.getUserId());

        if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
            // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋‹ค๋ฉด
            throw new BadCredentialsException("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„!(T_T)");
        }

        Authentication authentication = authenticateHandler.authenticate(request);

        String accessToken = jwtProvider.createAccessToken(authentication);

        return new TokenDTO(accessToken);
    }

 

[์ฐธ๊ณ  ์ž๋ฃŒ]

https://velog.io/@corgi/Spring-Security-PasswordEncoder%EB%9E%80-4kkyw8gi

 

[Spring Security] PasswordEncoder๋ž€?

Spring Security 5.3.3 ๋ฒ„์ „์—์„œ ์ง€์›ํ•˜๋Š” PasswordEncoder์— ๋Œ€ํ•ด ๊ฐ„๋žตํžˆ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

velog.io

https://coding-start.tistory.com/153

 

Spring boot - Spring Security(์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ) ๋ž€? ์™„์ „ ํ•ด๊ฒฐ!

์˜ค๋Š˜ ํฌ์ŠคํŒ…ํ•  ๋‚ด์šฉ์€ Spring Security์ด๋‹ค. ์‚ฌ์‹ค ํ•„์ž๋Š” ๋จธ๋ฆฌ๊ฐ€ ๋‚˜๋น ์„œ ๊ทธ๋Ÿฐ์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ์•„๋ฌด๋ฆฌ ๊ตฌ๊ธ€๋ง์„ ํ†ตํ•ด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•ด๋„ ์ด๋ ‡๋‹คํ•  ๋ช…์พŒํ•œ ํ•ด๋‹ต์„ ์–ป์ง€ ๋ชปํ–ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„ ์ด๋ก ์ 

coding-start.tistory.com