๐Ÿ“Œ [JWT ์ธ์ฆ ์‹œ์Šคํ…œ ์‹œ๋ฆฌ์ฆˆ - 5ํŽธ] ํ† ํฐ ์žฌ๋ฐœ๊ธ‰๊ณผ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ ํ๋ฆ„

My Pale Blue Dotยท2025๋…„ 5์›” 21์ผ
0

SPRING BOOT

๋ชฉ๋ก ๋ณด๊ธฐ
40/40
post-thumbnail

๐Ÿ“… ๋‚ ์งœ

2025-05-20


๐Ÿ“ ํ•™์Šต ๋‚ด์šฉ

1๏ธโƒฃ JWT ์ธ์ฆ ์ „์ฒด ํ๋ฆ„ ์š”์•ฝ

๋‹จ๊ณ„์„ค๋ช…
๋กœ๊ทธ์ธ ์„ฑ๊ณตAT, RT ๋ฐœ๊ธ‰ โ†’ AT: ํด๋ผ์ด์–ธํŠธ ์ฟ ํ‚ค / RT: ์„œ๋ฒ„(DB) ์ €์žฅ
์ธ์ฆ ์š”์ฒญ์ฟ ํ‚ค์˜ AccessToken โ†’ JwtAuthorizationFilter์—์„œ ๊ฒ€์ฆ
AT ๋งŒ๋ฃŒRT ์œ ํšจ ์‹œ ์ƒˆ๋กœ์šด AT ์žฌ๋ฐœ๊ธ‰ (์ฟ ํ‚ค+DB ๊ฐฑ์‹ )
RT ๋งŒ๋ฃŒ์ฟ ํ‚ค ์‚ญ์ œ + DB ์‚ญ์ œ + ์žฌ๋กœ๊ทธ์ธ ํ•„์š”
๋กœ๊ทธ์•„์›ƒ์ฟ ํ‚ค ์‚ญ์ œ + DB์—์„œ AT ์‚ญ์ œ + ์†Œ์…œ ๋กœ๊ทธ์•„์›ƒ ์ง€์›

2๏ธโƒฃ CustomLoginSuccessHandler โ€“ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ์ „์ฒด ์‹œ๋‚˜๋ฆฌ์˜ค

@Slf4j
@Component
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Autowired
    private JwtTokenRepository jwtTokenRepository;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        TokenInfo tokenInfo = jwtTokenProvider.generateToken(authentication);

        // AccessToken โ†’ ์ฟ ํ‚ค
        Cookie cookie = new Cookie(JwtProperties.ACCESS_TOKEN_COOKIE_NAME, tokenInfo.getAccessToken());
        cookie.setMaxAge(JwtProperties.REFRESH_TOKEN_EXPIRATION_TIME);
        cookie.setPath("/");
        response.addCookie(cookie);

        // RT, AT ์ €์žฅ
        JwtToken jwtToken = JwtToken.builder()
                .accessToken(tokenInfo.getAccessToken())
                .refreshToken(tokenInfo.getRefreshToken())
                .username(authentication.getName())
                .createAt(LocalDateTime.now())
                .build();
        jwtTokenRepository.save(jwtToken);

        // ๋กœ๊ทธ์ธ ํ๋ฆ„ 
        /*
            [์ตœ์ดˆ ๋กœ๊ทธ์ธ] (Client: AT โŒ, DB โŒ)
            - AT ์ „์†ก, DB ์ €์žฅ

            [๊ธฐ์กด ๋กœ๊ทธ์ธ] (Client: AT โœ…, DB โœ…)
            - AT ์œ ํšจ โ†’ ๋กœ๊ทธ์ธ ์™„๋ฃŒ
            - AT ๋งŒ๋ฃŒ & RT ์—†์Œ โ†’ ์žฌ๋กœ๊ทธ์ธ
            - AT ๋งŒ๋ฃŒ & RT ์œ ํšจ โ†’ AT ์žฌ๋ฐœ๊ธ‰
            - AT ๋งŒ๋ฃŒ & RT ๋งŒ๋ฃŒ โ†’ AT, RT ์žฌ๋ฐœ๊ธ‰

            [์˜ˆ์™ธ ์ƒํ™ฉ]
            - Client: AT โœ…, DB โŒ โ†’ ๋น„์ •์ƒ
            - Client: AT โŒ, DB โœ… โ†’ ์ฟ ํ‚ค ์‚ญ์ œ๋จ
         */
        response.sendRedirect(request.getContextPath() + "/");
    }
}

