From 6582d324d4bf1d127dbeab90a1c32995281e187c Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Sun, 24 Dec 2023 22:28:35 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20#11=20Oauth=20=EC=A0=84=ED=99=94?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9D=B8=EC=A6=9D=20=EC=84=B1=EA=B3=B5=20?= =?UTF-8?q?=EC=8B=9C,=20=EA=B8=B0=EC=A1=B4=20=EA=B3=84=EC=A0=95=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EB=A9=B4=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fitapet/domain/oauth/api/OauthApi.java | 46 ++++++++++++------- .../fitapet/domain/oauth/dto/OauthSmsReq.java | 24 ++++++++++ .../oauth/service/component/OauthService.java | 29 ++++++++---- 3 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSmsReq.java 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 33a2f100..b01e34b2 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 @@ -3,9 +3,9 @@ import com.kcy.fitapet.domain.member.service.component.MemberAuthService; import com.kcy.fitapet.domain.oauth.dto.OauthSignInReq; import com.kcy.fitapet.domain.oauth.dto.OauthSignUpReq; +import com.kcy.fitapet.domain.oauth.dto.OauthSmsReq; import com.kcy.fitapet.domain.oauth.service.component.OauthService; import com.kcy.fitapet.domain.oauth.type.ProviderType; -import com.kcy.fitapet.global.common.redis.sms.type.SmsPrefix; import com.kcy.fitapet.global.common.response.SuccessResponse; import com.kcy.fitapet.global.common.security.jwt.dto.Jwt; import com.kcy.fitapet.global.common.util.cookie.CookieUtil; @@ -61,10 +61,16 @@ public ResponseEntity signIn( return (jwt == null) ? ResponseEntity.ok(SuccessResponse.from(Map.of("id", req.id()))) - : getResponseEntity(jwt); + : getJwtResponseEntity(jwt); } @Operation(summary = "OAuth 회원가입", description = "/{id}/sms로 전화번호 인증 후, accessToken 발급이 선행되어야 한다.") + @Parameters({ + @Parameter(name = "id", description = "OAuth 제공자에서 발급받은 ID"), + @Parameter(name = "provider", description = "OAuth 제공자"), + @Parameter(name = "accessToken", description = "OAuth 전화번호 인증 시 발급받은 accessToken"), + @Parameter(name = "req", description = "OAuth 회원가입 요청 정보") + }) @PostMapping("/{id}") @PreAuthorize("isAnonymous()") public ResponseEntity signUp( @@ -80,36 +86,44 @@ public ResponseEntity signUp( jwt = oAuthService.signUpByOIDC(id, provider, accessToken, req); } - return getResponseEntity(jwt); - } - - private ResponseEntity getResponseEntity(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()); + return getJwtResponseEntity(jwt); } @PostMapping("/{id}/sms") + @PreAuthorize("isAnonymous()") public ResponseEntity signUpSmsAuthorization( @PathVariable("id") Long id, @RequestParam("provider") ProviderType provider, @RequestParam(value = "code", required = false) String code, - @RequestBody @Valid SmsReq req + @RequestBody @Valid OauthSmsReq req ) { if (code == null) { SmsRes smsRes = oAuthService.sendCode(req, id, provider); return ResponseEntity.ok(SuccessResponse.from(smsRes)); } - String token = oAuthService.checkCertificationNumber(req, id, code, provider); - if (!StringUtils.hasText(token)) + Jwt token = oAuthService.checkCertificationNumber(req, id, code, provider); + if (token == null) return ResponseEntity.status(HttpStatus.SC_UNAUTHORIZED).build(); + else if (token.refreshToken() == null) + return ResponseEntity.ok() + .header(ACCESS_TOKEN.getValue(), token.accessToken()) + .body(SuccessResponse.noContent()); + + ResponseCookie cookie = cookieUtil.createCookie(REFRESH_TOKEN.getValue(), token.refreshToken(), 60 * 60 * 24 * 7); + + return ResponseEntity.ok() + .header(HttpHeaders.SET_COOKIE, cookie.toString()) + .header(ACCESS_TOKEN.getValue(), token.accessToken()) + .body(SuccessResponse.from(Map.of("member", "등록된 oauth 계정 연동 성공"))); + } + + private ResponseEntity getJwtResponseEntity(Jwt jwt) { + ResponseCookie cookie = cookieUtil.createCookie(REFRESH_TOKEN.getValue(), jwt.refreshToken(), 60 * 60 * 24 * 7); return ResponseEntity.ok() - .header(ACCESS_TOKEN.getValue(), token) + .header(HttpHeaders.SET_COOKIE, cookie.toString()) + .header(ACCESS_TOKEN.getValue(), jwt.accessToken()) .body(SuccessResponse.noContent()); } } diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSmsReq.java b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSmsReq.java new file mode 100644 index 00000000..bb8ea635 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSmsReq.java @@ -0,0 +1,24 @@ +package com.kcy.fitapet.domain.oauth.dto; + +import com.kcy.fitapet.global.common.util.sms.dto.SmsReq; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "OAuth 전화 번호 인증 요청 정보") +public record OauthSmsReq( + @Schema(description = "전화 번호", example = "01012345678") + @NotNull(message = "전화 번호는 필수 입력값입니다.") + String to, + @Schema(description = "OIDC 토큰") + @NotNull(message = "OIDC 토큰은 필수 입력값입니다.") + String idToken, + @Schema(description = "OIDC 토큰 유효성 검사를 위한 nonce") + @NotNull(message = "nonce는 필수 입력값입니다.") + String nonce +) { + public SmsReq toSmsReq() { + return SmsReq.builder() + .to(this.to) + .build(); + } +} 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 0a7c36d3..976d581c 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 @@ -7,6 +7,7 @@ import com.kcy.fitapet.domain.member.type.RoleType; import com.kcy.fitapet.domain.oauth.domain.OauthAccount; import com.kcy.fitapet.domain.oauth.dto.OauthSignUpReq; +import com.kcy.fitapet.domain.oauth.dto.OauthSmsReq; import com.kcy.fitapet.domain.oauth.exception.OauthException; import com.kcy.fitapet.domain.oauth.service.module.OauthApplicationConfigHelper; import com.kcy.fitapet.domain.oauth.service.module.OauthClientHelper; @@ -76,15 +77,14 @@ public Jwt signInByOIDC(Long id, String idToken, ProviderType provider, String n public Jwt signUpByOIDC(Long id, ProviderType provider, String requestAccessToken, OauthSignUpReq req) { String accessToken = jwtUtil.resolveToken(requestAccessToken); String topic = jwtUtil.getPhoneNumberFromToken(accessToken); + String phone = getPhoneByTopic(topic); + validateToken(accessToken, topic, provider); - String phone = getPhoneByTopic(topic); String idToken = oidcTokenService.findOIDCToken(req.idToken()).getToken(); OIDCDecodePayload payload = getPayload(provider, idToken, req.nonce()); - Member member = (memberSearchService.isExistByPhone(phone)) - ? memberSearchService.findByPhone(phone) - : Member.builder().uid(req.uid()).name(req.name()) + Member member = Member.builder().uid(req.uid()).name(req.name()) .phone(phone).isOauth(Boolean.TRUE).role(RoleType.USER).build(); memberSaveService.saveMember(member); OauthAccount oauthAccount = OauthAccount.of(id, provider, payload.email(), member); @@ -100,8 +100,8 @@ public Jwt signUpByOIDC(Long id, ProviderType provider, String requestAccessToke } @Transactional - public SmsRes sendCode(SmsReq dto, Long id, ProviderType provider) { - SensInfo smsInfo = smsProvider.sendCodeByPhoneNumber(dto); + public SmsRes sendCode(OauthSmsReq dto, Long id, ProviderType provider) { + SensInfo smsInfo = smsProvider.sendCodeByPhoneNumber(dto.toSmsReq()); String key = makeTopic(dto.to(), provider); smsRedisHelper.saveSmsAuthToken(key, smsInfo.code(), SmsPrefix.OAUTH); @@ -111,16 +111,29 @@ public SmsRes sendCode(SmsReq dto, Long id, ProviderType provider) { } @Transactional - public String checkCertificationNumber(SmsReq req, Long id, String code, ProviderType provider) { + public Jwt checkCertificationNumber(OauthSmsReq req, Long id, String code, ProviderType provider) { String key = makeTopic(req.to(), provider); if (!smsRedisHelper.isCorrectCode(key, code, SmsPrefix.OAUTH)) { log.warn("인증번호 불일치 -> 사용자 입력 인증 번호 : {}", code); throw new GlobalErrorException(SmsErrorCode.INVALID_AUTH_CODE); } smsRedisHelper.removeCode(key, SmsPrefix.OAUTH); - return jwtUtil.generateSmsOauthToken(SmsAuthInfo.of(id, key)); + + if (memberSearchService.isExistByPhone(req.to())) { + Member member = memberSearchService.findByPhone(req.to()); + String idToken = oidcTokenService.findOIDCToken(req.idToken()).getToken(); + OIDCDecodePayload payload = getPayload(provider, idToken, req.nonce()); + OauthAccount oauthAccount = OauthAccount.of(id, provider, payload.email(), member); + + return generateToken(JwtUserInfo.from(member)); + } + + return Jwt.of(jwtUtil.generateSmsOauthToken(SmsAuthInfo.of(id, key)), null); } + /** + * idToken을 통해 payload를 가져온다. + */ private OIDCDecodePayload getPayload(ProviderType provider, String idToken, String nonce) { OauthClient oauthClient = oauthClientHelper.getOauthClient(provider); OauthApplicationConfig oauthApplicationConfig = oauthApplicationConfigHelper.getOauthApplicationConfig(provider);