diff --git a/README.md b/README.md index 8b17e622..e9d8313c 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,8 @@ - [ ] 실제 서비스를 공개적으로 배포하고 운영하는 경험을 해보았다. - [ ] 유저의 피드백에 따라 성능/사용성을 개선하고 신규 기능을 추가해보았다. - [ ] 발견되는 버그와 개선사항들을 정리하고 쌓인 이슈들을 체계적으로 관리해보았다. -- [ ] 코드를 지속적으로 리팩토링하고 디자인 패턴을 적용해보았다. -- [ ] 위의 시도에서 더 좋은 설계와 더 빠른 개발 사이의 트레이드 오프를 고민해본 적이 있다. +- [X] 코드를 지속적으로 리팩토링하고 디자인 패턴을 적용해보았다. +- [X] 위의 시도에서 더 좋은 설계와 더 빠른 개발 사이의 트레이드 오프를 고민해본 적이 있다. - [ ] 반복되는 수정과 배포에 수반되는 작업들을 자동화 해보았다. - [ ] 언어나 프레임워크만으로 구현할 수 없는 것들을 직접 구현해보았다. - [ ] 내가 사용한 라이브러리나 프레임 워크의 한계를 느끼고 개선해보았다. @@ -86,7 +86,7 @@ - WAS Server 내부에 Nginx를 통해 Reverse Proxy를 구현했습니다. ## ERD -
+
- 현재 많은 부분이 수정되었고, 앞으로도 계속 수정될 예정입니다. diff --git a/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java b/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java index f10d50df..2af52292 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java +++ b/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java @@ -66,8 +66,8 @@ public class AuthApi { @PostMapping("/register") @PreAuthorize("isAnonymous()") public ResponseEntity signUp(@RequestHeader("Authorization") @NotBlank String accessToken, @RequestBody @Valid SignUpReq dto) { - Jwt tokens = memberAuthService.register(accessToken, dto); - return getResponseEntity(tokens); + Pair result = memberAuthService.register(accessToken, dto); + return getResponseEntity(result.getKey(), result.getValue()); } @Operation(summary = "회원가입 전화번호 인증") @@ -138,11 +138,8 @@ public ResponseEntity searchSmsAuthorization( @PreAuthorize("isAnonymous()") public ResponseEntity signIn(@RequestBody @Valid SignInReq dto) { Pair result = memberAuthService.login(dto); - ResponseCookie cookie = cookieUtil.createCookie(REFRESH_TOKEN.getValue(), result.getValue().refreshToken(), 60 * 60 * 24 * 7); - return ResponseEntity.ok() - .header(HttpHeaders.SET_COOKIE, cookie.toString()) - .header(ACCESS_TOKEN.getValue(), result.getValue().accessToken()) - .body(SuccessResponse.from(Map.of("userId", result.getKey()))); + + return getResponseEntity(result.getKey(), result.getValue()); } @Operation(summary = "로그아웃", description = "액세스 토큰과 리프레시 토큰을 만료시킵니다.") @@ -188,7 +185,13 @@ public ResponseEntity signOut( @PreAuthorize("isAnonymous()") public ResponseEntity refresh(@CookieValue("refreshToken") @Valid String refreshToken) { Jwt tokens = memberAuthService.refresh(refreshToken); - return getResponseEntity(tokens); + + ResponseCookie cookie = cookieUtil.createCookie(REFRESH_TOKEN.getValue(), tokens.refreshToken(), 60 * 60 * 24 * 7); + + return ResponseEntity.ok() + .header(HttpHeaders.SET_COOKIE, cookie.toString()) + .header(ACCESS_TOKEN.getValue(), tokens.accessToken()) + .body(SuccessResponse.noContent()); } @Operation(summary = "토큰 검증", description = "액세스 토큰의 유효성을 검사합니다.") @@ -204,12 +207,12 @@ public ResponseEntity verify(@AccessTokenInfo AccessToken accessToken) { * @param tokens : 액세스 토큰과 리프레시 토큰 * @return ResponseEntity */ - private ResponseEntity getResponseEntity(Jwt tokens) { + private ResponseEntity getResponseEntity(Long userId, Jwt tokens) { ResponseCookie cookie = cookieUtil.createCookie(REFRESH_TOKEN.getValue(), tokens.refreshToken(), 60 * 60 * 24 * 7); return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, cookie.toString()) .header(ACCESS_TOKEN.getValue(), tokens.accessToken()) - .body(SuccessResponse.noContent()); + .body(SuccessResponse.from(Map.of("userId", userId))); } } diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java index 439da6b3..ce4542cf 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java @@ -56,7 +56,7 @@ public class MemberAuthService { private final PasswordEncoder bCryptPasswordEncoder; @Transactional - public Jwt register(String requestSmsAccessToken, SignUpReq dto) { + public Pair register(String requestSmsAccessToken, SignUpReq dto) { String accessToken = jwtMapper.getProvider(SMS_AUTH_TOKEN).resolveToken(requestSmsAccessToken); if (forbiddenTokenService.isForbidden(accessToken)) @@ -76,7 +76,7 @@ public Jwt register(String requestSmsAccessToken, SignUpReq dto) { jwtMapper.getProvider(SMS_AUTH_TOKEN).getExpiryDate(accessToken), false) ); - return generateToken(JwtUserInfo.from(registeredMember)); + return Pair.of(registeredMember.getId(), generateToken(JwtUserInfo.from(registeredMember))); } @Transactional diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java b/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java index feaed8c7..141974c7 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java @@ -18,6 +18,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hc.core5.http.HttpStatus; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; @@ -50,7 +51,7 @@ public ResponseEntity signIn( @RequestParam("provider") ProviderType provider, @RequestBody @Valid OauthSignInReq req ) { - Optional jwt; + Optional> jwt; if (ProviderType.NAVER.equals(provider)) { return null; // TODO: 2023-12-24 네이버 로그인 구현 } else { @@ -58,7 +59,7 @@ public ResponseEntity signIn( } return jwt.isPresent() - ? getJwtResponseEntity(jwt.get()) + ? getJwtResponseEntity(jwt.get().getKey(), jwt.get().getValue()) : ResponseEntity.ok(SuccessResponse.from(Map.of("id", req.id()))); } @@ -76,14 +77,14 @@ public ResponseEntity signUp( @RequestHeader("Authorization") String accessToken, @RequestBody @Valid OauthSignUpReq req ) { - Jwt jwt; + Pair jwt; if (ProviderType.NAVER.equals(provider)) { return null; // TODO: 2023-12-24 네이버 로그인 구현 } else { jwt = oAuthService.signUpByOIDC(id, provider, accessToken, req); } - return getJwtResponseEntity(jwt); + return getJwtResponseEntity(jwt.getKey(), jwt.getValue()); } @Operation(summary = "OAuth 회원가입 전화번호 인증") @@ -105,23 +106,23 @@ public ResponseEntity signUpSmsAuthorization( return ResponseEntity.ok(SuccessResponse.from(smsRes)); } - Jwt token = oAuthService.checkCertificationNumber(req, id, code, provider); - if (token == null) + Pair token = oAuthService.checkCertificationNumber(req, id, code, provider); + if (token.getValue() == null) return ResponseEntity.status(HttpStatus.SC_UNAUTHORIZED).build(); - else if (token.refreshToken() == null) + else if (token.getValue().refreshToken() == null) return ResponseEntity.ok() - .header(ACCESS_TOKEN.getValue(), token.accessToken()) + .header(ACCESS_TOKEN.getValue(), token.getValue().accessToken()) .body(SuccessResponse.from(Map.of("member", "신규 회원"))); - return getJwtResponseEntity(token); + return getJwtResponseEntity(token.getKey(), token.getValue()); } - private ResponseEntity getJwtResponseEntity(Jwt jwt) { + private ResponseEntity getJwtResponseEntity(Long userId, Jwt jwt) { ResponseCookie cookie = cookieUtil.createCookie(REFRESH_TOKEN.getValue(), jwt.refreshToken(), 60 * 60 * 24 * 7); return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, cookie.toString()) .header(ACCESS_TOKEN.getValue(), jwt.accessToken()) - .body(SuccessResponse.noContent()); + .body(SuccessResponse.from(Map.of("userId", userId))); } } diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java index 5ede05f8..76d7a59a 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java @@ -30,6 +30,7 @@ import com.kcy.fitapet.global.common.util.sms.dto.SmsRes; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -59,14 +60,14 @@ public class OauthService { private final SmsRedisHelper smsRedisHelper; @Transactional - public Optional signInByOIDC(String id, String idToken, ProviderType provider, String nonce) { + public Optional> signInByOIDC(String id, String idToken, ProviderType provider, String nonce) { OIDCDecodePayload payload = getPayload(provider, idToken, nonce); log.info("payload : {}", payload); isValidRequestId(id, payload.sub()); if (oauthSearchService.isExistMember(new BigInteger(id), provider)) { Member member = oauthSearchService.findMemberByOauthIdAndProvider(new BigInteger(id), provider); - return Optional.of(generateToken(JwtUserInfo.from(member))); + return Optional.of(Pair.of(member.getId(), generateToken(JwtUserInfo.from(member)))); } else { oidcTokenService.saveOIDCToken(idToken, provider, id); return Optional.empty(); @@ -74,7 +75,7 @@ public Optional signInByOIDC(String id, String idToken, ProviderType provid } @Transactional - public Jwt signUpByOIDC(String id, ProviderType provider, String requestOauthAccessToken, OauthSignUpReq req) { + public Pair signUpByOIDC(String id, ProviderType provider, String requestOauthAccessToken, OauthSignUpReq req) { String accessToken = jwtMapper.getProvider(AuthConstants.SMS_OAUTH_TOKEN).resolveToken(requestOauthAccessToken); JwtSubInfo subs = jwtMapper.getProvider(AuthConstants.SMS_OAUTH_TOKEN).getSubInfoFromToken(accessToken); String phone = getPhoneByTopic(subs.phoneNumber()); @@ -98,7 +99,7 @@ public Jwt signUpByOIDC(String id, ProviderType provider, String requestOauthAcc log.info("success oauth signup member id : {} - oauth id : {} [provider: {}]", member.getId(), oauthAccount.getOauthId(), oauthAccount.getProvider()); - return generateToken(JwtUserInfo.from(member)); + return Pair.of(member.getId(), generateToken(JwtUserInfo.from(member))); } @Transactional @@ -113,7 +114,7 @@ public SmsRes sendCode(OauthSmsReq dto, ProviderType provider) { } @Transactional - public Jwt checkCertificationNumber(OauthSmsReq req, String id, String code, ProviderType provider) { + public Pair checkCertificationNumber(OauthSmsReq req, String id, String code, ProviderType provider) { String key = makeTopic(req.to(), provider); log.info("key: {}", key); if (!smsRedisHelper.isCorrectCode(key, code, SmsPrefix.OAUTH)) { @@ -130,10 +131,10 @@ public Jwt checkCertificationNumber(OauthSmsReq req, String id, String code, Pro oauthAccount.updateMember(member); oidcTokenService.deleteOIDCToken(req.idToken()); - return generateToken(JwtUserInfo.from(member)); + return Pair.of(member.getId(), generateToken(JwtUserInfo.from(member))); } - return Jwt.of(jwtMapper.getProvider(SMS_OAUTH_TOKEN).generateToken(SmsOauthInfo.of(id, key)), null); + return Pair.of(0L, Jwt.of(jwtMapper.getProvider(SMS_OAUTH_TOKEN).generateToken(SmsOauthInfo.of(id, key)), null)); } /**