๐Ÿ“Œ [JWT ์ธ์ฆ ์‹œ์Šคํ…œ ์‹œ๋ฆฌ์ฆˆ - 4ํŽธ] AccessToken + RefreshToken ์ €์žฅ ๋ฐ ์žฌ๋ฐœ๊ธ‰ ํ๋ฆ„ + ์˜ˆ์™ธ ์ƒํ™ฉ ์ •๋ฆฌ

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

SPRING BOOT

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

๐Ÿ“… ๋‚ ์งœ

2025-05-20


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

1๏ธโƒฃ RefreshToken ์ €์žฅ์ด ํ•„์š”ํ•œ ์ด์œ 

AccessToken์€ ๋ณดํ†ต ์งง์€ ์ˆ˜๋ช…์„ ๊ฐ€์ง€๋ฉฐ, ํด๋ผ์ด์–ธํŠธ๋Š” ๋งŒ๋ฃŒ ์‹œ RefreshToken์„ ํ†ตํ•ด ๋‹ค์‹œ ์ธ์ฆํ•ด์•ผ ํ•œ๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— RefreshToken์„ ์„œ๋ฒ„์— ์ €์žฅํ•ด๋‘๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

โœ… ์„œ๋ฒ„(DB)์— RefreshToken์„ ์ €์žฅํ•˜๋ฉด:

  • ๐Ÿ›ก๏ธ ํƒˆ์ทจ ์—ฌ๋ถ€ ํ™•์ธ ๊ฐ€๋Šฅ: ๊ณต๊ฒฉ์ž๊ฐ€ ์ž„์˜๋กœ ๋งŒ๋“  RefreshToken์€ ์„œ๋ฒ„์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ
  • ๐Ÿ” ๋ถˆํ•„์š”ํ•œ ์žฌ๋กœ๊ทธ์ธ ๋ฐฉ์ง€: AccessToken๋งŒ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ, ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•  ํ•„์š” ์—†์ด ์ƒˆ๋กœ ๋ฐœ๊ธ‰ ๊ฐ€๋Šฅ
  • ๐Ÿ”’ ๋กœ๊ทธ์•„์›ƒ ์‹œ ๊ฐ•์ œ ๋งŒ๋ฃŒ ๊ฐ€๋Šฅ: ์„œ๋ฒ„์—์„œ ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ํ† ํฐ์„ ์ œ๊ฑฐํ•˜๋ฉด ์ž๋™ ๋กœ๊ทธ์•„์›ƒ ํšจ๊ณผ

๐Ÿ”— ์ด ํ๋ฆ„์„ ์œ„ํ•ด JwtToken ํ…Œ์ด๋ธ”์— AccessToken + RefreshToken์„ ํ•จ๊ป˜ ์ €์žฅํ•œ๋‹ค.


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

๋‹จ๊ณ„์„ค๋ช…
๋กœ๊ทธ์ธ ์„ฑ๊ณตAccessToken + RefreshToken ์ƒ์„ฑ
AccessToken โ†’ ํด๋ผ์ด์–ธํŠธ์ฟ ํ‚ค์— ์ €์žฅ (HttpOnly, Secure ์ ์šฉ ๊ฐ€๋Šฅ)
RefreshToken โ†’ ์„œ๋ฒ„DB(JwtToken Entity)์— ์ €์žฅ
์ธ์ฆ ์š”์ฒญํด๋ผ์ด์–ธํŠธ๊ฐ€ AccessToken์„ ํ—ค๋”๋กœ ์ „์†ก
AccessToken ๋งŒ๋ฃŒํด๋ผ์ด์–ธํŠธ๊ฐ€ RefreshToken๊ณผ ํ•จ๊ป˜ /refresh ์š”์ฒญ
RefreshToken ๊ฒ€์ฆ ์„ฑ๊ณต์ƒˆ๋กœ์šด AccessToken ๋ฐœ๊ธ‰ ๋ฐ ์ฟ ํ‚ค ๊ฐฑ์‹ 
RefreshToken ๋งŒ๋ฃŒ or ์—†์Œ์ „์ฒด ์žฌ๋กœ๊ทธ์ธ ์š”๊ตฌ

