Skip to content
Open
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
Expand Up @@ -6,7 +6,9 @@
import com.deardream.deardream_be.global.apiPayload.ApiResponse;
import com.deardream.deardream_be.global.apiPayload.code.status.ErrorStatus;
import com.deardream.deardream_be.global.apiPayload.exception.GeneralException;
import com.deardream.deardream_be.domain.cookie.CookieService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
Expand All @@ -19,32 +21,52 @@
public class AuthController {

private final AuthService authService;
private final CookieService cookieService;

@Operation(summary = "인가코드와 redirectUri를 통해 카카오 서버로부터 정보를 내려받습니다.")
@GetMapping("/login/kakao")
public ApiResponse<KakaoLoginResponseDto> kakaoLogin(@RequestParam("code") String code, @RequestParam(value = "redirectUri") String redirectUri, @RequestParam(value = "state", required = false) Long familyId) {

log.info("Received redirectUri={}", redirectUri);
public ApiResponse<KakaoLoginResponseDto> kakaoLogin(
@RequestParam("code") String code,
@RequestParam(value = "redirectUri") String redirectUri,
Long familyId,
HttpServletResponse response) {

if (!WhiteRedirectUriList.getAllowedRedirectUris().contains(redirectUri)) {
boolean allowed = WhiteRedirectUriList.getAllowedRedirectUris().contains(redirectUri);
log.info("Is redirectUri allowed? {}", allowed);

throw new GeneralException(ErrorStatus._INVALID_REDIRECT_URI);
}

KakaoLoginResponseDto loginResponseDto = authService.loginWithKakao(code, redirectUri, familyId);

// 쿠키 세팅 로직
// tempToken은 가입 전용, 가입된 유저가 아니면 임시 토큰만 세팅
if (!loginResponseDto.isRegistered()) {
if (loginResponseDto.getTempToken() != null) {
cookieService.setTempTokenCookie(response, loginResponseDto.getTempToken());
}
} else { // 가입된 유저면 accessToken, refreshToken 쿠키 세팅
cookieService.setTokenCookies(response, loginResponseDto.getNewAccessToken(), loginResponseDto.getNewRefreshToken());
}

return ApiResponse.onSuccess(loginResponseDto);

}


@Operation(summary = "토큰을 재발급 받습니다.")
@PostMapping("/reissue")
public ApiResponse<KakaoLoginResponseDto> reissueToken(@RequestHeader("Authorization") String token) {
public ApiResponse<KakaoLoginResponseDto> reissueToken(
@RequestHeader("Authorization") String token,
HttpServletResponse response
) {
try {
String refreshToken = token.replace("Bearer ", "").trim();
KakaoLoginResponseDto responseDto = authService.reissueToken(refreshToken);

// 리프레시 후에는 accessToken/refreshToken 쿠키 모두 세팅
cookieService.setTokenCookies(response, responseDto.getNewAccessToken(), responseDto.getNewRefreshToken());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 재발급 = 리프레쉬 토큰 이라고 새각했는데 아니네요! 새 토큰 발급받는 api 였구뇨!

return ApiResponse.onSuccess(responseDto);

} catch (Exception e) {
Expand All @@ -55,31 +77,38 @@ public ApiResponse<KakaoLoginResponseDto> reissueToken(@RequestHeader("Authoriza

@Operation(summary = "일반 로그아웃 : 로그아웃 시 재로그인이 필요하지 않습니다.")
@PostMapping("/logout")
public ApiResponse<String> logout(@RequestHeader("Authorization") String token) {
public ApiResponse<String> logout(
@RequestHeader("Authorization") String token,
HttpServletResponse response
) {
String accessToken = token.replace("Bearer ", "");
authService.logout(accessToken);

// accessToken, refreshToken, tempToken 쿠키 모두 삭제
cookieService.deleteTokenCookies(response);
cookieService.deleteTempTokenCookie(response);

return ApiResponse.onSuccess("로그아웃에 성공했습니다.");
}

@Operation(summary = "카카오 계정과 함께 로그아웃 : 로그아웃 시 재로그인이 필요합니다.")
@GetMapping("/logout/kakao-account")
public ApiResponse<String> logoutKakaoAccount(@RequestHeader(value = "Authorization") String token, HttpServletRequest request) {
public ApiResponse<String> logoutKakaoAccount(
@RequestHeader(value = "Authorization") String token,
HttpServletRequest request,
HttpServletResponse response
) {
if (token != null && !token.isBlank()) {
String accessToken = token.replace("Bearer ", "");
authService.logout(accessToken);

// accessToken, refreshToken, tempToken 쿠키 모두 삭제
cookieService.deleteTokenCookies(response);
cookieService.deleteTempTokenCookie(response);
}
String logoutRedirectUri = authService.logoutWithKakaoAccount(request);
return ApiResponse.onSuccess(logoutRedirectUri);
}

// @GetMapping("/logout/callback")
// public ApiResponse<String> kakaoAccountLogoutCallback(@RequestHeader(value = "Authorization", required = false) String token) {
// if (token != null && !token.isBlank()) {
// String accessToken = token.replace("Bearer ", "");
// authService.logout(accessToken);
// }
// return ApiResponse.onSuccess("카카오 계정 및 서비스 로그아웃 완료");
// }


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.deardream.deardream_be.domain.cookie;

import com.deardream.deardream_be.domain.cookie.CookieUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;

@Service
public class CookieService {

public static final long ACCESS_TOKEN_EXPIRES_IN = 60 * 60 * 2;
public static final long REFRESH_TOKEN_EXPIRES_IN = 60 * 60 * 24 * 2;
public static final long TEMP_TOKEN_EXPIRES_IN = 60 * 10;

public void setAccessTokenCookie(HttpServletResponse response, String token) {
response.addHeader("Set-Cookie", CookieUtil.createAccessTokenCookie(token, ACCESS_TOKEN_EXPIRES_IN).toString());
}


public void setRefreshTokenCookie(HttpServletResponse response, String token) {
response.addHeader("Set-Cookie", CookieUtil.createRefreshTokenCookie(token, REFRESH_TOKEN_EXPIRES_IN).toString());
}

public void setTempTokenCookie(HttpServletResponse response, String token) {
response.addHeader("Set-Cookie", CookieUtil.createTempTokenCookie(token, TEMP_TOKEN_EXPIRES_IN).toString());
}

// 토큰 삭제(만료)
public void deleteAccessTokenCookie(HttpServletResponse response) {
response.addHeader("Set-Cookie", CookieUtil.deleteAccessTokenCookie().toString());
}

public void deleteRefreshTokenCookie(HttpServletResponse response) {
response.addHeader("Set-Cookie", CookieUtil.deleteRefreshTokenCookie().toString());
}

public void deleteTempTokenCookie(HttpServletResponse response) {
response.addHeader("Set-Cookie", CookieUtil.deleteTempTokenCookie().toString());
}

public void setTokenCookies(HttpServletResponse response, String accessToken, String refreshToken) {
if (accessToken != null) {
setAccessTokenCookie(response, accessToken);
}
if (refreshToken != null) {
setRefreshTokenCookie(response, refreshToken);
}
}

public void deleteTokenCookies(HttpServletResponse response) {
deleteAccessTokenCookie(response);
deleteRefreshTokenCookie(response);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.deardream.deardream_be.domain.cookie;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;

public class CookieUtil {

public static final String ACCESS_TOKEN = "accessToken";
public static final String REFRESH_TOKEN = "refreshToken";
public static final String TEMP_TOKEN = "tempToken";

// 필요하다면 SameSite 값을 "Strict"나 "Lax", "None" 등으로 변경
private static final String SAME_SITE = "Strict";
private static final String PATH = "/";

// 액세스 토큰 세팅
public static ResponseCookie createAccessTokenCookie(String token, long maxAgeSeconds) {
return ResponseCookie.from(ACCESS_TOKEN, token)
.httpOnly(true)
.secure(true)
.path(PATH)
.maxAge(maxAgeSeconds)
.sameSite(SAME_SITE)
.build();
}

// 리프레시 토큰 세팅
public static ResponseCookie createRefreshTokenCookie(String token, long maxAgeSeconds) {
return ResponseCookie.from(REFRESH_TOKEN, token)
.httpOnly(true)
.secure(true)
.path(PATH)
.maxAge(maxAgeSeconds)
.sameSite(SAME_SITE)
.build();
}

public static ResponseCookie createTempTokenCookie(String token, long maxAgeSeconds) {
return ResponseCookie.from(TEMP_TOKEN, token)
.httpOnly(true)
.secure(true)
.path(PATH)
.maxAge(maxAgeSeconds)
.sameSite(SAME_SITE)
.build();
}

// 액세스 토큰 쿠키 삭제 (로그아웃 등)
public static ResponseCookie deleteAccessTokenCookie() {
return ResponseCookie.from(ACCESS_TOKEN, "")
.httpOnly(true)
.secure(true)
.path(PATH)
.maxAge(0)
.sameSite(SAME_SITE)
.build();
}

// 리프레시 토큰 쿠키 삭제
public static ResponseCookie deleteRefreshTokenCookie() {
return ResponseCookie.from(REFRESH_TOKEN, "")
.httpOnly(true)
.secure(true)
.path(PATH)
.maxAge(0)
.sameSite(SAME_SITE)
.build();
}

public static ResponseCookie deleteTempTokenCookie() {
return ResponseCookie.from(TEMP_TOKEN, "")
.httpOnly(true)
.secure(true)
.path(PATH)
.maxAge(0)
.sameSite(SAME_SITE)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.deardream.deardream_be.domain.user.controller;

import com.deardream.deardream_be.domain.auth.service.AuthService;
import com.deardream.deardream_be.domain.cookie.CookieService;
import com.deardream.deardream_be.domain.cookie.CookieUtil;
import com.deardream.deardream_be.domain.jwt.CustomUserDetails;
import com.deardream.deardream_be.domain.jwt.JwtUtil;
import com.deardream.deardream_be.domain.user.dto.RegisterResponseDto;
Expand All @@ -12,6 +14,7 @@
import com.deardream.deardream_be.global.apiPayload.code.status.ErrorStatus;
import com.deardream.deardream_be.global.apiPayload.code.status.SuccessStatus;
import com.deardream.deardream_be.global.apiPayload.exception.GeneralException;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.Value;
Expand All @@ -34,6 +37,7 @@ public class UserController {
private final UserServiceWithdraw userServiceWithdraw;
private final AuthService authService;
private final JwtUtil jwtUtil;
private final CookieService cookieService;


/**
Expand All @@ -46,7 +50,8 @@ public ApiResponse<RegisterResponseDto> registerUser(
@RequestParam(value = "code", required = false) String inviteCode,
@RequestHeader("Authorization") String authorization,
@RequestPart("userRequestDto") @Valid UserRequestDto userRequestDto,
@RequestPart(value = "profileImage", required = false) MultipartFile profileImage
@RequestPart(value = "profileImage", required = false) MultipartFile profileImage,
HttpServletResponse response

) {

Expand All @@ -62,6 +67,9 @@ public ApiResponse<RegisterResponseDto> registerUser(
// 3. 프로필 등록
RegisterResponseDto registerResponseDto = userService.register(kakaoId, userRequestDto, profileImage, inviteCode);

// 4. 쿠키 등록
cookieService.setTokenCookies(response, registerResponseDto.getAccessToken(), registerResponseDto.getRefreshToken());

return ApiResponse.onSuccess(registerResponseDto);
}

Expand Down Expand Up @@ -118,7 +126,9 @@ public ApiResponse<UserResponseDto> updateMyInfo(
@DeleteMapping("/me")
public ApiResponse<Void> withdraw(
Authentication authentication,
@RequestHeader("Authorization") String token) {
@RequestHeader("Authorization") String token,
HttpServletResponse response
) {

CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
Long kakaoId = userDetails.getKakaoId();
Expand All @@ -129,9 +139,11 @@ public ApiResponse<Void> withdraw(
// redis에서 리프레시 토큰 삭제 -> 접근 불가하게 만듦
authService.logout(accessToken);


userServiceWithdraw.withdraw(kakaoId);

cookieService.deleteTokenCookies(response);
cookieService.deleteTempTokenCookie(response);

return ApiResponse.of(SuccessStatus._OK, null);
}
}