-
jwt ๊ด๋ จ Tip
Q. access token ์ด ๋ง๋ฃ๋๋ฉด, refresh token์ ํตํด ์ฌ๋ฐํ ํ๋ค. refresh token์ ๋ง๋ฃ ๊ธฐ๊ฐ์ด ์์๊น?
A.
Refresh Token๋ ๋ฐ๋์ ๋ง๋ฃ ๊ธฐ๊ฐ์ด ์์ด์ผ ํฉ๋๋ค.- ์ผ๋ฐ์ ์ผ๋ก๋
Access Token๋ณด๋ค ํจ์ฌ ๊ธด ๋ง๋ฃ ๊ธฐ๊ฐ์ ์ค์ ํ ๋ฟ์ด์์.
1. ์ผ๋ฐ์ ์ธ ๋ง๋ฃ ์๊ฐ ์ค์
- Access Token: ๋ณดํต 15๋ถ ~ 1์๊ฐ
- Refresh Token: ๋ณดํต 7์ผ ~ 30์ผ ๋๋ 90์ผ
2. ์ Refresh Token๋ ๋ง๋ฃ ๊ธฐ๊ฐ์ด ํ์ํ๊ฐ?
- Refresh Token์ด ์ ์ถ๋์์ ๊ฒฝ์ฐ, ๋ฌด์ ํ์ผ๋ก Access Token์ ์ฌ๋ฐ๊ธ๋ฐ์ ์ ์๋ค๋ฉด ๋ณด์์ ์น๋ช ์ ์ ๋๋ค.
- ์ผ์ ๊ธฐ๊ฐ์ด ์ง๋๋ฉด ๋ก๊ทธ์ธ ์ธ์ ์ ์ฌํ์ธํ๊ฑฐ๋ ์ฌ๋ก๊ทธ์ธ์ ์ ๋ํ๋ ๊ฒ์ด ๋ณด์์ ๋ ์์ ํฉ๋๋ค.
3. ์ ์ฅ ์์น๋?
- Refresh Token์ ๋ณดํต ์๋ฒ ์ ์ฅ ๋ฐฉ์ (e.g., Redis)์ ์ ์ฅํ๊ณ , ์ฌ์ฉ์์๊ฒ๋ ์๋ณ์๋ง ์ ๋ฌํ๊ฑฐ๋ ํด๋ผ์ด์ธํธ์ ์ ์ฅํ๋ ๋ณด์์ ์ผ๋ก ์์ ํ ์ ์ฅ์ (e.g., Secure Cookie, LocalStorage X) ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.
Q. Spring Boot์์์ ์์
A. Spring Boot์์ JWT ๊ธฐ๋ฐ์ผ๋ก Access Token๊ณผ Refresh Token์ ๋ฐ๊ธ ๋ฐ ์ฌ๋ฐ๊ธํ๋ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋ฅผ ์์
1. ํ ํฐ ์์ฑ ํด๋์ค (JwtProvider)
@Component public class JwtProvider { private final String secretKey = "yourSecretKey"; // ์ ์ ํ ์ํธํ ํ์ private final long accessTokenValidity = 1000L * 60 * 15; // 15๋ถ private final long refreshTokenValidity = 1000L * 60 * 60 * 24 * 14; // 14์ผ public String createAccessToken(String userId) { return createToken(userId, accessTokenValidity); } public String createRefreshToken(String userId) { return createToken(userId, refreshTokenValidity); } private String createToken(String userId, long validity) { Claims claims = Jwts.claims().setSubject(userId); Date now = new Date(); Date expiry = new Date(now.getTime() + validity); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(expiry) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } public String getUserIdFromToken(String token) { return Jwts.parser().setSigningKey(secretKey) .parseClaimsJws(token).getBody().getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); return true; } catch (ExpiredJwtException e) { return false; } } }
2. ๋ก๊ทธ์ธ ์ ํ ํฐ ๋ฐ๊ธ
@PostMapping("/login") public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) { // ์ธ์ฆ ์ฒ๋ฆฌ (์๋ต) String userId = "user-id"; String accessToken = jwtProvider.createAccessToken(userId); String refreshToken = jwtProvider.createRefreshToken(userId); // Refresh Token์ DB๋ Redis์ ์ ์ฅ refreshTokenService.save(userId, refreshToken); return ResponseEntity.ok(new TokenResponse(accessToken, refreshToken)); }
3. Access Token ๋ง๋ฃ ์ Refresh Token์ผ๋ก ์ฌ๋ฐ๊ธ
@PostMapping("/token/refresh") public ResponseEntity<TokenResponse> refreshToken(@RequestBody TokenRequest request) { String refreshToken = request.getRefreshToken(); if (!jwtProvider.validateToken(refreshToken)) { throw new RuntimeException("Invalid refresh token"); } String userId = jwtProvider.getUserIdFromToken(refreshToken); // ์ ์ฅ๋ refresh token๊ณผ ๋น๊ต String stored = refreshTokenService.get(userId); if (!stored.equals(refreshToken)) { throw new RuntimeException("Token mismatch"); } String newAccessToken = jwtProvider.createAccessToken(userId); return ResponseEntity.ok(new TokenResponse(newAccessToken, refreshToken)); }
4. Refresh Token ์ ์ฅ ์์ (Redis ๊ธฐ๋ฐ)
@Service public class RefreshTokenService { private final RedisTemplate<String, String> redisTemplate; public RefreshTokenService(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } public void save(String userId, String token) { redisTemplate.opsForValue().set(userId, token, 14, TimeUnit.DAYS); // ์ ํจ ๊ธฐ๊ฐ ์ค์ } public String get(String userId) { return redisTemplate.opsForValue().get(userId); } }
Q. ์์์์ RefreshToken ์์ refresh save์ ์ ๊ธฐ์กด๊บผ๋ ์์ง์๋ ๋ ๊น?
A. ์ผ๋ฐ์ ์ผ๋ก๋ ๊ธฐ์กด Refresh Token์ ์ญ์ ํ๊ฑฐ๋ ๋ฎ์ด์จ์ผ ํฉ๋๋ค.
์๋๋ฉด:
CreatedThu, 27 Mar 2025 11:36:45 +0900 - ์ผ๋ฐ์ ์ผ๋ก๋
-
Spring Boot์์ Kafka๋ฅผ ์ฌ์ฉํ ๋ ์ค๋ฅ๋ฅผ ์ฝ๋๋ก ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํด๋ณผ๊ฒ์.
Kafka ํ๋ก๋์์ ์ปจ์๋จธ์์ Fallback ๋ฉ์๋, ์ฌ์๋ (Retry), ์์ธ ์ฒ๋ฆฌ ๋ฑ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ด ๋๋ค.
1. Kafka ํ๋ก๋์์์ ์ค๋ฅ ์ฒ๋ฆฌ (Fallback, Retry)
Kafka ํ๋ก๋์๋ ๋ฉ์์ง๋ฅผ ๋ธ๋ก์ปค๋ก ๋ณด๋ด๋ ๊ณผ์ ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ๋คํธ์ํฌ ๋ฌธ์ , ๋ธ๋ก์ปค ๋ค์ด, ๋ฉ์์ง ํฌ๊ธฐ ์ด๊ณผ ๋ฑ์ ๋ฌธ์ ๊ฐ ์์ ์ ์๋๋ฐ์, ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.โ (1) Fallback ๋ฉ์๋ ์ ์ฉ (Resilience4j ์ฌ์ฉ)
Resilience4j์
@Retry์@Recover๋ฅผ ํ์ฉํ๋ฉด
Kafka ์ ์ก์ด ์คํจํ์ ๋ ์๋์ผ๋ก ์ฌ์๋๋ฅผ ํ๊ณ , ์ต์ข ์ ์ผ๋ก ์คํจํ๋ฉด ๋์ฒด ๋ก์ง์ ์ํํ ์ ์์ต๋๋ค.CreatedWed, 26 Mar 2025 13:38:03 +0900 -
Apache Kafka ์ฌ์ฉ ์ ๋ฐ์ํ ์ ์๋ ์ฃผ์ ์ค๋ฅ ์ ํ๊ณผ ๊ทธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
1. ๋คํธ์ํฌ ๋ฐ ์ฐ๊ฒฐ ์ค๋ฅ
โ ์ค๋ฅ:
Connection to broker <broker_id> failed๐ ์์ธ: ๋ธ๋ก์ปค์์ ๋คํธ์ํฌ ์ฐ๊ฒฐ ๋ฌธ์ , ๋ธ๋ก์ปค ๋ค์ด, ๋ฐฉํ๋ฒฝ ๋ฌธ์ ๋ฑ
โ ํด๊ฒฐ ๋ฐฉ๋ฒ:zookeeper์broker๊ฐ ์คํ ์ค์ธ์ง ํ์ธ (ps aux | grep kafka)listeners๋ฐadvertised.listeners์ค์ ํ์ธ (server.properties)- ๋ฐฉํ๋ฒฝ ์ค์ (
iptables -L,firewalld)์ ์ ๊ฒํ์ฌ ํฌํธ๊ฐ ์ด๋ ค ์๋์ง ํ์ธ
2. ๋ธ๋ก์ปค ๋ฐ ํ ํฝ ๊ด๋ จ ์ค๋ฅ
โ ์ค๋ฅ:
Leader not available for partition <partition_id>๐ ์์ธ:
CreatedWed, 26 Mar 2025 13:30:03 +0900 -
๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ชจ๋ ํธ๋์ญ์ ์ ๋ํด Lock๊ณผ Unlock์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐ ํธ๋์ญ์ ์ ๋์ ๋ฐฉ์์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ํ ์ด๋ธ ์ ๊ธ, ํ ์ ๊ธ, ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค, ๋ช ์์ ์ ๊ธ ํด์ ๋ฅผ ์กฐํฉํ์ฌ ๊ตฌํํ ์ ์์ต๋๋ค. Java ํ๊ฒฝ์์๋ JDBC, JPA, QueryDSL ๋ฑ์ ํ์ฉํด ์ด๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
์๋๋ SELECT๋ฟ๋ง ์๋๋ผ INSERT, UPDATE, DELETE ๋ฑ ๋ชจ๋ ํธ๋์ญ์ ์์ Lock๊ณผ Unlock์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค.
1. ํ ์ด๋ธ ์ ๊ธ (Table Lock)
ํ ์ด๋ธ ๋จ์๋ก ์ ๊ธ์ ์ค์ ํ์ฌ ํธ๋์ญ์ ์ด ์๋ฃ๋๊ธฐ ์ ๊น์ง ๋ค๋ฅธ ํธ๋์ญ์ ์ ์ ๊ทผ์ ๋ง์ต๋๋ค.
CreatedWed, 26 Mar 2025 11:33:45 +0900 -
QueryDSL์ ์ฌ์ฉํ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ Lock ๊ธฐ๋ฅ์ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ JPA์ ์ ์ฌํฉ๋๋ค. QueryDSL์ JPA์ ํตํฉ๋์ด ๋์ํ๋ฉฐ, JPQL ๋ฐ Native Query์์ ์ฌ์ฉํ๋ ์ ๊ธ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํฉ๋๋ค. ์๋๋ QueryDSL์์ Lock๊ณผ ๊ด๋ จ๋ ์ฃผ์ ๊ธฐ๋ฅ๊ณผ ๊ตฌํ ๋ฐฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค.
1. QueryDSL์์ Pessimistic Lock ์ฌ์ฉ
QueryDSL์ JPA
LockModeType์ ์ง์ํ์ฌ ๋น๊ด์ ์ ๊ธ์ ๊ตฌํํ ์ ์์ต๋๋ค.์์ :
LockModeType.PESSIMISTIC_WRITE์ฌ์ฉimport com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.LockModeType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class ProductService { private final JPAQueryFactory queryFactory; public ProductService(EntityManager entityManager) { this.queryFactory = new JPAQueryFactory(entityManager); } @Transactional public Product findByIdWithLock(Long productId) { QProduct product = QProduct.product; // Pessimistic Write Lock ์ค์ return queryFactory.selectFrom(product) .where(product.id.eq(productId)) .setLockMode(LockModeType.PESSIMISTIC_WRITE) .fetchOne(); } }์ฃผ์ ํฌ์ธํธ:
setLockMode(LockModeType.PESSIMISTIC_WRITE)๋ฅผ ์ฌ์ฉํ์ฌ ๋น๊ด์ ์ฐ๊ธฐ ์ ๊ธ์ ์ค์ ํฉ๋๋ค.PESSIMISTIC_READ์ ๊ฐ์ ๋ค๋ฅธ ์ ๊ธ ๋ชจ๋๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
2. ๋๊ด์ ์ ๊ธ (Optimistic Lock)
๋๊ด์ ์ ๊ธ์ QueryDSL์์ ๋ณ๋๋ก ์ง์๋์ง ์์ง๋ง, JPA ์ํฐํฐ์
@Versionํ๋๋ฅผ ํ์ฉํ์ฌ ๋์ํฉ๋๋ค.CreatedWed, 26 Mar 2025 11:30:45 +0900 -
Java ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ ๋
-Xmx512m๊ณผ-XX:MaxRAM=512m๋ ๋ชจ๋ ์ต๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ ํํ๋ ์ต์ ์ด์ง๋ง, ๋์ ๋ฐฉ์์ ์ฐจ์ด๊ฐ ์์ต๋๋ค.1.
-Xmx512m- ํ(Heap) ๋ฉ๋ชจ๋ฆฌ์ ์ต๋ ํฌ๊ธฐ๋ฅผ 512MB๋ก ์ค์ ํฉ๋๋ค.
- ์์ :
java -Xmx512m -jar myapp.jar - ์ด ์ค์ ์ Java ํ ๋ฉ๋ชจ๋ฆฌ๋ง ์ ํํ๋ฉฐ, ์ ์ฒด ํ๋ก์ธ์ค์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ ํํ๋ ๊ฒ์ ์๋๋๋ค.
- ์ฆ, ํ ์ธ์ ๋ฉ๋ชจ๋ฆฌ(๋ฉํ์คํ์ด์ค, ์คํ, ๋ค์ดํฐ๋ธ ๋ฉ๋ชจ๋ฆฌ ๋ฑ)๋ ๋ฐ๋ก ์ ํ๋์ง ์์ต๋๋ค.
2.
-XX:MaxRAM=512m- JVM์ด ์ฌ์ฉํ ์ ์๋ ์ ์ฒด ๋ฉ๋ชจ๋ฆฌ(ํ + ๋ฉํ์คํ์ด์ค + ๊ธฐํ ๋ค์ดํฐ๋ธ ๋ฉ๋ชจ๋ฆฌ) ๋ฅผ 512MB๋ก ์ ํํฉ๋๋ค.
- ์์ :
java -XX:MaxRAM=512m -jar myapp.jar -XX:MaxRAM์ ์ค์ ํ๋ฉด-Xmx์ ๊ธฐ๋ณธ๊ฐ์ด ๋ฌ๋ผ์ง ์ ์์ต๋๋ค.- ์ผ๋ฐ์ ์ผ๋ก
-Xmx์ ๊ธฐ๋ณธ๊ฐ์MaxRAM๊ฐ์ 25~50% ์ ๋๋ก ์ค์ ๋ฉ๋๋ค(JVM ๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฆ).
- ์ผ๋ฐ์ ์ผ๋ก
- ์ด ์ค์ ์ ์ปจํ ์ด๋ ํ๊ฒฝ(Docker, Kubernetes) ์์ JVM์ด ์ฌ์ฉํ ์ ์๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ์ดํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
์ฐจ์ด์ ์์ฝ
์ต์ ๋์ ์ค๋ช -Xmx512mํ(Heap) ๋ฉ๋ชจ๋ฆฌ ํ ๋ฉ๋ชจ๋ฆฌ์ ์ต๋ ํฌ๊ธฐ๋ง ์ ํ -XX:MaxRAM=512m์ ์ฒด JVM ๋ฉ๋ชจ๋ฆฌ ํ + ๋ฉํ์คํ์ด์ค + ๋ค์ดํฐ๋ธ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํฌํจํ JVM ์ ์ฒด ๋ฉ๋ชจ๋ฆฌ ์ ํ ํจ๊ป ์ฌ์ฉํ๋ฉด?
๋ ์ต์ ์ ํจ๊ป ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
CreatedWed, 26 Mar 2025 11:26:45 +0900