Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ out/

### Mac ###
.DS_Store
.agent

src/main/resources/application.yml
src/main/resources/application-dev.yml
Expand Down
19 changes: 3 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
## πŸ“‹ λͺ©μ°¨

- [기술 μŠ€νƒ](#-기술-μŠ€νƒ)
- [μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜](#-μ‹œμŠ€ν…œ-μ•„ν‚€ν…μ²˜)
- [μ£Όμš” κΈ°λŠ₯](#-μ£Όμš”-κΈ°λŠ₯)
- [ν”„λ‘œμ νŠΈ ꡬ쑰](#-ν”„λ‘œμ νŠΈ-ꡬ쑰)
- [CI/CD](#cicd)
Expand Down Expand Up @@ -48,22 +47,10 @@

---

## πŸ— μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Client │────▢│ Nginx │────▢│ Spring Boot β”‚
β”‚ (Frontend) β”‚ β”‚ (Reverse β”‚ β”‚ Server β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Proxy) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚ MySQL β”‚ β”‚ Redis β”‚ β”‚ AWS S3 β”‚ β”‚ Toss Payments β”‚ β”‚ Google β”‚ β”‚ Kakao β”‚
β”‚ DB β”‚ β”‚ Cache β”‚ β”‚ (Images) β”‚ β”‚ API β”‚ β”‚ OAuth β”‚ β”‚ OAuth β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```




---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.permitseoul.permitserver.domain.admin.base.api.AdminProperties;
import com.permitseoul.permitserver.domain.admin.base.api.dto.res.UserAuthorityGetResponse;
import com.permitseoul.permitserver.domain.admin.base.api.exception.AdminAuthorizationException;
import com.permitseoul.permitserver.domain.auth.core.jwt.RefreshTokenManager;
import com.permitseoul.permitserver.domain.user.core.component.UserRetriever;
import com.permitseoul.permitserver.domain.user.core.component.UserUpdater;
import com.permitseoul.permitserver.domain.user.core.domain.User;
Expand All @@ -20,6 +21,7 @@ public class AdminService {
private final AdminProperties adminProperties;
private final UserRetriever userRetriever;
private final UserUpdater userUpdater;
private final RefreshTokenManager refreshTokenManager;

public void validateAdminCode(final String adminCode) {
if(!(adminProperties.accessCode().equals(adminCode))){
Expand All @@ -44,6 +46,7 @@ public void updateUserAuthority(final long userId, final UserRole userRole) {
try {
userEntity = userRetriever.findUserEntityById(userId);
userUpdater.updateUserRole(userEntity, userRole);
refreshTokenManager.deleteRefreshToken(userEntity.getUserId());
} catch (UserNotFoundException e) {
throw new AdminAuthorizationException(ErrorCode.NOT_FOUND_USER);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ public TokenDto login(final SocialType socialType, final String authorizationCod
public TokenDto reissue(final String refreshToken) {
try {
final long userId = jwtProvider.extractUserIdFromToken(refreshToken);
checkIsSameRefreshToken(userId, refreshToken);
final UserRole userRole = UserRole.valueOf(jwtProvider.extractUserRoleFromToken(refreshToken));
checkIsSameRefreshToken(userId, refreshToken);

final User user = userRetriever.findUserById(userId);
final Token newToken = getLoginOrReissueJwtToken(user.getUserId(), user.getUserRole());
final Token newToken = getLoginOrReissueJwtToken(userId, userRole);

saveRefreshTokenInRedis(user.getUserId(), newToken.getRefreshToken());
saveRefreshTokenInRedis(userId, newToken.getRefreshToken());

return TokenDto.of(newToken.getAccessToken(), newToken.getRefreshToken());
} catch (AuthWrongJwtException | AuthRTNotFoundException e) {
} catch (AuthWrongJwtException e) {
throw new AuthUnAuthorizedException(ErrorCode.UNAUTHORIZED_WRONG_RT);
} catch (AuthExpiredJwtException e) {
} catch (AuthExpiredJwtException |AuthRTNotFoundException e) {
throw new AuthUnAuthorizedException(ErrorCode.UNAUTHORIZED_RT_EXPIRED);
} catch (AuthRTException e) {
throw new AuthUnAuthorizedException(ErrorCode.INTERNAL_RT_REDIS_ERROR);
Expand All @@ -125,7 +125,6 @@ public void logout(final long userId, final String refreshTokenFromCookie) {
} catch (DataAccessException e) {
throw new AuthRedisException(ErrorCode.INTERNAL_RT_REDIS_ERROR);
}

}

private void checkIsSameRefreshToken(final long userId, final String refreshToken) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@
@RedisHash("refreshToken")
public class RefreshToken {

/** μ‚¬μš©μž μ‹λ³„μž(ν‚€). μ‚¬μš©μžλ‹Ή 1개 μ„Έμ…˜λ§Œ ν—ˆμš© λͺ¨λΈ */
@Id
private Long userId;

/** 토큰 ν•΄μ‹œ(SHA-256 λ“±). 평문 μ €μž₯ κΈˆμ§€ */
private String refreshToken;

/** 만료 μ‹œκ°„(초 λ‹¨μœ„). @RedisHash 클래슀 λ‹¨μœ„ TTL λŒ€μ‹  μΈμŠ€ν„΄μŠ€λ³„ TTL μΆ”μ²œ */
@TimeToLive
private long ttlSeconds;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ public long extractUserIdFromToken(final String token) {

public String extractUserRoleFromToken(final String token) {
final Jws<Claims> claims = parseToken(token);
return claims.getBody().get(Constants.USER_ROLE, String.class);
final String role = claims.getBody().get(Constants.USER_ROLE, String.class);
if(role == null) {
throw new AuthWrongJwtException();
}
return role;
}

//토큰 νŒŒμ‹±
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ protected void doFilterInternal(@NonNull final HttpServletRequest request,
try {
filterChain.doFilter(request, response);
} catch (FilterException e) {
log.warn("[FilterException] code={}, ua={}",
e.getErrorCode().name(),
request.getHeader("User-Agent")
);
handleUnauthorizedException(response, e);
}
catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,30 @@

@Component
@Slf4j
@Profile("!local")
//@Profile("!local")
@Order(Ordered.HIGHEST_PRECEDENCE)
class MDCLoggingFilter extends OncePerRequestFilter {
class RequestObservabilityFilter extends OncePerRequestFilter {
private static final String NGINX_REQUEST_ID = "X-Request-ID";
private static final String TRACE_ID = "trace_id";

private static final String URI = "uri";
private static final String METHOD = "method";
private static final String STATUS = "status";

private static final long SLOW_REQUEST_THRESHOLD_MS = 1000L;

@Override
protected void doFilterInternal(@NonNull final HttpServletRequest request,
@NonNull final HttpServletResponse response,
@NonNull final FilterChain filterChain) throws ServletException, IOException {
@NonNull final HttpServletResponse response,
@NonNull final FilterChain filterChain) throws ServletException, IOException {
final String uri = request.getRequestURI();
// ν—¬μŠ€μ²΄ν¬λŠ” 패슀
if (uri != null && uri.contains(Constants.HEALTH_CHECK_URL)) {
filterChain.doFilter(request, response);
return;
}

final long start = System.currentTimeMillis();
try {
String traceId = request.getHeader(NGINX_REQUEST_ID);
if (traceId == null || traceId.isBlank()) {
Expand All @@ -48,9 +52,17 @@ protected void doFilterInternal(@NonNull final HttpServletRequest request,
MDC.put(METHOD, request.getMethod());

filterChain.doFilter(request, response);

MDC.put(STATUS, String.valueOf(response.getStatus()));
} finally {
final long duration = System.currentTimeMillis() - start;
final int status = response.getStatus();
final String method = request.getMethod();

if (duration >= SLOW_REQUEST_THRESHOLD_MS) {
log.warn("[SLOW] {} {} β†’ {} ({}ms)", method, uri, status, duration);
} else {
log.info("{} {} β†’ {} ({}ms)", method, uri, status, duration);
}

MDC.remove(STATUS);
MDC.remove(METHOD);
MDC.remove(URI);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import static org.assertj.core.api.Assertions.assertThat;

// TODO: RTCacheManager ν΄λž˜μŠ€κ°€ μ‚­μ œλ˜μ–΄ κ΄€λ ¨ ν…ŒμŠ€νŠΈκ°€ λΉ„ν™œμ„±ν™”λ¨. μΊμ‹œ λ§€λ‹ˆμ € λ³€κ²½ ν›„ ν…ŒμŠ€νŠΈ 볡원 ν•„μš”.
@SpringBootTest
class JwtGeneratorCacheTest {

Expand All @@ -27,42 +28,30 @@ class JwtGeneratorCacheTest {
@Autowired
private JwtProvider jwtProvider;

@Autowired
private RTCacheManager rtCacheManager;
// @Autowired
// private RTCacheManager rtCacheManager; // TODO: RTCacheManager μ‚­μ œλ¨ - λŒ€μ²΄ κ΅¬ν˜„ ν›„
// 볡원

//ν…ŒμŠ€νŠΈ ν›„ μΊμ‹œ μ‚­μ œ
// ν…ŒμŠ€νŠΈ ν›„ μΊμ‹œ μ‚­μ œ
@AfterEach
void tearDown() {
Objects.requireNonNull(cacheManager.getCache(Constants.REFRESH_TOKEN)).clear();
}

@Test
void λ¦¬ν”„λ ˆμ‹œ_토큰_μΊμ‹œμ—_정상_μ €μž₯됨() {
// given
long userId = 1L;

// when
String token = jwtGenerator.generateRefreshToken(userId, UserRole.USER);

// then
String cachedToken = rtCacheManager.getRefreshTokenFromCache(userId);

assertThat(cachedToken).isEqualTo(token);
}

@Test
void μΊμ‹œμ—_userId_값이_μ—†μœΌλ©΄_null_λ°˜ν™˜() {
// given
long nonExistUserId = 999L;

// when
String token = rtCacheManager.getRefreshTokenFromCache(nonExistUserId);

// then
Assertions.assertNull(token);
}



// TODO: RTCacheManager λŒ€μ²΄ 이후 볡원
// @Test
// void λ¦¬ν”„λ ˆμ‹œ_토큰_μΊμ‹œμ—_정상_μ €μž₯됨() {
// long userId = 1L;
// String token = jwtGenerator.generateRefreshToken(userId, UserRole.USER);
// String cachedToken = rtCacheManager.getRefreshTokenFromCache(userId);
// assertThat(cachedToken).isEqualTo(token);
// }

// @Test
// void μΊμ‹œμ—_userId_값이_μ—†μœΌλ©΄_null_λ°˜ν™˜() {
// long nonExistUserId = 999L;
// String token = rtCacheManager.getRefreshTokenFromCache(nonExistUserId);
// Assertions.assertNull(token);
// }

}
Loading