Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
package com.doubleo.adminservice.domain.auth.controller;

import com.doubleo.adminservice.domain.auth.dto.AccessTokenDto;
import com.doubleo.adminservice.domain.auth.dto.RefreshTokenDto;
import com.doubleo.adminservice.domain.auth.dto.request.LoginRequest;
import com.doubleo.adminservice.domain.auth.dto.response.LoginResponse;
import com.doubleo.adminservice.domain.auth.service.AuthService;
import com.doubleo.adminservice.domain.auth.service.JwtTokenService;
import com.doubleo.adminservice.global.util.CookieUtil;
import com.doubleo.adminservice.global.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.WebUtils;

@Slf4j
@Tag(name = "1-2. Auth API", description = "관리자 로그인/로그아웃/Refresh Token 관련 API")
@RestController
@RequiredArgsConstructor
Expand All @@ -29,6 +31,7 @@ public class AuthController {
private final AuthService authService;
private final CookieUtil cookieUtil;
private final JwtTokenService jwtTokenService;
private final JwtUtil jwtUtil;

@Operation(summary = "관리자 로그인", description = "관리자 로그인을 처리합니다.")
@PostMapping("/login")
Expand Down Expand Up @@ -67,31 +70,30 @@ public ResponseEntity<Void> adminLogout(
@PostMapping("/reissue")
public ResponseEntity<Void> tokenReissue(
HttpServletRequest request, HttpServletResponse response) {
String oldAccessToken = extractAccessTokenFromHeader(request);
String oldAccessToken = jwtUtil.resolveToken(request.getHeader(HttpHeaders.AUTHORIZATION));
String refreshToken = extractRefreshTokenFromCookie(request);

log.info("oldAccessToken: {}, refreshToken: {}", oldAccessToken, refreshToken);
RefreshTokenDto refreshTokenDto = jwtTokenService.retrieveRefreshToken(refreshToken);
if (refreshTokenDto == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
AccessTokenDto newAccessTokenDto =
jwtTokenService.reissueAccessTokenIfExpired(oldAccessToken);
response.addHeader(
HttpHeaders.AUTHORIZATION, "Bearer " + newAccessTokenDto.accessTokenValue());

return ResponseEntity.ok().build();
return jwtTokenService
.reissueAccessTokenIfExpired(oldAccessToken)
.map(
newToken -> {
// 새 토큰이 존재할 때 헤더에 담고 200 OK 리턴
response.setHeader(
HttpHeaders.AUTHORIZATION,
"Bearer " + newToken.accessTokenValue());
return ResponseEntity.ok().<Void>build();
})
// 없으면 204 No Content 리턴
.orElseGet(() -> ResponseEntity.noContent().build());
}

private String extractRefreshTokenFromCookie(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, "refreshToken");
return (cookie != null) ? cookie.getValue() : null;
}

private String extractAccessTokenFromHeader(HttpServletRequest request) {
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
return authorizationHeader.substring(7);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.doubleo.adminservice.domain.auth.dto.AccessTokenDto;
import com.doubleo.adminservice.domain.auth.dto.RefreshTokenDto;
import java.util.Optional;

public interface JwtTokenService {

Expand All @@ -18,7 +19,7 @@ public interface JwtTokenService {
RefreshTokenDto retrieveRefreshToken(String refreshTokenValue);

// AccessToken 만료 여부 검증 후 재발급
AccessTokenDto reissueAccessTokenIfExpired(String accessTokenValue);
Optional<AccessTokenDto> reissueAccessTokenIfExpired(String accessTokenValue);

// 사용하지 않는 AccessToken BlackList 등록
void putAccessTokenOnBlackList(String accessTokenValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import io.jsonwebtoken.ExpiredJwtException;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class JwtTokenServiceImpl implements JwtTokenService {
Expand Down Expand Up @@ -43,7 +45,6 @@ public String createRefreshToken(Long adminId) {

public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) {
RefreshTokenDto refreshTokenDto = parseRefreshToken(refreshTokenValue);

if (refreshTokenDto == null) {
return null;
}
Expand All @@ -58,14 +59,18 @@ public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) {
return null;
}

public AccessTokenDto reissueAccessTokenIfExpired(String accessTokenValue) {
public Optional<AccessTokenDto> reissueAccessTokenIfExpired(String accessTokenValue) {
try {
// 파싱에 성공하면 아직 유효 ⇒ Optional.empty()
jwtUtil.parseAccessToken(accessTokenValue);
return null;
log.info("Access token is still valid, no reissue needed");
return Optional.empty();
} catch (ExpiredJwtException e) {
// 만료된 경우에만 새 토큰 생성
Long adminId = Long.parseLong(e.getClaims().getSubject());

return createAccessTokenDto(adminId);
AccessTokenDto newToken = createAccessTokenDto(adminId);
log.info("Access token expired, issued new one for adminId={}", adminId);
return Optional.of(newToken);
}
}

Expand Down