3๏ธโƒฃ JwtToken Entity ๊ตฌ์กฐ

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
public class JwtToken {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="accessToken", columnDefinition = "TEXT", nullable = false)
    private String accessToken;

    @Column(name="refreshToken", columnDefinition = "TEXT", nullable = false)
    private String refreshToken;

    @Column(name="username", nullable = false)
    private String username;

    @Column(name="createAt", nullable = false)
    private LocalDateTime createAt;
}

4๏ธโƒฃ JwtToken ์ €์žฅ ํ๋ฆ„ (๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ)

@Slf4j
@Component
public class CustomSuccessHandler 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);

        // AccessToken + RefreshToken โ†’ DB ์ €์žฅ
        JwtToken jwtToken = JwtToken.builder()
                .accessToken(tokenInfo.getAccessToken())
                .refreshToken(tokenInfo.getRefreshToken())
                .username(authentication.getName())
                .createAt(LocalDateTime.now())
                .build();
        jwtTokenRepository.save(jwtToken);

        response.sendRedirect(request.getContextPath() + "/");
    }
}

5๏ธโƒฃ AccessToken ๋งŒ๋ฃŒ ์‹œ ์ฒ˜๋ฆฌ ํ๋ฆ„ (Refresh ๊ธฐ๋ฐ˜ ์žฌ๋ฐœ๊ธ‰)

๐Ÿงญ ๊ธฐ๋ณธ ํ๋ฆ„

  1. ํด๋ผ์ด์–ธํŠธ๋Š” AccessToken์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Œ์„ ๊ฐ์ง€
  2. ์„œ๋ฒ„์— /refresh ์š”์ฒญ์„ ๋ณด๋ƒ„ (RefreshToken ํฌํ•จ)
  3. ์„œ๋ฒ„๋Š” DB์—์„œ ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ RefreshToken ์กฐํšŒ
  4. RefreshToken ์œ ํšจํ•˜๋ฉด ์ƒˆ๋กœ์šด AccessToken ๋ฐœ๊ธ‰ โ†’ ์ฟ ํ‚ค๋กœ ์ „๋‹ฌ
  5. ๊ธฐ์กด AccessToken์€ ํ๊ธฐํ•˜๊ฑฐ๋‚˜ ๊ทธ๋Œ€๋กœ ๋‘ 

๐Ÿงฉ ์˜ˆ์™ธ ์ƒํ™ฉ ์ •๋ฆฌ

์ƒํ™ฉ์„ค๋ช…์ฒ˜๋ฆฌ ์ „๋žต (์˜ˆ์ •)
โœ… ์ตœ์ดˆ ๋กœ๊ทธ์ธ(Client: AT โŒ / DB: โŒ)์ƒˆ๋กœ ๋ฐœ๊ธ‰ํ•ด์„œ ํด๋ผ์ด์–ธํŠธ ์ „๋‹ฌ + DB ์ €์žฅ์ •์ƒ ํ๋ฆ„
โœ… ๊ธฐ์กด ๋กœ๊ทธ์ธ(Client: AT โœ… / DB: โœ…)ํ† ํฐ ์œ ํšจ ์‹œ ๋ฌธ์ œ ์—†์Œ์ •์ƒ ํ๋ฆ„
๐Ÿ” AccessToken ๋งŒ๋ฃŒ / RefreshToken ์กด์žฌ/refresh ์š”์ฒญ ํ†ตํ•ด ์žฌ๋ฐœ๊ธ‰์ƒˆ๋กœ์šด AT ๋ฐœ๊ธ‰
โš ๏ธ AccessToken ๋งŒ๋ฃŒ / RefreshToken ์—†์Œ์žฌ๋กœ๊ทธ์ธ ํ•„์š”๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
โŒ Client: AT โŒ / DB: โœ…์ฟ ํ‚ค ์ œ๊ฑฐ๋˜๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐRefresh๋กœ ์ธ์ฆ ๊ฐ€๋Šฅ / ์—†์œผ๋ฉด ๋กœ๊ทธ์ธ ์œ ๋„
๐Ÿงจ RefreshToken ์กฐ์ž‘๋จ or ํƒˆ์ทจ๋จ๋ณด์•ˆ ์œ„ํ—˜์„œ๋ฒ„์—์„œ ์ฐจ๋‹จ ํ›„ ์ „์ฒด ์„ธ์…˜ ๋กœ๊ทธ์•„์›ƒ ํ•„์š” (๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ๋“ฑ ํ™œ์šฉ ์˜ˆ์ •)

