-
Notifications
You must be signed in to change notification settings - Fork 1
[REFACTOR] 유저 도메인 추가 #267
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
[REFACTOR] 유저 도메인 추가 #267
Changes from all commits
1739b8a
0aa1bec
adb632a
97b6f04
be3f7ac
0f505a7
6afd89a
d5ad386
294ab05
db19228
a912c1a
6856655
156087e
1915133
927146f
3a85947
2322782
3c7d762
7b93cdc
987054d
4c38f74
bf7fa78
4d54d78
539b5d8
144fa4e
5c7172a
fb100d6
1cb8249
5d73734
6e2d1a3
73a1d13
9d38623
239bf32
89bb67a
1df63ce
654926c
7750e80
9a02fed
d89e00f
e480db5
c9774f0
a1a6db0
5be3fd2
0f4b57b
0f3c0d2
5844504
3936b5f
36c5a1b
bf2bc3e
3936954
af2fbdf
80eba66
7eaa28c
cfef5cb
f568e01
71dc6a4
12186c5
0a44b9a
1c3024c
b0b3490
f25b048
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,7 @@ | ||
| package com.somemore.global.auth.oauth.checker; | ||
|
|
||
| import com.somemore.global.auth.oauth.domain.OAuthProvider; | ||
|
|
||
| public interface OAuthInfoChecker { | ||
| boolean doesUserExist(OAuthProvider provider, String id); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.somemore.global.auth.oauth.checker; | ||
|
|
||
| import com.somemore.global.auth.oauth.domain.OAuthProvider; | ||
| import com.somemore.global.auth.oauth.repository.OAuthInfoRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional(readOnly = true) | ||
| public class OAuthInfoCheckerImpl implements OAuthInfoChecker { | ||
|
Collaborator
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. Cheker가 보편적인 이름인가요? 나중에 참고해보려고 여쭤봅니다!
Collaborator
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. 존재 여부를 확인하는 유즈케이스에서 클래스 이름에 exist라는 단어를 사용하는 대신, 더 추상화된 이름을 선택하려고 고민했습니다. 클래스는 추상적인 이름을, 메서드는 구체화된 이름을 갖도록 설계하는 것을 목표로 했습니다. 또한, validate는 검증의 뉘앙스가 강하고, inspector나 analyzer는 작업의 성격과 잘 맞지 않아 적합하지 않다고 판단했습니다. 만약 검증의 의미가 더 강했다면 validate를, 추가적인 처리가 필요했다면 processor를 사용했겠지만, 이번 작업은 단순히 존재 여부만을 확인하는 가벼운 작업이기 때문에, exist라는 단어를 피하면서도 적합한 이름으로 checker를 선택했습니다.
Collaborator
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. 덧붙여, 원래 기존 도메인에서는 이러한 로직이 쿼리 유즈케이스(서비스)에 포함되는 것이 일반적이지만, 인증/인가 로직에서는 별도로 구분하는 것이 훨씬 명확하고 적합하다고 판단해서 checker와 같은 클래스가 나타난 것 같습니다.
Collaborator
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. 답변 감사합니다! |
||
|
|
||
| private final OAuthInfoRepository oAuthInfoRepository; | ||
|
|
||
| @Override | ||
| public boolean doesUserExist(OAuthProvider provider, String id) { | ||
| return oAuthInfoRepository.existsByOAuthProviderAndOauthId(provider, id); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.somemore.global.auth.oauth.converter; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.somemore.global.auth.oauth.domain.CommonOAuthInfo; | ||
| import com.somemore.global.auth.oauth.domain.OAuthProvider; | ||
| import com.somemore.global.auth.oauth.naver.dto.NaverUserProfileResponseDto; | ||
| import org.springframework.security.oauth2.core.user.OAuth2User; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class NaverOAuthResponseConverter implements OAuthResponseConverter { | ||
|
|
||
| @Override | ||
| public CommonOAuthInfo convert(OAuth2User oAuth2User) { | ||
| NaverUserProfileResponseDto naverUserProfileResponseDto = new ObjectMapper().convertValue(oAuth2User.getAttributes(), NaverUserProfileResponseDto.class); | ||
|
|
||
| return new CommonOAuthInfo( | ||
| OAuthProvider.NAVER, | ||
| naverUserProfileResponseDto.response().id()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.somemore.global.auth.oauth.converter; | ||
|
|
||
| import com.somemore.global.auth.oauth.domain.CommonOAuthInfo; | ||
| import org.springframework.security.oauth2.core.user.OAuth2User; | ||
|
|
||
| public interface OAuthResponseConverter { | ||
| CommonOAuthInfo convert(OAuth2User oAuth2User); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.somemore.global.auth.oauth.domain; | ||
|
|
||
| public record CommonOAuthInfo( | ||
| OAuthProvider provider, | ||
| String oauthId) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package com.somemore.global.auth.oauth.domain; | ||
|
|
||
| import com.somemore.global.common.entity.BaseEntity; | ||
| import com.somemore.user.domain.User; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.EnumType; | ||
| import jakarta.persistence.Enumerated; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Entity | ||
| @Table(name = "oauth_info") | ||
| public class OAuthInfo extends BaseEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "id", nullable = false) | ||
| private Long id; | ||
|
|
||
| @Column(name = "user_id", nullable = false) | ||
| private UUID userId; | ||
|
|
||
| @Column(name = "oauth_id", nullable = false) | ||
| private String oauthId; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(name = "oauth_provider", nullable = false) | ||
| private OAuthProvider oAuthProvider; | ||
|
|
||
| public static OAuthInfo create(User user, CommonOAuthInfo commonOAuthInfo) { | ||
| return OAuthInfo.builder() | ||
| .userId(user.getId()) | ||
| .oauthId(commonOAuthInfo.oauthId()) | ||
| .oAuthProvider(commonOAuthInfo.provider()) | ||
| .build(); | ||
| } | ||
|
|
||
| @Builder | ||
| private OAuthInfo(UUID userId, String oauthId, OAuthProvider oAuthProvider) { | ||
| this.userId = userId; | ||
| this.oauthId = oauthId; | ||
| this.oAuthProvider = oAuthProvider; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package com.somemore.global.auth.oauth; | ||
| package com.somemore.global.auth.oauth.domain; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,65 +1,63 @@ | ||
| package com.somemore.global.auth.oauth.handler; | ||
|
|
||
| import com.somemore.global.auth.cookie.CookieUseCase; | ||
| import com.somemore.global.auth.jwt.domain.EncodedToken; | ||
| import com.somemore.global.auth.jwt.domain.UserRole; | ||
| import com.somemore.global.auth.jwt.usecase.GenerateTokensOnLoginUseCase; | ||
| import com.somemore.global.auth.oauth.OAuthProvider; | ||
| import com.somemore.global.auth.oauth.naver.service.query.ProcessNaverOAuthUserService; | ||
| import com.somemore.global.auth.oauth.processor.OAuthUserProcessor; | ||
| import com.somemore.global.auth.redirect.RedirectUseCase; | ||
| import com.somemore.domains.volunteer.usecase.VolunteerQueryUseCase; | ||
| import com.somemore.user.domain.UserRole; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | ||
| import org.springframework.security.oauth2.core.user.OAuth2User; | ||
| import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.UUID; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| @Slf4j | ||
| public class CustomOAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { | ||
|
|
||
| private final ProcessNaverOAuthUserService processNaverOAuthService; | ||
| private final VolunteerQueryUseCase volunteerQueryUseCase; | ||
| private final OAuthUserProcessor oauthUserProcessor; | ||
| private final GenerateTokensOnLoginUseCase generateTokensOnLoginUseCase; | ||
| private final CookieUseCase cookieUseCase; | ||
| private final RedirectUseCase redirectUseCase; | ||
|
|
||
| @Value("${app.front-url}") | ||
| private String frontendRootUrl; | ||
| public static final String AUTHORIZATION = "Authorization"; | ||
| public static final String MAIN_PATH = "/main"; | ||
|
|
||
| @Override | ||
| public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { | ||
| String oAuthId; | ||
| switch (getOAuthProvider(authentication)) { | ||
| case NAVER -> oAuthId = processNaverOAuthService.processOAuthUser(authentication); | ||
| default -> { | ||
| log.error("지원하지 않는 OAuth 제공자입니다."); | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| } | ||
| public void onAuthenticationSuccess(HttpServletRequest request, | ||
| HttpServletResponse response, | ||
| Authentication authentication) { | ||
| OAuth2User oauthUser = extractOAuthUser(authentication); | ||
| UUID userId = oauthUserProcessor.fetchUserIdByOAuthUser(oauthUser); | ||
|
|
||
| processAccessToken(response, userId); | ||
| redirect(request, response); | ||
| } | ||
|
|
||
| private void redirect(HttpServletRequest request, HttpServletResponse response) { | ||
| // TODO 유저 정보 커스텀 확인 분기 | ||
| redirectUseCase.redirect(request, response, MAIN_PATH); | ||
| } | ||
|
|
||
| UUID volunteerId = volunteerQueryUseCase.getVolunteerIdByOAuthId(oAuthId); | ||
| private void processAccessToken(HttpServletResponse response, UUID userId) { | ||
| EncodedToken accessToken = | ||
| generateTokensOnLoginUseCase.saveRefreshTokenAndReturnAccessToken( | ||
| volunteerId, UserRole.VOLUNTEER | ||
| ); | ||
| userId, UserRole.getOAuthUserDefaultRole()); | ||
|
|
||
| cookieUseCase.setAccessToken(response, accessToken.value()); | ||
| redirectUseCase.redirect(request, response, frontendRootUrl); | ||
| response.addHeader(AUTHORIZATION, accessToken.getValueWithPrefix()); | ||
| } | ||
|
|
||
| private static OAuthProvider getOAuthProvider(Authentication authentication) { | ||
| private OAuth2User extractOAuthUser(Authentication authentication) { | ||
| if (authentication instanceof OAuth2AuthenticationToken token) { | ||
| return OAuthProvider.from(token.getAuthorizedClientRegistrationId()); | ||
| return token.getPrincipal(); | ||
| } | ||
| throw new IllegalArgumentException(); | ||
| log.error("Authentication 객체가 OAuth2AuthenticationToken 타입이 아닙니다: {}", authentication.getClass().getName()); | ||
| throw new IllegalArgumentException("잘못된 인증 객체입니다."); | ||
| } | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.somemore.global.auth.oauth.naver.dto; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
| import com.fasterxml.jackson.databind.PropertyNamingStrategies; | ||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
|
|
||
| @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | ||
| public record NaverUserProfileResponseDto( | ||
| String resultcode, // 결과 코드 | ||
| String message, // 결과 메시지 | ||
| Response response // 응답 데이터 | ||
| ) { | ||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public record Response( | ||
| String id // 일련 번호 | ||
| ) { | ||
| } | ||
| } |
This file was deleted.
This file was deleted.
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.
감사합니다 ㅎㅎ