3๏ธโƒฃ AccessToken ๋งŒ๋ฃŒ โ†’ RefreshToken ์žฌ๋ฐœ๊ธ‰ (JwtAuthorizationFilter)

if (jwtTokenProvider.validateToken(refreshToken)) {
    // AT ์žฌ๋ฐœ๊ธ‰
    String newAccessToken = ...
    response.addCookie(new Cookie(..., newAccessToken));
    jwtToken.setAccessToken(newAccessToken);
    jwtTokenRepository.save(jwtToken);
} else {
    // RT๋„ ๋งŒ๋ฃŒ๋จ โ†’ ์ฟ ํ‚ค ์ œ๊ฑฐ + DB ์‚ญ์ œ
}

๐Ÿ” AT ์žฌ๋ฐœ๊ธ‰ ํ๋ฆ„ ๋„์‹:

[์š”์ฒญ] โ†’ [AT ๋งŒ๋ฃŒ ํ™•์ธ]
         โ†“
     [RT ์กด์žฌ?] โ†’ โŒ ์žฌ๋กœ๊ทธ์ธ
         โ†“
    [RT ์œ ํšจ?] โ†’ โŒ ์žฌ๋กœ๊ทธ์ธ
         โ†“
  โœ… โ†’ ์ƒˆ AT ๋ฐœ๊ธ‰ โ†’ ์ฟ ํ‚ค ๊ฐฑ์‹  + DB ์—…๋ฐ์ดํŠธ

4๏ธโƒฃ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ (CustomLogoutSuccessHandler)

๋‹จ๊ณ„์„ค๋ช…
1๏ธโƒฃ AccessToken ์ฟ ํ‚ค์—์„œ ์ถ”์ถœ
2๏ธโƒฃ ํ•ด๋‹น AT DB์—์„œ ์‚ญ์ œ (deleteByAccessToken)
3๏ธโƒฃ ํด๋ผ์ด์–ธํŠธ ์ฟ ํ‚ค ์‚ญ์ œ (MaxAge=0)
4๏ธโƒฃ ์†Œ์…œ ๋กœ๊ทธ์ธ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ (Kakao, Naver, Google)
5๏ธโƒฃ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™

๐Ÿ” ๋ณด์•ˆ ํŒ โ€“ ์ฟ ํ‚ค ์„ค์ •

์„ค์ • ํ•ญ๋ชฉ์„ค๋ช…
HttpOnlyJavaScript ์ ‘๊ทผ ์ฐจ๋‹จ (XSS ๋ฐฉ์ง€)
SecureHTTPS์—์„œ๋งŒ ์ „์†ก
SameSite=LaxCSRF ๋ฐฉ์–ด (ํ˜น์€ Strict)
Path="/"๋ฃจํŠธ ์ „์ฒด์—์„œ ์ฟ ํ‚ค ์ ์šฉ
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");

๐Ÿงช ํ…Œ์ŠคํŠธ ํŒ

  • Postman์—์„œ AT ์—†์ด ์š”์ฒญ โ†’ 401 ์‘๋‹ต ํ™•์ธ
  • ๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ > Application > Cookie ํƒญ์—์„œ ์ž๋™ ๊ฐฑ์‹  ์—ฌ๋ถ€ ํ™•์ธ
  • ๋กœ๊ทธ์•„์›ƒ ํ›„ ์ฟ ํ‚ค ๋ฐ DB ์‚ญ์ œ ์—ฌ๋ถ€ ์ฝ˜์†” ํ™•์ธ

๐Ÿงฉ ์˜ˆ์™ธ ์ƒํ™ฉ ์š”์•ฝ

