diff --git a/src/main/java/com/genius/herewe/business/crew/controller/CrewApi.java b/src/main/java/com/genius/herewe/business/crew/controller/CrewApi.java index 081cda4..093d6b2 100644 --- a/src/main/java/com/genius/herewe/business/crew/controller/CrewApi.java +++ b/src/main/java/com/genius/herewe/business/crew/controller/CrewApi.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestParam; import com.genius.herewe.business.crew.dto.CrewCreateRequest; +import com.genius.herewe.business.crew.dto.CrewLeaderTransferRequest; import com.genius.herewe.business.crew.dto.CrewMemberResponse; import com.genius.herewe.business.crew.dto.CrewModifyRequest; import com.genius.herewe.business.crew.dto.CrewPreviewResponse; @@ -418,7 +419,7 @@ CommonResponse inviteCrew( { "resultCode": 400, "code": "LEADER_CANNOT_EXPEL", - "message"; "CREW LEADER는 CREW에서 탈퇴할 수 없습니다." + "message": "CREW LEADER는 CREW에서 탈퇴할 수 없습니다." } """ ) @@ -435,7 +436,7 @@ CommonResponse inviteCrew( { "resultCode": 403, "code": "LEADER_PERMISSION_DENIED", - "message"; "CREW LEADER의 권한이 필요합니다." + "message": "CREW LEADER의 권한이 필요합니다." } """ ) @@ -457,7 +458,7 @@ CommonResponse inviteCrew( { "resultCode": 404, "code": "MEMBER_NOT_FOUND", - "message"; "사용자를 찾을 수 없습니다." + "message": "사용자를 찾을 수 없습니다." } """ ), @@ -467,7 +468,7 @@ CommonResponse inviteCrew( { "resultCode": 404, "code": "CREW_NOT_FOUND", - "message"; "해당 CREW를 찾을 수 없습니다." + "message": "해당 CREW를 찾을 수 없습니다." } """ ), @@ -477,7 +478,7 @@ CommonResponse inviteCrew( { "resultCode": 404, "code": "MEMBER_NOT_FOUND", - "message"; "사용자를 찾을 수 없습니다." + "message": "사용자를 찾을 수 없습니다." } """ ) @@ -488,5 +489,66 @@ CommonResponse inviteCrew( CommonResponse expelCrew( @HereWeUser User user, @PathVariable Long crewId, @RequestParam(name = "nickname") String nickname); + + @Operation(summary = "크루 리더 양도 API", description = "크루 멤버에 대하여 크루 리더를 양도하는 API") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "크루 리더 양도 성공" + ), + @ApiResponse( + responseCode = "403", + description = """ + 1. 요청자가 크루 리더가 아닌 경우 + 2. 대상자가 크루 멤버가 아닌 경우 + """, + content = @Content( + schema = @Schema(implementation = ExceptionResponse.class), + examples = { + @ExampleObject( + name = "1. 요청자가 크루 리더가 아닌 경우", + value = """ + { + "resultCode": 403, + "code": "LEADER_PERMISSION_DENIED", + "message": "CREW LEADER의 권한이 필요합니다." + } + """ + ), + @ExampleObject( + name = "2. 대상자가 크루 멤버가 아닌 경우", + value = """ + { + "resultCode": 403, + "code": "CREW_MEMBERSHIP_REQUIRED", + "message": "크루 멤버에게만 허용된 작업입니다. 크루에 참여 후 재시도해주세요." + } + """ + ) + } + ) + ), + @ApiResponse( + responseCode = "404", + description = "닉네임을 통해 대상자를 찾을 수 없는 경우", + content = @Content( + schema = @Schema(implementation = ExceptionResponse.class), + examples = { + @ExampleObject( + name = "닉네임을 통해 대상자를 찾을 수 없는 경우", + value = """ + { + "resultCode": 404, + "code": "MEMBER_NOT_FOUND", + "message": "사용자를 찾을 수 없습니다." + } + """ + ) + } + ) + ) + }) + CommonResponse handOverLeader(@HereWeUser User user, @PathVariable Long crewId, + @RequestBody @Valid CrewLeaderTransferRequest transferRequest); } diff --git a/src/main/java/com/genius/herewe/business/crew/controller/CrewController.java b/src/main/java/com/genius/herewe/business/crew/controller/CrewController.java index babbd67..cbf4959 100644 --- a/src/main/java/com/genius/herewe/business/crew/controller/CrewController.java +++ b/src/main/java/com/genius/herewe/business/crew/controller/CrewController.java @@ -17,6 +17,7 @@ import com.genius.herewe.business.crew.dto.CrewCreateRequest; import com.genius.herewe.business.crew.dto.CrewExpelRequest; import com.genius.herewe.business.crew.dto.CrewIdResponse; +import com.genius.herewe.business.crew.dto.CrewLeaderTransferRequest; import com.genius.herewe.business.crew.dto.CrewMemberResponse; import com.genius.herewe.business.crew.dto.CrewModifyRequest; import com.genius.herewe.business.crew.dto.CrewPreviewResponse; @@ -43,7 +44,7 @@ public class CrewController implements CrewApi { @GetMapping("/profile/{crewId}") public SingleResponse inquiryCrewProfile(@HereWeUser User user, - @PathVariable Long crewId) { + @PathVariable Long crewId) { CrewProfileResponse crewProfileResponse = crewFacade.inquiryCrewProfile(user.getId(), crewId); return new SingleResponse<>(HttpStatus.OK, crewProfileResponse); } @@ -126,4 +127,19 @@ public CommonResponse expelCrew( return CommonResponse.ok(); } + + @DeleteMapping("/{crewId}/members/me") + public CommonResponse quitCrew(@HereWeUser User user, + @PathVariable Long crewId) { + crewFacade.quitCrew(user.getId(), crewId); + return CommonResponse.ok(); + } + + @PatchMapping("/{crewId}/members/leader") + public CommonResponse handOverLeader(@HereWeUser User user, @PathVariable Long crewId, + @RequestBody @Valid CrewLeaderTransferRequest transferRequest) { + + crewFacade.handoverLeader(crewId, user.getId(), transferRequest.nickname()); + return CommonResponse.ok(); + } } diff --git a/src/main/java/com/genius/herewe/business/crew/domain/CrewMember.java b/src/main/java/com/genius/herewe/business/crew/domain/CrewMember.java index 518b616..464a6a6 100644 --- a/src/main/java/com/genius/herewe/business/crew/domain/CrewMember.java +++ b/src/main/java/com/genius/herewe/business/crew/domain/CrewMember.java @@ -51,6 +51,11 @@ public static CrewMember createByRole(CrewRole role) { return new CrewMember(role, LocalDate.now()); } + //== 비지니스 로직 ==// + public void updateRole(CrewRole crewRole) { + this.role = crewRole; + } + //== 연관관계 편의 메서드 ==// public void joinCrew(User user, Crew crew) { this.user = user; diff --git a/src/main/java/com/genius/herewe/business/crew/dto/CrewLeaderTransferRequest.java b/src/main/java/com/genius/herewe/business/crew/dto/CrewLeaderTransferRequest.java new file mode 100644 index 0000000..030a83c --- /dev/null +++ b/src/main/java/com/genius/herewe/business/crew/dto/CrewLeaderTransferRequest.java @@ -0,0 +1,9 @@ +package com.genius.herewe.business.crew.dto; + +import jakarta.validation.constraints.NotBlank; + +public record CrewLeaderTransferRequest( + @NotBlank + String nickname +) { +} diff --git a/src/main/java/com/genius/herewe/business/crew/facade/CrewFacade.java b/src/main/java/com/genius/herewe/business/crew/facade/CrewFacade.java index 3dd4396..18fdda2 100644 --- a/src/main/java/com/genius/herewe/business/crew/facade/CrewFacade.java +++ b/src/main/java/com/genius/herewe/business/crew/facade/CrewFacade.java @@ -27,4 +27,8 @@ public interface CrewFacade { void deleteCrew(Long crewId); void expelCrew(Long userId, CrewExpelRequest expelRequest); + + void quitCrew(Long userId, Long crewId); + + void handoverLeader(Long crewId, Long userId, String targetNickname); } diff --git a/src/main/java/com/genius/herewe/business/crew/facade/DefaultCrewFacade.java b/src/main/java/com/genius/herewe/business/crew/facade/DefaultCrewFacade.java index 47e18fc..7325bdf 100644 --- a/src/main/java/com/genius/herewe/business/crew/facade/DefaultCrewFacade.java +++ b/src/main/java/com/genius/herewe/business/crew/facade/DefaultCrewFacade.java @@ -2,6 +2,8 @@ import static com.genius.herewe.core.global.exception.ErrorCode.*; +import java.util.Objects; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -125,4 +127,40 @@ public void expelCrew(Long userId, CrewExpelRequest expelRequest) { crewMemberService.delete(crewMember); crew.updateParticipantCount(-1); } + + @Override + @Transactional + public void quitCrew(Long userId, Long crewId) { + User user = userService.findById(userId); + Crew crew = crewService.findById(crewId); + + CrewMember crewMember = crewMemberService.find(userId, crewId); + if (crewMember.getRole() == CrewRole.LEADER) { + throw new BusinessException(LEADER_CANNOT_EXPEL); + } + + crewMemberService.delete(crewMember); + crew.updateParticipantCount(-1); + } + + @Override + @Transactional + public void handoverLeader(Long crewId, Long userId, String targetNickname) { + User leader = userService.findById(userId); + User newLeader = userService.findByNickname(targetNickname) + .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); + + CrewMember originLeaderJoinInfo = crewMemberService.find(leader.getId(), crewId); + if (originLeaderJoinInfo.getRole() != CrewRole.LEADER) { + throw new BusinessException(LEADER_PERMISSION_DENIED); + } + if (Objects.equals(leader.getId(), newLeader.getId())) { + throw new BusinessException(LEADER_ALREADY_ASSIGNED); + } + CrewMember newLeaderJoinInfo = crewMemberService.findOptional(newLeader.getId(), crewId) + .orElseThrow(() -> new BusinessException(CREW_MEMBERSHIP_REQUIRED)); + + originLeaderJoinInfo.updateRole(CrewRole.MEMBER); + newLeaderJoinInfo.updateRole(CrewRole.LEADER); + } } diff --git a/src/main/java/com/genius/herewe/core/global/exception/ErrorCode.java b/src/main/java/com/genius/herewe/core/global/exception/ErrorCode.java index be3db40..678f822 100644 --- a/src/main/java/com/genius/herewe/core/global/exception/ErrorCode.java +++ b/src/main/java/com/genius/herewe/core/global/exception/ErrorCode.java @@ -28,6 +28,7 @@ public enum ErrorCode { CREW_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 CREW를 찾을 수 없습니다."), LEADER_PERMISSION_DENIED(HttpStatus.FORBIDDEN, "CREW LEADER의 권한이 필요합니다."), LEADER_CANNOT_EXPEL(HttpStatus.BAD_REQUEST, "CREW LEADER는 CREW에서 탈퇴할 수 없습니다."), + LEADER_ALREADY_ASSIGNED(HttpStatus.BAD_REQUEST, "이미 LEADER인 사용자에 대해서는 LEADER 양도가 불가능합니다."), CREW_JOIN_INFO_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 크루에 대한 참여 정보가 없습니다."), ALREADY_JOINED_CREW(HttpStatus.BAD_REQUEST, "이미 참여한 크루입니다."), @@ -54,10 +55,10 @@ public enum ErrorCode { JWT_NOT_VALID(HttpStatus.UNAUTHORIZED, "JWT가 유효하지 않습니다."), JWT_NOT_FOUND_IN_HEADER(HttpStatus.UNAUTHORIZED, "Header에서 JWT를 찾을 수 없습니다."), JWT_NOT_FOUND_IN_COOKIE(HttpStatus.UNAUTHORIZED, "Cookie에서 JWT를 찾을 수 없습니다."), - REFRESH_NOT_FOUND_IN_DB(HttpStatus.NOT_FOUND, "DB에서 사용자의 Refresh token 정보를 찾을 수 없습니다."), + REFRESH_NOT_FOUND_IN_DB(HttpStatus.UNAUTHORIZED, "DB에서 사용자의 Refresh token 정보를 찾을 수 없습니다."), TOKEN_HIJACKED(HttpStatus.UNAUTHORIZED, "토큰 탈취가 감지되었습니다. 다시 로그인해주세요."), - REGISTRATION_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "회원가입 토큰을 찾을 수 없습니다."), + TOKEN_NOT_FOUND_IN_REDIS(HttpStatus.UNAUTHORIZED, "Redis에서 저장되어 있는 토큰을 찾을 수 없습니다."), FILE_NOT_EXIST(HttpStatus.NOT_FOUND, "해당 파일이 존재하지 않습니다."), FILE_INVALID(HttpStatus.BAD_REQUEST, "파일의 형태가 유효하지 않습니다."), diff --git a/src/main/java/com/genius/herewe/core/security/config/SecurityConfig.java b/src/main/java/com/genius/herewe/core/security/config/SecurityConfig.java index 71dc24f..db695f5 100644 --- a/src/main/java/com/genius/herewe/core/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/herewe/core/security/config/SecurityConfig.java @@ -19,7 +19,7 @@ import com.genius.herewe.core.security.handler.OAuth2FailureHandler; import com.genius.herewe.core.security.handler.OAuth2SuccessHandler; import com.genius.herewe.core.security.service.CustomOAuth2Service; -import com.genius.herewe.core.security.service.JwtFacade; +import com.genius.herewe.core.security.service.JwtManager; import lombok.RequiredArgsConstructor; @@ -37,7 +37,7 @@ public class SecurityConfig { private final OAuth2SuccessHandler oAuth2SuccessHandler; private final OAuth2FailureHandler oAuth2FailureHandler; - private final JwtFacade jwtFacade; + private final JwtManager jwtManager; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -59,7 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // JWT .sessionManagement(configurer -> configurer .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .addFilterBefore(new JwtAuthenticationFilter(jwtFacade), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JwtAuthenticationFilter(jwtManager), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new AuthenticationExceptionFilter(), JwtAuthenticationFilter.class) // OAuth2 diff --git a/src/main/java/com/genius/herewe/core/security/controller/AuthApi.java b/src/main/java/com/genius/herewe/core/security/controller/AuthApi.java index 61a6368..3735824 100644 --- a/src/main/java/com/genius/herewe/core/security/controller/AuthApi.java +++ b/src/main/java/com/genius/herewe/core/security/controller/AuthApi.java @@ -2,7 +2,9 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import com.genius.herewe.core.global.response.CommonResponse; import com.genius.herewe.core.global.response.ExceptionResponse; import com.genius.herewe.core.global.response.SingleResponse; import com.genius.herewe.core.security.dto.AuthRequest; @@ -15,6 +17,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.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @Tag(name = "Auth API", description = "인증/인가 관련 API") @@ -47,4 +50,98 @@ public interface AuthApi { @PostMapping("/auth") SingleResponse authorize(HttpServletResponse response, @RequestBody AuthRequest authRequest); + + @Operation(summary = "JWT 재발급 API", description = "JWT 재발급 API, refresh-token이 유효하다면 JWT를 재발급합니다.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "JWT 재발급 성공" + ), + @ApiResponse( + responseCode = "401", + description = """ + 1. refresh-token을 Cookie에서 찾을 수 없는 경우 + 2. refresh-token 자체가 유효하지 않은 경우 + 3. Redis에서 해당 사용자의 토큰을 찾을 수 없는 경우 + 4. refresh-token이 redis에 저장되어 있던 토큰 정보와 일치하지 않는 경우 + """, + content = @Content( + schema = @Schema(implementation = ExceptionResponse.class), + examples = { + @ExampleObject( + name = "1. refresh-token을 Cookie에서 찾을 수 없는 경우", + value = """ + { + "resultCode": "401", + "code": "JWT_NOT_FOUND_IN_COOKIE", + "message": "Cookie에서 JWT를 찾을 수 없습니다." + } + """ + ), + @ExampleObject( + name = "2. refresh-token 자체가 유효하지 않은 경우", + value = """ + { + "resultCode": "401", + "code": "JWT_NOT_VALID", + "message": "JWT가 유효하지 않습니다." + } + """ + ), + @ExampleObject( + name = "3. Redis에서 해당 사용자의 토큰을 찾을 수 없는 경우", + value = """ + { + "resultCode": "401", + "code": "TOKEN_NOT_FOUND_IN_REDIS", + "message": "Redis에서 저장되어 있는 토큰을 찾을 수 없습니다." + } + """ + ), + @ExampleObject( + name = "4. refresh-token이 redis에 저장되어 있던 토큰 정보와 일치하지 않는 경우", + value = """ + { + "resultCode": "401", + "code": "TOKEN_HIJACKED", + "message": "토큰 탈취가 감지되었습니다. 다시 로그인해주세요." + } + """ + ) + } + ) + ) + }) + SingleResponse reissueToken(HttpServletRequest request, HttpServletResponse response); + + @Operation(summary = "로그아웃 API", description = "로그아웃 처리를 하는 API로서, redis & cookie에서 토큰 정보 삭제") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "로그아웃 처리 성공" + ), + @ApiResponse( + responseCode = "404", + description = "해당 닉네임을 사용하고 있는 사용자를 찾지 못한 경우", + content = @Content( + schema = @Schema(implementation = ExceptionResponse.class), + examples = { + @ExampleObject( + name = "해당 닉네임을 사용하고 있는 사용자를 찾지 못한 경우", + value = """ + { + "resultCode": "404", + "code": "MEMBER_NOT_FOUND", + "message": "사용자를 찾을 수 없습니다." + } + """ + ) + } + ) + ) + }) + CommonResponse logout(HttpServletResponse response, @RequestParam String nickname); + + @Operation(summary = "health check 전용 API", description = "서버가 제대로 동작하는지 확인하는 API") + String healthCheck(); } diff --git a/src/main/java/com/genius/herewe/core/security/controller/AuthController.java b/src/main/java/com/genius/herewe/core/security/controller/AuthController.java index eed955f..db316be 100644 --- a/src/main/java/com/genius/herewe/core/security/controller/AuthController.java +++ b/src/main/java/com/genius/herewe/core/security/controller/AuthController.java @@ -5,15 +5,17 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import com.genius.herewe.core.global.response.CommonResponse; import com.genius.herewe.core.global.response.SingleResponse; import com.genius.herewe.core.security.dto.AuthRequest; import com.genius.herewe.core.security.dto.AuthResponse; -import com.genius.herewe.core.security.service.JwtFacade; -import com.genius.herewe.core.user.domain.User; +import com.genius.herewe.core.security.facade.AuthFacade; import com.genius.herewe.core.user.facade.UserFacade; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -22,22 +24,29 @@ @RequestMapping("/api") public class AuthController implements AuthApi { public final UserFacade userFacade; - public final JwtFacade jwtFacade; + private final AuthFacade authFacade; @PostMapping("/auth") public SingleResponse authorize(HttpServletResponse response, @RequestBody AuthRequest authRequest) { + Long userId = authFacade.authorize(response, authRequest); + AuthResponse authResponse = userFacade.getAuthInfo(userId); + return new SingleResponse<>(HttpStatus.OK, authResponse); + } - User user = userFacade.findUser(authRequest.userId()); - - jwtFacade.generateAccessToken(response, user); - jwtFacade.generateRefreshToken(response, user); - jwtFacade.setReissuedHeader(response); - - AuthResponse authResponse = userFacade.getAuthInfo(user.getId()); + @PostMapping("/auth/reissue") + public SingleResponse reissueToken(HttpServletRequest request, HttpServletResponse response) { + Long userId = authFacade.reissueToken(request, response); + AuthResponse authResponse = userFacade.getAuthInfo(userId); return new SingleResponse<>(HttpStatus.OK, authResponse); } + @PostMapping("/auth/logout") + public CommonResponse logout(HttpServletResponse response, @RequestParam String nickname) { + authFacade.logout(response, nickname); + return CommonResponse.ok(); + } + @GetMapping("/auth/health-check") public String healthCheck() { return "health check OK"; diff --git a/src/main/java/com/genius/herewe/core/security/domain/TokenType.java b/src/main/java/com/genius/herewe/core/security/domain/TokenType.java index 7b65dd7..5ddb24f 100644 --- a/src/main/java/com/genius/herewe/core/security/domain/TokenType.java +++ b/src/main/java/com/genius/herewe/core/security/domain/TokenType.java @@ -1,6 +1,14 @@ package com.genius.herewe.core.security.domain; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum TokenType { - REFRESH_TOKEN, - REGISTRATION_TOKEN + REFRESH_TOKEN("refresh_token:"), + REGISTRATION_TOKEN("registration_token:"), + AUTH_TOKEN("auth_token:"); + + private final String PREFIX; } diff --git a/src/main/java/com/genius/herewe/core/security/dto/AuthRequest.java b/src/main/java/com/genius/herewe/core/security/dto/AuthRequest.java index 4dd6e81..91bfd89 100644 --- a/src/main/java/com/genius/herewe/core/security/dto/AuthRequest.java +++ b/src/main/java/com/genius/herewe/core/security/dto/AuthRequest.java @@ -5,6 +5,8 @@ @Schema(description = "인가 요청 시 필요한 사용자 정보") public record AuthRequest( @Schema(description = "사용자 식별자(PK)", example = "1") - Long userId + Long userId, + @Schema(description = "인가 토큰", example = "7053658f-b32b-4fe1-84da-73d654907b12") + String token ) { } diff --git a/src/main/java/com/genius/herewe/core/security/facade/AuthFacade.java b/src/main/java/com/genius/herewe/core/security/facade/AuthFacade.java new file mode 100644 index 0000000..a7e1e34 --- /dev/null +++ b/src/main/java/com/genius/herewe/core/security/facade/AuthFacade.java @@ -0,0 +1,14 @@ +package com.genius.herewe.core.security.facade; + +import com.genius.herewe.core.security.dto.AuthRequest; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface AuthFacade { + Long authorize(HttpServletResponse response, AuthRequest authRequest); + + Long reissueToken(HttpServletRequest request, HttpServletResponse response); + + void logout(HttpServletResponse response, String nickname); +} diff --git a/src/main/java/com/genius/herewe/core/security/facade/DefaultAuthFacade.java b/src/main/java/com/genius/herewe/core/security/facade/DefaultAuthFacade.java new file mode 100644 index 0000000..dde0d30 --- /dev/null +++ b/src/main/java/com/genius/herewe/core/security/facade/DefaultAuthFacade.java @@ -0,0 +1,75 @@ +package com.genius.herewe.core.security.facade; + +import static com.genius.herewe.core.global.exception.ErrorCode.*; +import static com.genius.herewe.core.security.constants.JwtRule.*; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.genius.herewe.core.global.exception.BusinessException; +import com.genius.herewe.core.security.dto.AuthRequest; +import com.genius.herewe.core.security.service.JwtManager; +import com.genius.herewe.core.security.service.token.AuthTokenService; +import com.genius.herewe.core.security.service.token.RefreshTokenService; +import com.genius.herewe.core.user.domain.User; +import com.genius.herewe.core.user.service.UserService; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class DefaultAuthFacade implements AuthFacade { + private final JwtManager jwtManager; + private final UserService userService; + private final AuthTokenService authTokenService; + private final RefreshTokenService refreshTokenService; + + @Transactional + public Long authorize(HttpServletResponse response, AuthRequest authRequest) { + Long userId = authRequest.userId(); + authTokenService.validateAuthToken(userId, authRequest.token()); + User user = userService.findById(userId); + + jwtManager.generateAccessToken(response, user); + jwtManager.generateRefreshToken(response, user); + jwtManager.setReissuedHeader(response); + + authTokenService.deleteAuthToken(userId); + return userId; + } + + public Long reissueToken(HttpServletRequest request, HttpServletResponse response) { + String refreshToken = jwtManager.resolveRefreshToken(request); + jwtManager.verifyRefreshToken(refreshToken); + User user = jwtManager.getPKFromRefresh(refreshToken); + + boolean isHijacked = jwtManager.isRefreshHijacked(user.getId(), refreshToken); + if (isHijacked) { + jwtManager.logout(response, user.getId()); + throw new BusinessException(TOKEN_HIJACKED); + } + + String reissuedAccessToken = jwtManager.generateAccessToken(response, user); + jwtManager.generateRefreshToken(response, user); + jwtManager.setReissuedHeader(response); + + return user.getId(); + } + + @Override + public void logout(HttpServletResponse response, String nickname) { + User user = userService.findByNickname(nickname) + .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); + + Cookie cookie = new Cookie(REFRESH_PREFIX.getValue(), null); + cookie.setMaxAge(0); + cookie.setPath("/"); + response.addCookie(cookie); + + refreshTokenService.delete(user.getId()); + } +} diff --git a/src/main/java/com/genius/herewe/core/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/herewe/core/security/filter/JwtAuthenticationFilter.java index 6f3dd64..3098803 100644 --- a/src/main/java/com/genius/herewe/core/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/herewe/core/security/filter/JwtAuthenticationFilter.java @@ -13,7 +13,7 @@ import com.genius.herewe.core.global.exception.BusinessException; import com.genius.herewe.core.security.constants.JwtStatus; -import com.genius.herewe.core.security.service.JwtFacade; +import com.genius.herewe.core.security.service.JwtManager; import com.genius.herewe.core.user.domain.User; import jakarta.servlet.FilterChain; @@ -26,7 +26,7 @@ @Slf4j @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtFacade jwtFacade; + private final JwtManager jwtManager; @Override protected void doFilterInternal(HttpServletRequest request, @@ -39,8 +39,8 @@ protected void doFilterInternal(HttpServletRequest request, return; } - String accessToken = jwtFacade.resolveAccessToken(request); - JwtStatus accessStatus = jwtFacade.verifyAccessToken(accessToken); + String accessToken = jwtManager.resolveAccessToken(request); + JwtStatus accessStatus = jwtManager.verifyAccessToken(accessToken); if (accessStatus == VALID) { setAuthenticationToContext(accessToken); @@ -48,26 +48,26 @@ protected void doFilterInternal(HttpServletRequest request, return; } - String refreshToken = jwtFacade.resolveRefreshToken(request); - jwtFacade.verifyRefreshToken(refreshToken); - User user = jwtFacade.getPKFromRefresh(refreshToken); + String refreshToken = jwtManager.resolveRefreshToken(request); + jwtManager.verifyRefreshToken(refreshToken); + User user = jwtManager.getPKFromRefresh(refreshToken); - boolean isHijacked = jwtFacade.isRefreshHijacked(user.getId(), refreshToken); + boolean isHijacked = jwtManager.isRefreshHijacked(user.getId(), refreshToken); if (isHijacked) { - jwtFacade.logout(response, user.getId()); + jwtManager.logout(response, user.getId()); throw new BusinessException(TOKEN_HIJACKED); } - String reissuedAccessToken = jwtFacade.generateAccessToken(response, user); - jwtFacade.generateRefreshToken(response, user); - jwtFacade.setReissuedHeader(response); + String reissuedAccessToken = jwtManager.generateAccessToken(response, user); + jwtManager.generateRefreshToken(response, user); + jwtManager.setReissuedHeader(response); setAuthenticationToContext(reissuedAccessToken); filterChain.doFilter(request, response); } private void setAuthenticationToContext(String accessToken) { - Authentication authentication = jwtFacade.createAuthentication(accessToken); + Authentication authentication = jwtManager.createAuthentication(accessToken); SecurityContextHolder.getContext().setAuthentication(authentication); } diff --git a/src/main/java/com/genius/herewe/core/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/herewe/core/security/handler/OAuth2SuccessHandler.java index d9e3c1d..5cd3fdb 100644 --- a/src/main/java/com/genius/herewe/core/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/herewe/core/security/handler/OAuth2SuccessHandler.java @@ -1,5 +1,7 @@ package com.genius.herewe.core.security.handler; +import static com.genius.herewe.core.security.domain.TokenType.*; + import java.io.IOException; import java.net.URI; @@ -12,7 +14,7 @@ import com.genius.herewe.core.global.exception.BusinessException; import com.genius.herewe.core.global.exception.ErrorCode; import com.genius.herewe.core.security.domain.UserPrincipal; -import com.genius.herewe.core.security.service.token.RegistrationTokenService; +import com.genius.herewe.core.security.service.token.AuthTokenService; import com.genius.herewe.core.user.domain.ProviderInfo; import com.genius.herewe.core.user.domain.Role; import com.genius.herewe.core.user.domain.User; @@ -27,15 +29,15 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler private final String SIGNUP_URL; private final String AUTH_URL; private final UserRepository userRepository; - private final RegistrationTokenService registrationTokenService; + private final AuthTokenService authTokenService; public OAuth2SuccessHandler(@Value("${url.base}") String BASE_URL, @Value("${url.path.signup}") String SIGN_UP_PATH, @Value("${url.path.auth}") String AUTH_PATH, UserRepository userRepository, - RegistrationTokenService registrationTokenService) { + AuthTokenService authTokenService) { this.userRepository = userRepository; - this.registrationTokenService = registrationTokenService; + this.authTokenService = authTokenService; this.SIGNUP_URL = BASE_URL + SIGN_UP_PATH; this.AUTH_URL = BASE_URL + AUTH_PATH; } @@ -51,21 +53,24 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo User user = userRepository.findByOAuth2Info(email, providerInfo) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - String registrationToken = registrationTokenService.generateTokenForUser(user.getId()); - - String redirectUrl = getRedirectUrlByRole(user.getRole(), user.getId(), registrationToken); + String redirectUrl = getRedirectUrlByRole(user); getRedirectStrategy().sendRedirect(request, response, redirectUrl); } - private String getRedirectUrlByRole(Role role, Long userId, String token) { + private String getRedirectUrlByRole(User user) { + Role role = user.getRole(); if (role == Role.NOT_REGISTERED) { + String token = authTokenService.generateTokenForUser(user.getId(), REGISTRATION_TOKEN); return UriComponentsBuilder.fromUri(URI.create(SIGNUP_URL)) .queryParam("token", token) .build() .toUriString(); } + + String token = authTokenService.generateTokenForUser(user.getId(), AUTH_TOKEN); return UriComponentsBuilder.fromUri(URI.create(AUTH_URL)) - .queryParam("id", userId) + .queryParam("userId", user.getId()) + .queryParam("token", token) .build() .toUriString(); } diff --git a/src/main/java/com/genius/herewe/core/security/repository/TokenRepository.java b/src/main/java/com/genius/herewe/core/security/repository/TokenRepository.java index 762d71c..33e3e56 100644 --- a/src/main/java/com/genius/herewe/core/security/repository/TokenRepository.java +++ b/src/main/java/com/genius/herewe/core/security/repository/TokenRepository.java @@ -1,5 +1,7 @@ package com.genius.herewe.core.security.repository; +import static com.genius.herewe.core.security.domain.TokenType.*; + import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -16,16 +18,18 @@ @Repository @RequiredArgsConstructor public class TokenRepository { - public static final String REFRESH_TOKEN_PREFIX = "refresh_token:"; - public static final String REGISTRATION_TOKEN_PREFIX = "registration_token:"; private final RedisTemplate redisTemplate; public Optional findRefreshToken(Long userId) { - return findByKey(REFRESH_TOKEN_PREFIX + userId); + return findByKey(REFRESH_TOKEN.getPREFIX() + userId); } public Optional findRegistrationToken(String uuidToken) { - return findByKey(REGISTRATION_TOKEN_PREFIX + uuidToken); + return findByKey(REGISTRATION_TOKEN.getPREFIX() + uuidToken); + } + + public Optional findAuthToken(Long userId) { + return findByKey(AUTH_TOKEN.getPREFIX() + userId); } private Optional findByKey(String key) { @@ -37,7 +41,7 @@ private Optional findByKey(String key) { } public Token saveRefreshToken(Token token) { - String key = REFRESH_TOKEN_PREFIX + token.getUserId(); + String key = REFRESH_TOKEN.getPREFIX() + token.getUserId(); Map map = new HashMap<>(); map.put("userId", String.valueOf(token.getUserId())); @@ -51,20 +55,37 @@ public Token saveRefreshToken(Token token) { return token; } + public Token saveAuthToken(String tokenValue, Long userId, long ttl) { + String key = AUTH_TOKEN.getPREFIX() + userId; + + Map map = new HashMap<>(); + map.put("userId", String.valueOf(userId)); + map.put("token", tokenValue); + map.put("tokenType", AUTH_TOKEN.name()); + + saveToRedis(key, map, ttl); + return Token.builder() + .userId(userId) + .token(tokenValue) + .tokenType(AUTH_TOKEN) + .ttl(ttl) + .build(); + } + public Token saveRegistrationToken(String tokenValue, Long userId, long ttl) { - String key = REGISTRATION_TOKEN_PREFIX + tokenValue; + String key = REGISTRATION_TOKEN.getPREFIX() + tokenValue; Map map = new HashMap<>(); map.put("userId", String.valueOf(userId)); map.put("token", tokenValue); - map.put("tokenType", TokenType.REGISTRATION_TOKEN.name()); + map.put("tokenType", REGISTRATION_TOKEN.name()); saveToRedis(key, map, ttl); return Token.builder() .userId(userId) .token(tokenValue) - .tokenType(TokenType.REGISTRATION_TOKEN) + .tokenType(REGISTRATION_TOKEN) .ttl(ttl) .build(); } @@ -75,10 +96,14 @@ private void saveToRedis(String key, Map map, long ttl) { } public void deleteRefreshToken(Long userId) { - redisTemplate.delete(REFRESH_TOKEN_PREFIX + userId); + redisTemplate.delete(REFRESH_TOKEN.getPREFIX() + userId); } public void deleteRegistrationToken(String token) { - redisTemplate.delete(REGISTRATION_TOKEN_PREFIX + token); + redisTemplate.delete(REGISTRATION_TOKEN.getPREFIX() + token); + } + + public void deleteAuthToken(Long userId) { + redisTemplate.delete(AUTH_TOKEN.getPREFIX() + userId); } } diff --git a/src/main/java/com/genius/herewe/core/security/service/DefaultJwtFacade.java b/src/main/java/com/genius/herewe/core/security/service/DefaultJwtManager.java similarity index 93% rename from src/main/java/com/genius/herewe/core/security/service/DefaultJwtFacade.java rename to src/main/java/com/genius/herewe/core/security/service/DefaultJwtManager.java index 95cb34b..af8201b 100644 --- a/src/main/java/com/genius/herewe/core/security/service/DefaultJwtFacade.java +++ b/src/main/java/com/genius/herewe/core/security/service/DefaultJwtManager.java @@ -36,7 +36,7 @@ @Slf4j @Service @Transactional(readOnly = true) -public class DefaultJwtFacade implements JwtFacade { +public class DefaultJwtManager implements JwtManager { private final CustomUserDetailsService customUserDetailsService; private final UserService userService; private final RefreshTokenService refreshTokenService; @@ -47,13 +47,13 @@ public class DefaultJwtFacade implements JwtFacade { private final long ACCESS_EXPIRATION; private final long REFRESH_EXPIRATION; - public DefaultJwtFacade(CustomUserDetailsService customUserDetailsService, - UserService userService, RefreshTokenService refreshTokenService, - @Value("${jwt.issuer}") String ISSUER, - @Value("${jwt.secret.access}") String ACCESS_SECRET_KEY, - @Value("${jwt.secret.refresh}") String REFRESH_SECRET_KEY, - @Value("${jwt.expiration.access}") long ACCESS_EXPIRATION, - @Value("${jwt.expiration.refresh}") long REFRESH_EXPIRATION) { + public DefaultJwtManager(CustomUserDetailsService customUserDetailsService, + UserService userService, RefreshTokenService refreshTokenService, + @Value("${jwt.issuer}") String ISSUER, + @Value("${jwt.secret.access}") String ACCESS_SECRET_KEY, + @Value("${jwt.secret.refresh}") String REFRESH_SECRET_KEY, + @Value("${jwt.expiration.access}") long ACCESS_EXPIRATION, + @Value("${jwt.expiration.refresh}") long REFRESH_EXPIRATION) { this.customUserDetailsService = customUserDetailsService; this.userService = userService; this.refreshTokenService = refreshTokenService; diff --git a/src/main/java/com/genius/herewe/core/security/service/JwtFacade.java b/src/main/java/com/genius/herewe/core/security/service/JwtManager.java similarity index 97% rename from src/main/java/com/genius/herewe/core/security/service/JwtFacade.java rename to src/main/java/com/genius/herewe/core/security/service/JwtManager.java index 939cbb9..a8be4b0 100644 --- a/src/main/java/com/genius/herewe/core/security/service/JwtFacade.java +++ b/src/main/java/com/genius/herewe/core/security/service/JwtManager.java @@ -8,7 +8,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -public interface JwtFacade { +public interface JwtManager { void verifyIssueCondition(User user); String generateAccessToken(HttpServletResponse response, User user); diff --git a/src/main/java/com/genius/herewe/core/security/service/token/AuthTokenService.java b/src/main/java/com/genius/herewe/core/security/service/token/AuthTokenService.java new file mode 100644 index 0000000..2d77140 --- /dev/null +++ b/src/main/java/com/genius/herewe/core/security/service/token/AuthTokenService.java @@ -0,0 +1,60 @@ +package com.genius.herewe.core.security.service.token; + +import static com.genius.herewe.core.global.exception.ErrorCode.*; + +import java.util.Optional; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.genius.herewe.core.global.exception.BusinessException; +import com.genius.herewe.core.security.domain.Token; +import com.genius.herewe.core.security.domain.TokenType; +import com.genius.herewe.core.security.repository.TokenRepository; + +@Service +public class AuthTokenService { + private final TokenRepository tokenRepository; + private final long TTL; + + public AuthTokenService(TokenRepository tokenRepository, + @Value("${jwt.expiration.registration}") long TTL) { + this.tokenRepository = tokenRepository; + this.TTL = TTL; + } + + public String generateTokenForUser(Long userId, TokenType tokenType) { + String token = UUID.randomUUID().toString(); + switch (tokenType) { + case REGISTRATION_TOKEN -> tokenRepository.saveRegistrationToken(token, userId, TTL); + case AUTH_TOKEN -> tokenRepository.saveAuthToken(token, userId, TTL); + } + return token; + } + + public Long getUserIdFromToken(String token) { + Optional registrationToken = tokenRepository.findRegistrationToken(token); + if (registrationToken.isEmpty()) { + throw new BusinessException(TOKEN_NOT_FOUND_IN_REDIS); + } + return registrationToken.get().getUserId(); + } + + public void validateAuthToken(Long userId, String token) { + Token storedToken = tokenRepository.findAuthToken(userId) + .orElseThrow(() -> new BusinessException(TOKEN_NOT_FOUND_IN_REDIS)); + if (!storedToken.getToken().equals(token)) { + throw new BusinessException(TOKEN_NOT_FOUND_IN_REDIS); + } + } + + public void deleteRegistrationToken(String token) { + tokenRepository.deleteRegistrationToken(token); + } + + public void deleteAuthToken(Long userId) { + tokenRepository.deleteAuthToken(userId); + } +} + diff --git a/src/main/java/com/genius/herewe/core/security/service/token/RegistrationTokenService.java b/src/main/java/com/genius/herewe/core/security/service/token/RegistrationTokenService.java deleted file mode 100644 index c987ef3..0000000 --- a/src/main/java/com/genius/herewe/core/security/service/token/RegistrationTokenService.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.genius.herewe.core.security.service.token; - -import static com.genius.herewe.core.global.exception.ErrorCode.*; - -import java.util.Optional; -import java.util.UUID; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import com.genius.herewe.core.global.exception.BusinessException; -import com.genius.herewe.core.security.domain.Token; -import com.genius.herewe.core.security.repository.TokenRepository; - -@Service -public class RegistrationTokenService { - private final TokenRepository tokenRepository; - private final long TTL; - - public RegistrationTokenService(TokenRepository tokenRepository, - @Value("${jwt.expiration.registration}") long TTL) { - this.tokenRepository = tokenRepository; - this.TTL = TTL; - } - - public String generateTokenForUser(Long userId) { - String registrationToken = UUID.randomUUID().toString(); - tokenRepository.saveRegistrationToken(registrationToken, userId, TTL); - - return registrationToken; - } - - public Long getUserIdFromToken(String token) { - Optional registrationToken = tokenRepository.findRegistrationToken(token); - if (registrationToken.isEmpty()) { - throw new BusinessException(REGISTRATION_TOKEN_NOT_FOUND); - } - return registrationToken.get().getUserId(); - } - - public void deleteToken(String token) { - tokenRepository.deleteRegistrationToken(token); - } -} - diff --git a/src/main/java/com/genius/herewe/core/user/controller/UserController.java b/src/main/java/com/genius/herewe/core/user/controller/UserController.java index 7fab9b0..e9fb648 100644 --- a/src/main/java/com/genius/herewe/core/user/controller/UserController.java +++ b/src/main/java/com/genius/herewe/core/user/controller/UserController.java @@ -12,7 +12,7 @@ import com.genius.herewe.core.global.response.CommonResponse; import com.genius.herewe.core.global.response.SingleResponse; -import com.genius.herewe.core.security.service.token.RegistrationTokenService; +import com.genius.herewe.core.security.service.token.AuthTokenService; import com.genius.herewe.core.user.dto.SignupRequest; import com.genius.herewe.core.user.dto.SignupResponse; import com.genius.herewe.core.user.facade.UserFacade; @@ -30,7 +30,7 @@ @RequestMapping("/api") public class UserController implements UserApi { private final UserFacade userFacade; - private final RegistrationTokenService registrationTokenService; + private final AuthTokenService authTokenService; private final FileHolderFinder holderFinder; private final FilesManager filesManager; @@ -48,7 +48,7 @@ public SingleResponse signup(@RequestBody SignupRequest signupRe @GetMapping("/auth/profile") public SingleResponse getProfile(@RequestParam("token") String token) { - Long userId = registrationTokenService.getUserIdFromToken(token); + Long userId = authTokenService.getUserIdFromToken(token); FileHolder fileHolder = holderFinder.find(userId, FileType.PROFILE); FileResponse fileResponse = filesManager.convertToFileResponse(fileHolder.getFiles()); @@ -60,7 +60,7 @@ public SingleResponse updateProfile( @RequestParam("token") String token, @RequestParam(value = "files") MultipartFile multipartFile ) { - Long userId = registrationTokenService.getUserIdFromToken(token); + Long userId = authTokenService.getUserIdFromToken(token); FileHolder fileHolder = holderFinder.find(userId, FileType.PROFILE); Files files = filesManager.updateFile(fileHolder.getFiles(), multipartFile); FileResponse fileResponse = filesManager.convertToFileResponse(files); diff --git a/src/main/java/com/genius/herewe/core/user/facade/DefaultUserFacade.java b/src/main/java/com/genius/herewe/core/user/facade/DefaultUserFacade.java index 6b45d44..9f19154 100644 --- a/src/main/java/com/genius/herewe/core/user/facade/DefaultUserFacade.java +++ b/src/main/java/com/genius/herewe/core/user/facade/DefaultUserFacade.java @@ -10,7 +10,7 @@ import com.genius.herewe.core.global.exception.BusinessException; import com.genius.herewe.core.security.dto.AuthResponse; -import com.genius.herewe.core.security.service.token.RegistrationTokenService; +import com.genius.herewe.core.security.service.token.AuthTokenService; import com.genius.herewe.core.user.domain.Role; import com.genius.herewe.core.user.domain.User; import com.genius.herewe.core.user.dto.SignupRequest; @@ -28,7 +28,7 @@ public class DefaultUserFacade implements UserFacade { private static final Pattern NICKNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9가-힣]{2,20}$"); private final UserService userService; - private final RegistrationTokenService registrationTokenService; + private final AuthTokenService authTokenService; private final FilesManager filesManager; @Override @@ -51,7 +51,7 @@ public void validateNickname(String nickname) { @Override @Transactional public SignupResponse signup(SignupRequest signupRequest) { - Long userId = registrationTokenService.getUserIdFromToken(signupRequest.token()); + Long userId = authTokenService.getUserIdFromToken(signupRequest.token()); User user = userService.findById(userId); String nickname = signupRequest.nickname(); @@ -62,7 +62,7 @@ public SignupResponse signup(SignupRequest signupRequest) { user.updateNickname(nickname); user.updateRole(Role.USER); - registrationTokenService.deleteToken(signupRequest.token()); + authTokenService.deleteRegistrationToken(signupRequest.token()); FileResponse fileResponse = filesManager.convertToFileResponse(user.getFiles()); return new SignupResponse(user.getId(), user.getNickname(), fileResponse); diff --git a/src/test/java/com/genius/herewe/core/security/service/JwtFacadeTest.java b/src/test/java/com/genius/herewe/core/security/service/JwtManagerTest.java similarity index 85% rename from src/test/java/com/genius/herewe/core/security/service/JwtFacadeTest.java rename to src/test/java/com/genius/herewe/core/security/service/JwtManagerTest.java index 71ec557..5a590d4 100644 --- a/src/test/java/com/genius/herewe/core/security/service/JwtFacadeTest.java +++ b/src/test/java/com/genius/herewe/core/security/service/JwtManagerTest.java @@ -32,7 +32,7 @@ import jakarta.servlet.http.HttpServletResponse; @ExtendWith(MockitoExtension.class) -class JwtFacadeTest { +class JwtManagerTest { private static final String TEST_ISSUER = "test-issuer"; private static final String TEST_ACCESS_SECRET = "testsecretkeytestsecretkeytestsecretkey"; private static final String TEST_REFRESH_SECRET = "refreshsecretrefreshsecretrefreshsecret"; @@ -49,11 +49,11 @@ class JwtFacadeTest { private HttpServletRequest request; @Mock private HttpServletResponse response; - private JwtFacade jwtFacade; + private JwtManager jwtManager; @BeforeEach void init() { - jwtFacade = new DefaultJwtFacade( + jwtManager = new DefaultJwtManager( customUserDetailsService, userService, refreshTokenService, TEST_ISSUER, TEST_ACCESS_SECRET, TEST_REFRESH_SECRET, TEST_ACCESS_EXPIRATION, TEST_REFRESH_EXPIRATION @@ -73,7 +73,7 @@ public void it_throws_UNAUTHORIZED_ISSUE_exception_when_NOT_REGISTERED() { User notRegistered = UserFixture.createByRole(Role.NOT_REGISTERED); //when & then - assertThatThrownBy(() -> jwtFacade.verifyIssueCondition(notRegistered)) + assertThatThrownBy(() -> jwtManager.verifyIssueCondition(notRegistered)) .isInstanceOf(BusinessException.class) .hasMessageContaining(UNAUTHORIZED_ISSUE.getMessage()); } @@ -87,7 +87,7 @@ public void it_does_not_throw_exception(Role role) { //when & then assertThatNoException() - .isThrownBy(() -> jwtFacade.verifyIssueCondition(user)); + .isThrownBy(() -> jwtManager.verifyIssueCondition(user)); } } @@ -101,7 +101,7 @@ void it_generate_token_to_header() { User user = UserFixture.createDefault(); // when - String accessToken = jwtFacade.generateAccessToken(response, user); + String accessToken = jwtManager.generateAccessToken(response, user); // then assertThat(accessToken).startsWith(ACCESS_PREFIX.getValue()); @@ -124,7 +124,7 @@ public void it_throws_UNAUTHORIZED_ISSUE_exception_when_NOT_REGISTERED() { User notRegistered = UserFixture.createByRole(Role.NOT_REGISTERED); //when & then - assertThatThrownBy(() -> jwtFacade.verifyIssueCondition(notRegistered)) + assertThatThrownBy(() -> jwtManager.verifyIssueCondition(notRegistered)) .isInstanceOf(BusinessException.class) .hasMessageContaining(UNAUTHORIZED_ISSUE.getMessage()); } @@ -138,7 +138,7 @@ public void it_does_not_throw_exception(Role role) { //when & then assertThatNoException() - .isThrownBy(() -> jwtFacade.verifyIssueCondition(user)); + .isThrownBy(() -> jwtManager.verifyIssueCondition(user)); } } @@ -153,7 +153,7 @@ public void it_generate_token_to_cookie() { ArgumentCaptor headerValueCaptor = ArgumentCaptor.forClass(String.class); //when - String refreshToken = jwtFacade.generateRefreshToken(response, user); + String refreshToken = jwtManager.generateRefreshToken(response, user); //then verify(response).addHeader(eq(REFRESH_ISSUE.getValue()), headerValueCaptor.capture()); @@ -197,7 +197,7 @@ public void it_resolves_token_from_valid_header() { mockRequest.addHeader(ACCESS_HEADER.getValue(), bearerToken); //when - String resolvedAccessToken = jwtFacade.resolveAccessToken(mockRequest); + String resolvedAccessToken = jwtManager.resolveAccessToken(mockRequest); //then assertThat(resolvedAccessToken).isEqualTo(accessToken); @@ -211,7 +211,7 @@ public void it_throws_JWT_NOT_FOUND_IN_HEADER_exception(String headerValue) { mockRequest.addHeader(ACCESS_HEADER.getValue(), headerValue); //when - String resolvedToken = jwtFacade.resolveAccessToken(mockRequest); + String resolvedToken = jwtManager.resolveAccessToken(mockRequest); //then assertThat(resolvedToken).isEqualTo(""); @@ -225,7 +225,7 @@ public void it_throws_exception_when_header_not_exist(String headerValue) { mockRequest.addHeader(ACCESS_HEADER.getValue(), headerValue); //when & then - assertThatThrownBy(() -> jwtFacade.resolveAccessToken(mockRequest)) + assertThatThrownBy(() -> jwtManager.resolveAccessToken(mockRequest)) .isInstanceOf(BusinessException.class) .hasMessageContaining(JWT_NOT_FOUND_IN_HEADER.getMessage()); } @@ -244,7 +244,7 @@ public void it_resolved_token_when_cookie_is_valid() { mockRequest.setCookies(refreshCookie); //when - String resolvedRefreshToken = jwtFacade.resolveRefreshToken(mockRequest); + String resolvedRefreshToken = jwtManager.resolveRefreshToken(mockRequest); //then assertThat(resolvedRefreshToken).isEqualTo(refreshToken); @@ -258,7 +258,7 @@ public void it_throws_JWT_NOT_FOUND_IN_COOKIE_exception() { mockRequest.setCookies(fakeCookie); //when & then - assertThatThrownBy(() -> jwtFacade.resolveRefreshToken(mockRequest)) + assertThatThrownBy(() -> jwtManager.resolveRefreshToken(mockRequest)) .isInstanceOf(BusinessException.class) .hasMessageContaining(JWT_NOT_FOUND_IN_COOKIE.getMessage()); } @@ -276,10 +276,10 @@ class Describe_verify_access_token { public void it_returns_VALID() { //given User user = UserFixture.createDefault(); - String accessToken = jwtFacade.generateAccessToken(response, user).substring(7); + String accessToken = jwtManager.generateAccessToken(response, user).substring(7); //when - JwtStatus status = jwtFacade.verifyAccessToken(accessToken); + JwtStatus status = jwtManager.verifyAccessToken(accessToken); //then assertThat(status).isEqualTo(JwtStatus.VALID); @@ -289,16 +289,16 @@ public void it_returns_VALID() { @DisplayName("만료된 토큰이라면 EXPIRED를 반환해야 한다.") public void it_return_EXPIRED() { //given - JwtFacade expiredJwtFacade = new DefaultJwtFacade( + JwtManager expiredJwtManager = new DefaultJwtManager( customUserDetailsService, userService, refreshTokenService, TEST_ISSUER, TEST_ACCESS_SECRET, TEST_REFRESH_SECRET, 0L, TEST_REFRESH_EXPIRATION ); User user = UserFixture.createDefault(); - String accessToken = expiredJwtFacade.generateAccessToken(response, user).substring(7); + String accessToken = expiredJwtManager.generateAccessToken(response, user).substring(7); //when - JwtStatus status = expiredJwtFacade.verifyAccessToken(accessToken); + JwtStatus status = expiredJwtManager.verifyAccessToken(accessToken); //then assertThat(status).isEqualTo(JwtStatus.EXPIRED); @@ -312,7 +312,7 @@ public void it_return_NEED_CHECK_RT() { String emptyToken = ""; //when - JwtStatus jwtStatus = jwtFacade.verifyAccessToken(emptyToken); + JwtStatus jwtStatus = jwtManager.verifyAccessToken(emptyToken); //then assertThat(jwtStatus).isEqualTo(JwtStatus.NEED_CHECK_RT); @@ -325,7 +325,7 @@ public void it_throws_JWT_NOT_VALID_exception() { String fakeAccessToken = "fake.access.token"; //when & then - assertThatThrownBy(() -> jwtFacade.verifyAccessToken(fakeAccessToken)) + assertThatThrownBy(() -> jwtManager.verifyAccessToken(fakeAccessToken)) .isInstanceOf(BusinessException.class) .hasMessageContaining(JWT_NOT_VALID.getMessage()); } @@ -339,11 +339,11 @@ class Describe_verify_refresh_token { public void it_does_not_throw_exception() { //given User user = UserFixture.createDefault(); - String refreshToken = jwtFacade.generateRefreshToken(response, user); + String refreshToken = jwtManager.generateRefreshToken(response, user); //when & then assertThatNoException() - .isThrownBy(() -> jwtFacade.verifyRefreshToken(refreshToken)); + .isThrownBy(() -> jwtManager.verifyRefreshToken(refreshToken)); } @Test @@ -351,15 +351,15 @@ public void it_does_not_throw_exception() { public void it_throw_JWT_NOT_VALID_exception() { //given User user = UserFixture.createDefault(); - JwtFacade expiredJwtFacade = new DefaultJwtFacade( + JwtManager expiredJwtManager = new DefaultJwtManager( customUserDetailsService, userService, refreshTokenService, TEST_ISSUER, TEST_ACCESS_SECRET, TEST_REFRESH_SECRET, TEST_ACCESS_EXPIRATION, 0L ); - String expiredToken = expiredJwtFacade.generateRefreshToken(response, user); + String expiredToken = expiredJwtManager.generateRefreshToken(response, user); //when & then - assertThatThrownBy(() -> expiredJwtFacade.verifyRefreshToken(expiredToken)) + assertThatThrownBy(() -> expiredJwtManager.verifyRefreshToken(expiredToken)) .isInstanceOf(BusinessException.class) .hasMessageContaining(JWT_NOT_VALID.getMessage()); } @@ -371,7 +371,7 @@ public void it_throws_JWT_NOT_VALID_exception() { String expiredToken = "refresh.invalid.token"; //when & then - assertThatThrownBy(() -> jwtFacade.verifyRefreshToken(expiredToken)) + assertThatThrownBy(() -> jwtManager.verifyRefreshToken(expiredToken)) .isInstanceOf(BusinessException.class) .hasMessageContaining(JWT_NOT_VALID.getMessage()); } diff --git a/src/test/java/com/genius/herewe/core/security/service/token/RegistrationTokenServiceTest.java b/src/test/java/com/genius/herewe/core/security/service/token/AuthTokenServiceTest.java similarity index 81% rename from src/test/java/com/genius/herewe/core/security/service/token/RegistrationTokenServiceTest.java rename to src/test/java/com/genius/herewe/core/security/service/token/AuthTokenServiceTest.java index 3c6faf3..ce72250 100644 --- a/src/test/java/com/genius/herewe/core/security/service/token/RegistrationTokenServiceTest.java +++ b/src/test/java/com/genius/herewe/core/security/service/token/AuthTokenServiceTest.java @@ -20,15 +20,15 @@ import com.genius.herewe.core.security.repository.TokenRepository; @ExtendWith(MockitoExtension.class) -class RegistrationTokenServiceTest { +class AuthTokenServiceTest { private final long TTL = 1800; @Mock private TokenRepository tokenRepository; - private RegistrationTokenService registrationTokenService; + private AuthTokenService authTokenService; @BeforeEach void init() { - registrationTokenService = new RegistrationTokenService(tokenRepository, TTL); + authTokenService = new AuthTokenService(tokenRepository, TTL); } @Nested @@ -46,9 +46,9 @@ public void it_throws_REGISTRATION_TOKEN_NOT_FOUND_exception() { given(tokenRepository.findRegistrationToken(token.getToken())).willReturn(Optional.empty()); //when & then - assertThatThrownBy(() -> registrationTokenService.getUserIdFromToken(token.getToken())) + assertThatThrownBy(() -> authTokenService.getUserIdFromToken(token.getToken())) .isInstanceOf(BusinessException.class) - .hasMessageContaining(REGISTRATION_TOKEN_NOT_FOUND.getMessage()); + .hasMessageContaining(TOKEN_NOT_FOUND_IN_REDIS.getMessage()); } @Test @@ -58,7 +58,7 @@ public void it_returns_userId() { given(tokenRepository.findRegistrationToken(token.getToken())).willReturn(Optional.of(token)); //when - Long userId = registrationTokenService.getUserIdFromToken(token.getToken()); + Long userId = authTokenService.getUserIdFromToken(token.getToken()); //then assertThat(userId).isEqualTo(token.getUserId()); diff --git a/src/test/java/com/genius/herewe/core/user/facade/UserFacadeTest.java b/src/test/java/com/genius/herewe/core/user/facade/UserFacadeTest.java index e1a7df9..fcec0bc 100644 --- a/src/test/java/com/genius/herewe/core/user/facade/UserFacadeTest.java +++ b/src/test/java/com/genius/herewe/core/user/facade/UserFacadeTest.java @@ -24,7 +24,7 @@ import com.genius.herewe.core.security.domain.Token; import com.genius.herewe.core.security.dto.AuthResponse; import com.genius.herewe.core.security.fixture.TokenFixture; -import com.genius.herewe.core.security.service.token.RegistrationTokenService; +import com.genius.herewe.core.security.service.token.AuthTokenService; import com.genius.herewe.core.user.domain.Role; import com.genius.herewe.core.user.domain.User; import com.genius.herewe.core.user.dto.SignupRequest; @@ -40,12 +40,12 @@ class UserFacadeTest { @Mock private UserService userService; @Mock - private RegistrationTokenService registrationTokenService; + private AuthTokenService authTokenService; private UserFacade userFacade; @BeforeEach public void init() { - userFacade = new DefaultUserFacade(userService, registrationTokenService, filesManager); + userFacade = new DefaultUserFacade(userService, authTokenService, filesManager); } @Nested @@ -202,7 +202,7 @@ public void it_does_not_throw_NICKNAME_DUPLICATED_exception() { registrationToken, targetNickname ); - given(registrationTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); + given(authTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); //when & then assertThatNoException() @@ -216,7 +216,7 @@ public void it_throw_NICKNAME_DUPLICATED_exception() { SignupRequest signupRequest = new SignupRequest( registrationToken, existNickname ); - given(registrationTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); + given(authTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); //when & then assertThatThrownBy(() -> userFacade.signup(signupRequest)) @@ -236,7 +236,7 @@ public void it_signup_successfully() { SignupRequest signupRequest = new SignupRequest(registrationToken, user.getNickname()); given(userService.findById(user.getId())).willReturn(user); - given(registrationTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); + given(authTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); //when SignupResponse signupResponse = userFacade.signup(signupRequest); @@ -255,7 +255,7 @@ public void it_throws_ALREADY_REGISTERED_exception(Role role) { SignupRequest signupRequest = new SignupRequest(registrationToken, user.getNickname()); given(userService.findById(user.getId())).willReturn(user); - given(registrationTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); + given(authTokenService.getUserIdFromToken(registrationToken)).willReturn(user.getId()); //when & then assertThatThrownBy(() -> userFacade.signup(signupRequest))