๐Ÿ”ฅ ์ •๋ฆฌ

๊ตฌ์„ฑ์š”์†Œ์„ค๋ช…
JwtToken EntityAccess/RefreshToken๊ณผ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์ €์žฅ
CustomSuccessHandler๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ํ† ํฐ ๋ฐœ๊ธ‰ ๋ฐ ์ €์žฅ ์ฒ˜๋ฆฌ
JwtTokenRepositoryDB์—์„œ ํ† ํฐ ์กฐํšŒ ๋ฐ ๊ด€๋ฆฌ
/refresh (์ถ”ํ›„ ๊ตฌํ˜„)AccessToken ์žฌ๋ฐœ๊ธ‰ ์ฒ˜๋ฆฌ ์—”๋“œํฌ์ธํŠธ ์˜ˆ์ •
์˜ˆ์™ธ์ฒ˜๋ฆฌ ์ „๋žต์ƒํ™ฉ๋ณ„ ํ๋ฆ„์„ ๋ถ„๊ธฐํ•˜์—ฌ ๋Œ€์‘ ์„ค๊ณ„ ๊ฐ€๋Šฅ

๐Ÿ”— ์ฐธ๊ณ  ์ž๋ฃŒ


๐Ÿง  ๋А๋‚€ ์ 

  • RefreshToken์„ ์ €์žฅํ•˜๊ณ  ์ธ์ฆ ํ๋ฆ„์„ ๋ถ„๋ฆฌํ•ด์„œ ์„ค๊ณ„ํ•˜๋‹ˆ, ๋ณด์•ˆ์„ฑ๊ณผ ์‚ฌ์šฉ์ž ํŽธ์˜์„ฑ์ด ๋™์‹œ์— ๋†’์•„์ง
  • ์˜ˆ์™ธ ์ƒํ™ฉ์„ ์ง์ ‘ ๊ทธ๋ ค๋ณด๋‹ˆ ์‹ค๋ฌด์—์„œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ํ๋ฆ„๊นŒ์ง€ ๊ณ ๋ คํ•˜๊ฒŒ ๋๊ณ , /refresh API ์„ค๊ณ„์— ๋” ํ™•์‹ ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์—ˆ๋‹ค
  • ์ดํ›„์—๋Š” Redis ๊ธฐ๋ฐ˜ ์ €์žฅ์†Œ, ํ† ํฐ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ, ์‚ฌ์šฉ์ž ๊ฐ•์ œ ๋กœ๊ทธ์•„์›ƒ ๋“ฑ๋„ ๊ตฌํ˜„ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค

โœ… ์š”์•ฝ

  • JWT ์ธ์ฆ ๊ตฌ์กฐ์—์„œ AccessToken๊ณผ RefreshToken์„ ๋ถ„๋ฆฌ ์ €์žฅํ•จ์œผ๋กœ์จ ์ธ์ฆ ํ๋ฆ„์„ ๋” ์œ ์—ฐํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.
  • RefreshToken์„ ์ €์žฅํ•˜๋ฉด ํƒˆ์ทจ ํƒ์ง€, ์žฌ๋กœ๊ทธ์ธ ์ตœ์†Œํ™”, ์„œ๋ฒ„ ์ธก ๋งŒ๋ฃŒ ์ œ์–ด ๋“ฑ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์˜ˆ์™ธ ์ƒํ™ฉ์„ ์‚ฌ์ „์— ์ •์˜ํ•˜๊ณ  ์„ค๊ณ„ํ•˜๋ฉด, ์‚ฌ์šฉ์ž ๊ฒฝํ—˜๊ณผ ๋ณด์•ˆ์„ ๋ชจ๋‘ ์ฑ™๊ธธ ์ˆ˜ ์žˆ๋‹ค.

profile
Here, My Pale Blue.๐ŸŒ

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