์ƒํ™ฉ์„ค๋ช…์ฒ˜๋ฆฌ
AT โŒ / RT โŒ์ตœ์ดˆ ๋กœ๊ทธ์ธAT ๋ฐœ๊ธ‰ + ์ €์žฅ
AT โœ… / RT โœ…์ •์ƒ ํ๋ฆ„๋กœ๊ทธ์ธ ์™„๋ฃŒ
AT โŒ / RT โœ…์ฟ ํ‚ค ์‚ญ์ œ๋จAT ์žฌ๋ฐœ๊ธ‰
AT โœ… / RT โŒ์˜ˆ์™ธ ํ๋ฆ„๋น„์ •์ƒ ํ† ํฐ (๋กœ๊ทธ์•„์›ƒ ๊ถŒ์žฅ)
AT, RT ๋ชจ๋‘ ๋งŒ๋ฃŒ์„ธ์…˜ ์ข…๋ฃŒ์ฟ ํ‚ค/DB ์ œ๊ฑฐ, ์žฌ๋กœ๊ทธ์ธ ์œ ๋„

โœ… ์ด ๊ธ€์—์„œ ๋ฐฐ์šด ๊ฒƒ

  • โœ… JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ์—์„œ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰๊ณผ ๋กœ๊ทธ์•„์›ƒ ํ๋ฆ„์„ ์™„์„ฑ
  • โœ… RefreshToken ์ €์žฅ์œผ๋กœ ์žฌ๋กœ๊ทธ์ธ ์ตœ์†Œํ™”, ๋ณด์•ˆ ์ œ์–ด ๊ฐ€๋Šฅ
  • โœ… ์˜ˆ์™ธ ์ƒํ™ฉ์„ ์ •๋ฆฌํ•จ์œผ๋กœ์จ ์‹ค๋ฌด ๋Œ€์‘ ์„ค๊ณ„ ๋Šฅ๋ ฅ ํ–ฅ์ƒ

๐Ÿง  ๋А๋‚€ ์ 

  • ์ „์ฒด ์ธ์ฆ ํ๋ฆ„์„ ์ง์ ‘ ์„ค๊ณ„ํ•˜๋ฉฐ ์‹ค๋ฌด ์ˆ˜์ค€์˜ ์ดํ•ด๋„๊ฐ€ ํ™•์‹คํžˆ ์˜ฌ๋ผ๊ฐ”๋‹ค.
  • JWT ์ธ์ฆ์€ ๋‹จ์ˆœ ๋ฐœ๊ธ‰์ด ์•„๋‹Œ ์ƒํƒœ ๊ด€๋ฆฌ, ๋งŒ๋ฃŒ ๋Œ€์‘, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊นŒ์ง€ ์„ค๊ณ„๊ฐ€ ์ƒ๋ช…์ž„์„ ์ฒด๊ฐํ–ˆ๋‹ค.
  • ๋‹ค์Œ์œผ๋กœ๋Š” Redis ๊ธฐ๋ฐ˜ RefreshToken ์ €์žฅ๊ณผ /refresh API๋ฅผ RestController๋กœ ๋ถ„๋ฆฌํ•ด๋ณด๊ณ  ์‹ถ๋‹ค.

โœ… ์š”์•ฝ

  • JWT ์ธ์ฆ ์‹œ์Šคํ…œ์„ ๊ตฌ์„ฑํ•  ๋•Œ ๋ฐ˜๋“œ์‹œ ํ† ํฐ ๋ฐœ๊ธ‰ โ†’ ์ธ์ฆ โ†’ ์žฌ๋ฐœ๊ธ‰ โ†’ ๋กœ๊ทธ์•„์›ƒ๊นŒ์ง€ ์„ค๊ณ„ํ•ด์•ผ ํ•œ๋‹ค.
  • RefreshToken์€ ์„œ๋ฒ„์—์„œ ๊ด€๋ฆฌ๋˜๋ฉฐ, AccessToken ๋งŒ๋ฃŒ ์‹œ์˜ ๋ณด์•ˆ์„ฑ๊ณผ UX๋ฅผ ๋™์‹œ์— ๋ณด์žฅํ•œ๋‹ค.
  • ์™„์„ฑ๋œ ๊ตฌ์กฐ๋Š” ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์ธ์ฆ ์‹œ์Šคํ…œ์˜ ๊ธฐ๋ฐ˜์ด ๋œ๋‹ค.

profile
Here, My Pale Blue.๐ŸŒ

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