Skip to content

Commit

Permalink
Merge pull request #47 from Tico-Corp/feature-be/TICO-227-modify-logi…
Browse files Browse the repository at this point in the history
…n-signup-jwt

[FEAT] 로그인 및 회원가입 JWT 반환 로직 수정 (TICO-227)
  • Loading branch information
bu119 authored Jul 24, 2024
2 parents d1181ca + 578b084 commit a0a30a8
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.tico.pomoro_do.domain.user.dto.request.AdminJoinDTO;
import com.tico.pomoro_do.domain.user.dto.request.AdminLoginDTO;
import com.tico.pomoro_do.domain.user.dto.response.JwtDTO;
import com.tico.pomoro_do.domain.user.dto.response.TokenDTO;
import com.tico.pomoro_do.domain.user.service.AdminService;
import com.tico.pomoro_do.global.code.SuccessCode;
import com.tico.pomoro_do.global.response.SuccessResponseDTO;
Expand All @@ -13,6 +13,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
Expand All @@ -35,13 +36,14 @@ public class AdminController {
* 관리자 회원가입 API
*
* @param request AdminJoinDTO 객체
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO
* @param response HttpServletResponse 객체
* @return 성공 시 TokenDTO를 포함하는 SuccessResponseDTO
*/
@Operation(
summary = "관리자 회원가입",
description = "관리자 회원가입을 수행합니다. <br>"
+ "관리자의 이메일은 @pomorodo.shop 도메인으로 제한됩니다. <br>"
+ "성공 시에는 JwtDTO를 포함하는 SuccessResponseDTO를 반환합니다.",
+ "성공 시에는 TokenDTO를 포함하는 SuccessResponseDTO를 반환합니다.",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "AdminJoinDTO 객체",
required = true,
Expand All @@ -56,28 +58,32 @@ public class AdminController {
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class)))
})
@PostMapping("/join")
public ResponseEntity<SuccessResponseDTO<JwtDTO>> adminJoin(@RequestBody AdminJoinDTO request) {
public ResponseEntity<SuccessResponseDTO<TokenDTO>> adminJoin(
@RequestBody AdminJoinDTO request,
HttpServletResponse response
) {
log.info("관리자 회원가입 요청: {}", request.getUsername());
JwtDTO jwtResponse = adminService.adminJoin(request);
SuccessResponseDTO<JwtDTO> response = SuccessResponseDTO.<JwtDTO>builder()
TokenDTO jwtResponse = adminService.adminJoin(request, response);
SuccessResponseDTO<TokenDTO> successResponse = SuccessResponseDTO.<TokenDTO>builder()
.status(SuccessCode.ADMIN_SIGNUP_SUCCESS.getHttpStatus().value())
.message(SuccessCode.ADMIN_SIGNUP_SUCCESS.getMessage())
.data(jwtResponse)
.build();
log.info("관리자 회원가입 성공: {}", request.getUsername());
return ResponseEntity.status(HttpStatus.CREATED).body(response);
return ResponseEntity.status(HttpStatus.CREATED).body(successResponse);
}

/**
* 관리자 로그인 API
*
* @param request AdminLoginDTO 객체
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO
* @param response HttpServletResponse 객체
* @return 성공 시 TokenDTO를 포함하는 SuccessResponseDTO
*/
@Operation(
summary = "관리자 로그인",
description = "관리자 로그인을 수행합니다. <br>"
+ "성공 시에는 JwtDTO를 포함하는 SuccessResponseDTO를 반환합니다.",
+ "성공 시에는 TokenDTO를 포함하는 SuccessResponseDTO를 반환합니다.",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "AdminLoginDTO 객체",
required = true,
Expand All @@ -94,15 +100,18 @@ public ResponseEntity<SuccessResponseDTO<JwtDTO>> adminJoin(@RequestBody AdminJo
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class)))
})
@PostMapping("/login")
public ResponseEntity<SuccessResponseDTO<JwtDTO>> adminLogin(@RequestBody AdminLoginDTO request) {
public ResponseEntity<SuccessResponseDTO<TokenDTO>> adminLogin(
@RequestBody AdminLoginDTO request,
HttpServletResponse response
) {
log.info("관리자 로그인 요청: {}", request.getUsername());
JwtDTO jwtResponse = adminService.adminLogin(request);
SuccessResponseDTO<JwtDTO> response = SuccessResponseDTO.<JwtDTO>builder()
TokenDTO jwtResponse = adminService.adminLogin(request, response);
SuccessResponseDTO<TokenDTO> successResponse = SuccessResponseDTO.<TokenDTO>builder()
.status(SuccessCode.ADMIN_LOGIN_SUCCESS.getHttpStatus().value())
.message(SuccessCode.ADMIN_LOGIN_SUCCESS.getMessage())
.data(jwtResponse)
.build();
log.info("관리자 로그인 성공: {}", request.getUsername());
return ResponseEntity.ok(response);
return ResponseEntity.ok(successResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class AuthController {
* 구글 로그인 API
*
* @param googleIdTokenHeader Google-ID-Token 헤더에 포함된 구글 ID 토큰
* @param response HttpServletResponse 객체
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO
* @throws CustomException 구글 ID 토큰 검증에 실패한 경우 예외를 던집니다.
*/
Expand All @@ -76,17 +77,18 @@ public class AuthController {
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class)))
})
@PostMapping("/google/login")
public ResponseEntity<SuccessResponseDTO<JwtDTO>> googleLogin(
@RequestHeader("Google-ID-Token") String googleIdTokenHeader
public ResponseEntity<SuccessResponseDTO<TokenDTO>> googleLogin(
@RequestHeader("Google-ID-Token") String googleIdTokenHeader,
HttpServletResponse response
) {
try {
JwtDTO jwtResponse = authService.googleLogin(googleIdTokenHeader);
SuccessResponseDTO<JwtDTO> response = SuccessResponseDTO.<JwtDTO>builder()
TokenDTO jwtResponse = authService.googleLogin(googleIdTokenHeader, response);
SuccessResponseDTO<TokenDTO> successResponse = SuccessResponseDTO.<TokenDTO>builder()
.status(SuccessCode.GOOGLE_LOGIN_SUCCESS.getHttpStatus().value())
.message(SuccessCode.GOOGLE_LOGIN_SUCCESS.getMessage())
.data(jwtResponse)
.build();
return ResponseEntity.ok(response);
return ResponseEntity.ok(successResponse);
} catch (GeneralSecurityException | IOException | IllegalArgumentException e) {
log.error("구글 ID 토큰 검증 실패: {}", e.getMessage(), e);
throw new CustomException(ErrorCode.GOOGLE_TOKEN_VERIFICATION_FAILED);
Expand All @@ -98,6 +100,7 @@ public ResponseEntity<SuccessResponseDTO<JwtDTO>> googleLogin(
*
* @param googleIdTokenHeader Google-ID-Token 헤더에 포함된 구글 ID 토큰
* @param requestUserInfo 회원가입 요청 정보가 포함된 DTO
* @param response HttpServletResponse 객체
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO
* @throws CustomException 구글 ID 토큰 검증에 실패한 경우 예외를 던집니다.
*/
Expand All @@ -122,18 +125,19 @@ public ResponseEntity<SuccessResponseDTO<JwtDTO>> googleLogin(
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class)))
})
@PostMapping("/google/join")
public ResponseEntity<SuccessResponseDTO<JwtDTO>> googleJoin(
public ResponseEntity<SuccessResponseDTO<TokenDTO>> googleJoin(
@RequestHeader("Google-ID-Token") String googleIdTokenHeader,
@Valid @RequestBody GoogleJoinDTO requestUserInfo
@Valid @RequestBody GoogleJoinDTO requestUserInfo,
HttpServletResponse response
) {
try {
JwtDTO jwtResponse = authService.googleJoin(googleIdTokenHeader, requestUserInfo);
SuccessResponseDTO<JwtDTO> response = SuccessResponseDTO.<JwtDTO>builder()
TokenDTO jwtResponse = authService.googleJoin(googleIdTokenHeader, requestUserInfo, response);
SuccessResponseDTO<TokenDTO> successResponse = SuccessResponseDTO.<TokenDTO>builder()
.status(SuccessCode.GOOGLE_SIGNUP_SUCCESS.getHttpStatus().value())
.message(SuccessCode.GOOGLE_SIGNUP_SUCCESS.getMessage())
.data(jwtResponse)
.build();
return ResponseEntity.status(HttpStatus.CREATED).body(response);
return ResponseEntity.status(HttpStatus.CREATED).body(successResponse);
} catch (GeneralSecurityException | IOException | IllegalArgumentException e) {
log.error("구글 ID 토큰 검증 실패: {}", e.getMessage(), e);
throw new CustomException(ErrorCode.GOOGLE_TOKEN_VERIFICATION_FAILED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import com.tico.pomoro_do.domain.user.dto.request.AdminJoinDTO;
import com.tico.pomoro_do.domain.user.dto.request.AdminLoginDTO;
import com.tico.pomoro_do.domain.user.dto.response.JwtDTO;
import com.tico.pomoro_do.domain.user.dto.response.TokenDTO;
import jakarta.servlet.http.HttpServletResponse;

public interface AdminService {

//관리자 회원가입
JwtDTO adminJoin(AdminJoinDTO adminJoinDTO);
TokenDTO adminJoin(AdminJoinDTO adminJoinDTO, HttpServletResponse response);

//관리자 로그인
JwtDTO adminLogin(AdminLoginDTO adminLoginDTO);
TokenDTO adminLogin(AdminLoginDTO adminLoginDTO, HttpServletResponse response);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import com.tico.pomoro_do.domain.user.dto.request.AdminJoinDTO;
import com.tico.pomoro_do.domain.user.dto.request.AdminLoginDTO;
import com.tico.pomoro_do.domain.user.dto.response.JwtDTO;
import com.tico.pomoro_do.domain.user.dto.response.TokenDTO;
import com.tico.pomoro_do.domain.user.entity.User;
import com.tico.pomoro_do.domain.user.repository.UserRepository;
import com.tico.pomoro_do.global.enums.UserRole;
import com.tico.pomoro_do.global.code.ErrorCode;
import com.tico.pomoro_do.global.exception.CustomException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -30,12 +32,13 @@ public class AdminServiceImpl implements AdminService {
* 관리자 회원가입 처리
*
* @param adminJoinDTO AdminJoinDTO 객체
* @return JwtDTO를 포함하는 ResponseEntity
* @throws CustomException 이메일 도메인이 유효하지 않거나 이미 등록된 사용자인 경우 예외
* @param response HttpServletResponse 객체
* @return 성공 시 JwtDTO를 포함하는 TokenDTO 객체
* @throws CustomException 이메일 도메인이 유효하지 않거나 이미 등록된 사용자인 경우 예외를 던집니다.
*/
@Override
@Transactional
public JwtDTO adminJoin(AdminJoinDTO adminJoinDTO) {
public TokenDTO adminJoin(AdminJoinDTO adminJoinDTO, HttpServletResponse response) {
String username = adminJoinDTO.getUsername();
String nickname = adminJoinDTO.getNickname();

Expand All @@ -49,18 +52,20 @@ public JwtDTO adminJoin(AdminJoinDTO adminJoinDTO) {
// 관리자 생성하기
User admin = authService.createUser(username, nickname, "", UserRole.ADMIN);

return authService.createJwtTokens(username, String.valueOf(UserRole.ADMIN));
return authService.generateAndStoreTokens(username, String.valueOf(UserRole.ADMIN), response);
}

/**
* 관리자 로그인 처리
*
* @param adminLoginDTO AdminLoginDTO 객체
* @return JwtDTO를 포함하는 ResponseEntity
* @throws CustomException 이메일 도메인이 유효하지 않거나 관리자가 아닌 경우 예외
* @param response HttpServletResponse 객체
* @return 성공 시 JwtDTO를 포함하는 TokenDTO 객체
* @throws CustomException 이메일 도메인이 유효하지 않거나 관리자가 아닌 경우 예외를 던집니다.
*/
@Override
public JwtDTO adminLogin(AdminLoginDTO adminLoginDTO){
@Transactional
public TokenDTO adminLogin(AdminLoginDTO adminLoginDTO, HttpServletResponse response){
String username = adminLoginDTO.getUsername();
String nickname = adminLoginDTO.getNickname();

Expand All @@ -70,7 +75,7 @@ public JwtDTO adminLogin(AdminLoginDTO adminLoginDTO){
validateAdminEmailDomain(domain);
// 관리자 로그인 검증
validateAdminUser(username, nickname);
return authService.createJwtTokens(username, String.valueOf(UserRole.ADMIN));
return authService.generateAndStoreTokens(username, String.valueOf(UserRole.ADMIN), response);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ public interface AuthService {
String extractToken(String header, TokenType tokenType);

// 구글 로그인
JwtDTO googleLogin(String idTokenHeader) throws GeneralSecurityException, IOException;
TokenDTO googleLogin(String idTokenHeader, HttpServletResponse response) throws GeneralSecurityException, IOException;

// 구글 회원가입
JwtDTO googleJoin(String idTokenHeader, GoogleJoinDTO request) throws GeneralSecurityException, IOException;
TokenDTO googleJoin(String idTokenHeader, GoogleJoinDTO request, HttpServletResponse response) throws GeneralSecurityException, IOException;

// User 생성
User createUser(String username, String nickname, String profileImageUrl, UserRole role);

// 토큰 생성
JwtDTO createJwtTokens(String email, String role);
TokenDTO generateAndStoreTokens(String username, String role, HttpServletResponse response);

// Refresh 토큰으로 Access토큰 발급
TokenDTO reissueToken(HttpServletRequest request, HttpServletResponse response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,6 @@ public GoogleUserInfoDTO verifyGoogleIdToken(String idToken) throws GeneralSecur
}
}

/**
* Access 토큰, Refresh 토큰 발급
*
* @param email 사용자 이메일
* @param role 사용자 역할
* @return JwtDTO 객체
*/
@Override
public JwtDTO createJwtTokens(String email, String role) {
//토큰 생성 (카테고리, 유저이름, 역할, 만료시간)
log.info("Access 토큰 및 Refresh 토큰 생성: 이메일 = {}, 역할 = {}", email, role);
String accessToken = jwtUtil.createJwt("access", email, role, accessExpiration); //10분
String refreshToken = jwtUtil.createJwt("refresh", email, role, refreshExpiration); //24시간
return new JwtDTO(accessToken, refreshToken);
}

/**
* 헤더에서 토큰 값을 추출
*
Expand All @@ -128,18 +112,44 @@ public String extractToken(String header, TokenType tokenType) {
return header.substring(7);
}

/**
* 사용자 인증을 위한 액세스 토큰과 리프레시 토큰을 생성하고 저장
*
* @param username 사용자 이메일
* @param role 사용자 역할
* @param response HttpServletResponse 객체, 생성된 리프레시 토큰을 쿠키로 추가하기 위해 사용됨
* @return TokenDTO 객체, 생성된 액세스 토큰을 포함
*/
@Override
@Transactional
public TokenDTO generateAndStoreTokens(String username, String role, HttpServletResponse response) {
log.info("Access 토큰 및 Refresh 토큰 생성: 이메일 = {}, 역할 = {}", username, role);

// 액세스 토큰 생성
String accessToken = jwtUtil.createJwt("access", username, role, accessExpiration); // 60분
// 리프레시 토큰 생성
String refreshToken = jwtUtil.createJwt("refresh", username, role, refreshExpiration); // 24시간
// 리프레시 토큰을 DB에 저장
tokenService.addRefreshEntity(username, refreshToken, refreshExpiration);
// 리프레시 토큰을 쿠키로 응답에 추가
response.addCookie(CookieUtil.createCookie("refresh", refreshToken));

return new TokenDTO(accessToken);
}

/**
* 구글 ID 토큰으로 로그인 처리
*
* @param idTokenHeader Google-ID-Token 헤더에 포함된 구글 ID 토큰
* @return JwtDTO를 포함하는 ResponseEntity
* @param response HttpServletResponse 객체
* @return TokenDTO를 포함하는 객체
* @throws GeneralSecurityException 구글 ID 토큰 검증 중 발생하는 보안 예외
* @throws IOException IO 예외
* @throws CustomException 구글 ID 토큰이 유효하지 않거나 사용자가 등록되어 있지 않은 경우 예외
* @throws CustomException 구글 ID 토큰이 유효하지 않거나 등록되지 않은 사용자인 경우 예외
*/
@Override
@Transactional
public JwtDTO googleLogin(String idTokenHeader) throws GeneralSecurityException, IOException {
public TokenDTO googleLogin(String idTokenHeader, HttpServletResponse response) throws GeneralSecurityException, IOException {
log.info("구글 로그인 처리 시작");

String idToken = extractToken(idTokenHeader, TokenType.GOOGLE);
Expand All @@ -151,22 +161,23 @@ public JwtDTO googleLogin(String idTokenHeader) throws GeneralSecurityException,
}

log.info("구글 로그인 성공: 이메일 = {}", userInfo.getEmail());
return createJwtTokens(userInfo.getEmail(), String.valueOf(UserRole.USER));
return generateAndStoreTokens(userInfo.getEmail(), String.valueOf(UserRole.USER), response);
}

/**
* 구글 ID 토큰으로 회원가입 처리
*
* @param idTokenHeader Google-ID-Token 헤더에 포함된 구글 ID 토큰
* @param requestUserInfo GoogleJoinDTO 객체
* @return JwtDTO를 포함하는 ResponseEntity
* @param response HttpServletResponse 객체
* @return TokenDTO를 포함하는 객체
* @throws GeneralSecurityException 구글 ID 토큰 검증 중 발생하는 보안 예외
* @throws IOException IO 예외
* @throws CustomException 구글 ID 토큰이 유효하지 않거나 이미 등록된 사용자인 경우 예외
*/
@Override
@Transactional
public JwtDTO googleJoin(String idTokenHeader, GoogleJoinDTO requestUserInfo) throws GeneralSecurityException, IOException {
public TokenDTO googleJoin(String idTokenHeader, GoogleJoinDTO requestUserInfo, HttpServletResponse response) throws GeneralSecurityException, IOException {
log.info("구글 회원가입 처리 시작");

String idToken = extractToken(idTokenHeader, TokenType.GOOGLE);
Expand Down Expand Up @@ -194,7 +205,7 @@ public JwtDTO googleJoin(String idTokenHeader, GoogleJoinDTO requestUserInfo) th
socialLoginRepository.save(socialLogin);

log.info("구글 회원가입 성공: 이메일 = {}", userInfo.getEmail());
return createJwtTokens(userInfo.getEmail(), String.valueOf(UserRole.USER));
return generateAndStoreTokens(userInfo.getEmail(), String.valueOf(UserRole.USER), response);
}

/**
Expand Down

0 comments on commit a0a30a8

Please sign in to comment.