-
Notifications
You must be signed in to change notification settings - Fork 0
feat : 앱 전용 카카오 로그인 구현 #335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
54698db
a98541c
4dbea44
5b0d548
c9b16bd
04224a7
8e781f2
f8d493e
3706efa
48942cb
2a43db3
f9dc1ed
aafabed
fc4a4bf
5d05abf
a0e362a
375b79f
9d5ec32
7bb68e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.nowait.applicationuser.oauth.controller; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| 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.RestController; | ||
|
|
||
| import com.nowait.applicationuser.oauth.dto.KakaoAppLoginRequest; | ||
| import com.nowait.applicationuser.oauth.dto.KakaoAppLoginResponse; | ||
| import com.nowait.applicationuser.oauth.service.KakaoAppLoginService; | ||
| import com.nowait.common.api.ApiUtils; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/v2/app/oauth/kakao") | ||
| @RequiredArgsConstructor | ||
| public class KakaoAppAuthController { | ||
|
|
||
| private final KakaoAppLoginService kakaoAppLoginService; | ||
|
|
||
| @PostMapping("/login") | ||
| public ResponseEntity<?> kakaoAppLogin(@RequestBody KakaoAppLoginRequest request) { | ||
|
|
||
| KakaoAppLoginResponse response = kakaoAppLoginService.login(request); | ||
|
|
||
| return ResponseEntity | ||
| .status(HttpStatus.OK) | ||
| .body( | ||
| ApiUtils.success(response) | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.nowait.applicationuser.oauth.dto; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @AllArgsConstructor | ||
| public class KakaoAppLoginRequest { | ||
| private String kakaoAccessToken; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.nowait.applicationuser.oauth.dto; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) | ||
| @AllArgsConstructor | ||
| @Builder | ||
| public class KakaoAppLoginResponse{ | ||
| private String accessToken; | ||
| private String refreshToken; | ||
| private Long userId; | ||
| private String email; | ||
| private String nickName; | ||
| private String profileImage; | ||
| private boolean phoneEntered; | ||
| private boolean marketingAgree; | ||
| private boolean isNewUser; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.nowait.applicationuser.oauth.dto; | ||
|
|
||
| import com.nowait.domaincorerdb.user.entity.User; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| public class OAuthUserResult { | ||
| private final User user; | ||
| private final boolean newUser; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||
| package com.nowait.applicationuser.oauth.service; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.time.Instant; | ||||||||||||||||||
|
|
||||||||||||||||||
| import org.springframework.security.oauth2.client.registration.ClientRegistration; | ||||||||||||||||||
| import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; | ||||||||||||||||||
| import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; | ||||||||||||||||||
| import org.springframework.security.oauth2.core.OAuth2AccessToken; | ||||||||||||||||||
| import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | ||||||||||||||||||
| import org.springframework.security.oauth2.core.user.OAuth2User; | ||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||
|
|
||||||||||||||||||
| import com.nowait.applicationuser.oauth.dto.KakaoAppLoginRequest; | ||||||||||||||||||
| import com.nowait.applicationuser.oauth.dto.KakaoAppLoginResponse; | ||||||||||||||||||
| import com.nowait.applicationuser.token.dto.AuthenticationResponse; | ||||||||||||||||||
| import com.nowait.applicationuser.token.service.AuthTokenService; | ||||||||||||||||||
| import com.nowait.domaincorerdb.user.entity.User; | ||||||||||||||||||
| import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; | ||||||||||||||||||
|
|
||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Service | ||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||
| public class KakaoAppLoginService { | ||||||||||||||||||
|
|
||||||||||||||||||
| private final CustomOAuth2UserService customOAuth2UserService; | ||||||||||||||||||
| private final ClientRegistrationRepository clientRegistrationRepository; | ||||||||||||||||||
| private final AuthTokenService authTokenService; | ||||||||||||||||||
|
|
||||||||||||||||||
| public KakaoAppLoginResponse login(KakaoAppLoginRequest request) { | ||||||||||||||||||
| String kakaoAccessTokenValue = request.getKakaoAccessToken(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (kakaoAccessTokenValue == null || kakaoAccessTokenValue.isEmpty()) { | ||||||||||||||||||
| throw new OAuth2AuthenticationException("Kakao Access Token is missing."); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| ClientRegistration kakaoRegistration = clientRegistrationRepository.findByRegistrationId("kakao"); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (kakaoRegistration == null) { | ||||||||||||||||||
| throw new OAuth2AuthenticationException("Kakao Client Registration not found."); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| OAuth2AccessToken kakaoAccessToken = new OAuth2AccessToken( | ||||||||||||||||||
| OAuth2AccessToken.TokenType.BEARER, | ||||||||||||||||||
| kakaoAccessTokenValue, | ||||||||||||||||||
| Instant.now(), | ||||||||||||||||||
| null | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
||||||||||||||||||
| OAuth2UserRequest userRequest = new OAuth2UserRequest( | ||||||||||||||||||
| kakaoRegistration, | ||||||||||||||||||
| kakaoAccessToken | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
||||||||||||||||||
| OAuth2User oAuth2User = customOAuth2UserService.loadUser(userRequest); | ||||||||||||||||||
| CustomOAuth2User customUser = (CustomOAuth2User) oAuth2User; | ||||||||||||||||||
| User user = customUser.getUser(); | ||||||||||||||||||
|
Comment on lines
+55
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
OAuth2User oAuth2User = customOAuth2UserService.loadUser(userRequest);
-CustomOAuth2User customUser = (CustomOAuth2User) oAuth2User;
+if (!(oAuth2User instanceof CustomOAuth2User customUser)) {
+ throw new OAuth2AuthenticationException("Unexpected OAuth2User type");
+}
User user = customUser.getUser();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추후 개선 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Jjiggu, 알겠습니다! 나중에 개선하시는 것으로 하겠습니다. 필요하시면 추후 개선 작업을 추적하기 위한 이슈를 생성해 드릴 수 있습니다. 원하시면 말씀해 주세요! 😊 |
||||||||||||||||||
|
|
||||||||||||||||||
| AuthenticationResponse authenticationResponse = authTokenService.issueTokens(user); | ||||||||||||||||||
|
|
||||||||||||||||||
| return KakaoAppLoginResponse.builder() | ||||||||||||||||||
| .accessToken(authenticationResponse.getAccessToken()) | ||||||||||||||||||
| .refreshToken(authenticationResponse.getRefreshToken()) | ||||||||||||||||||
| .userId(user.getId()) | ||||||||||||||||||
| .email(user.getEmail()) | ||||||||||||||||||
| .nickName(user.getNickname()) | ||||||||||||||||||
| .profileImage(user.getProfileImage()) | ||||||||||||||||||
| .phoneEntered(user.getPhoneEntered()) | ||||||||||||||||||
| .marketingAgree(user.getIsMarketingAgree()) | ||||||||||||||||||
| .isNewUser(customUser.isNewUser()) | ||||||||||||||||||
| .build(); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.nowait.applicationuser.oauth.service; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import com.nowait.applicationuser.oauth.dto.OAuth2Response; | ||
| import com.nowait.applicationuser.oauth.dto.OAuthUserResult; | ||
| import com.nowait.common.enums.Role; | ||
| import com.nowait.common.enums.SocialType; | ||
| import com.nowait.domaincorerdb.user.entity.User; | ||
| import com.nowait.domaincorerdb.user.repository.UserRepository; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class OAuthUserService { | ||
| private final UserRepository userRepository; | ||
|
|
||
| @Transactional | ||
| public OAuthUserResult loadOrCreateUser(OAuth2Response oAuth2Response) { | ||
| return userRepository.findByEmail(oAuth2Response.getEmail()) | ||
| .map(user -> new OAuthUserResult(user, false)) // 기존 유저 | ||
| .orElseGet(() -> { | ||
| User created = createUser(oAuth2Response); | ||
| return new OAuthUserResult(created, true); // 신규 유저 | ||
| }); | ||
| } | ||
|
|
||
| private User createUser(OAuth2Response oAuth2Response) { | ||
| User user = User.builder() | ||
| .email(oAuth2Response.getEmail()) | ||
| .phoneNumber("") | ||
| .nickname(oAuth2Response.getNickName()) | ||
| .profileImage(oAuth2Response.getProfileImage()) | ||
| .socialType(SocialType.KAKAO) | ||
| .role(Role.USER) // 일반 유저 설정 | ||
| .storeId(0L) | ||
| .phoneEntered(false) | ||
| .isMarketingAgree(false) | ||
| .createdAt(LocalDateTime.now()) | ||
| .updatedAt(LocalDateTime.now()) | ||
| .build(); | ||
|
|
||
| return userRepository.save(user); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OAuth 사용자/속성 전체를 INFO 레벨로 로그에 남기는 것은 PII 노출 위험이 있습니다
log.info("CustomOAuth2UserService :: {}", oAuth2User);와log.info("oAuthUser.getAttributes :: {}", oAuth2User.getAttributes());는 이메일, 프로필 이미지 등 개인정보가 포함된 전체 OAuth 응답을 그대로 애플리케이션 로그에 남깁니다. 운영 환경에서 INFO 로그는 수집/보관 범위가 넓어서, 개인정보 보호/컴플라이언스 관점에서 리스크가 큽니다.예시:
이렇게 하면 디버깅에 필요한 정보는 유지하면서, 실제 PII/토큰 값 노출은 피할 수 있습니다.
Also applies to: 28-29, 40-44
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 로그 삭제 여부 검토 필요하니 이슈 등록해줘
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 58
@Jjiggu, 이슈를 생성했습니다!
✅ 이슈 #336: CustomOAuth2UserService의 개인정보 로그 노출 검토 필요
해당 이슈에 로그 삭제 또는 DEBUG 레벨 변경 검토 사항을 정리해두었으며, 이 PR과 코멘트 링크도 포함했습니다. 담당자로 배정되었으니 검토 후 처리 부탁드립니다.
🧬 Code Graph Analysis Results
filename:nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KaKaoResponse.java
At lines 8-40:
filename:nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java
At lines 15-64:
filename:nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java
At lines 17-49:
filename:nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java
At lines 22-69: