-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: 헥사고날 아키텍쳐 기반 Auth 및 OAuth 로직 구현 (NEWZET 경험 기반 고도화) #17
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
Merged
Merged
Changes from 69 commits
Commits
Show all changes
71 commits
Select commit
Hold shift + click to select a range
3ee9754
Remove: 실제 DB 연결 테스트 삭제
GitJIHO 44acc94
Feat: AuthUser 도메인 설정
GitJIHO 0fa2cf7
Feat: Token 도메인 설정
GitJIHO f583c2a
Feat: OAuthMapping 도메인 설정
GitJIHO 81ffc7b
Feat: DeviceType 도메인 설정
GitJIHO f279114
Feat: OAuthProvider 도메인 설정
GitJIHO 10278f8
Feat: OAuthToken 도메인 설정
GitJIHO 66d6c94
Feat: OAuthUserInfo 도메인 설정
GitJIHO cea90f4
Feat: TokenType 도메인 설정
GitJIHO b8b5f0b
Feat: OAuth관련 Exceptions 설정
GitJIHO 92b4d84
Feat: Token 관련 Exceptions 설정
GitJIHO 5a896e7
Feat: OAuthMapping 도메인을 저장할 Entity 설정
GitJIHO ab1b8bf
Feat: Token repository 인터페이스 구성
GitJIHO 5f957ff
Feat: OAuth repository 인터페이스 구성
GitJIHO 05e8c10
Feat: OAuthMapping용 JPA를 상속받은 인터페이스 구성
GitJIHO 3d99d51
Feat: OauthRepository 구현체 구현
GitJIHO 05af223
Feat: Token 저장용 Redis를 활용한 구현체 구현
GitJIHO 9119306
Feat: 계층간의 소통을 위한 Auth관련 DTO 구현
GitJIHO 0ac7ec2
Feat: Jwt 비즈니스 로직 중 예외 처리를 위한 Validator 구현
GitJIHO 3ede311
Feat: Jwt를 생성하기 위한 Factory 구현
GitJIHO cf38f82
Feat: Jwt의 핵심 비즈니스 로직 구현
GitJIHO a02aff0
Feat: OAuth의 비즈니스 로직 확장성을 위한 인터페이스 구성
GitJIHO ea2616e
Feat: OAuth중 KAKAO관련 구현체 구현
GitJIHO 0970f25
Feat: Kakao 로직에 필요한 dto 구현
GitJIHO cb576b9
Feat: OauthLogin을 위한 핵심 비즈니스 로직 구현
GitJIHO 5488fff
Feat: @Login 어노테이션 설정
GitJIHO 5c1f7d5
Feat: 인증 부분적 활용을 위한 @RequireAuth 어노테이션 구현
GitJIHO c40576a
Feat: presentation단의 인증 활용을 위한 리졸버 구현
GitJIHO c2df5ff
Feat: DispatcherServlet단 이후 인터셉트하여 인증로직을 처리할 인터셉터 구현
GitJIHO 212f362
Feat: OAuth관련 엔드포인트 swagger 설정을 위한 인터페이스 구성
GitJIHO 9c82d31
Feat: OAuthApi 컨트롤러 구현체 구현
GitJIHO 0d07490
Chore: Redis 설정 안정성과 유동성을 확보한 방향으로 리팩터링
GitJIHO f63bae7
Feat: RestTemplate Bean 주입
GitJIHO a5f537b
Feat: 에러 global 처리용 advice 구현
GitJIHO c71f1cf
Feat: USER 도메인용 인터페이스 구성
GitJIHO 3d2013e
Feat: USER 도메인 구현체 구현
GitJIHO bb3bf28
Feat: UserStatus 도메인 구성
GitJIHO 5537be2
Feat: User 저장용 Entity 구현
GitJIHO 31c446d
Feat: User상태 저장용 enum 설정
GitJIHO 1fdd4cd
Feat: 유저용 exception 설정
GitJIHO 828d2fb
Feat: 비즈니스 로직에서 활용한 User 저장용 인터페이스 구성
GitJIHO 7d80160
Feat: User repository를 위한 jpa repository 상속받은 인터페이스 구성
GitJIHO be31e09
Feat: UserRepository 구현체 구현
GitJIHO 806a328
Feat: User단에서의 계층간 소통을 위한 dto 구현
GitJIHO 06f7f5c
Feat: User 생성용 Factory 구현
GitJIHO 1987b6d
Feat: User용 핵심 비즈니스 로직 구현
GitJIHO b382b76
Feat: User용 presentation 단에서 사용할 swagger및 rest 설정 인터페이스 구성
GitJIHO 16619f4
Feat: 유저용 controller 엔드포인트 구현
GitJIHO 010f6ad
Chore: gradle 의존성 정리
GitJIHO 894fb86
Refactor: 자동 리팩터링
GitJIHO 84a98f6
Test: MySQL 테스트 컨테이너 설정
GitJIHO c053693
Test: Redis 테스트 컨테이너 설정
GitJIHO 80c9122
Test: MySQL의 테스트 컨테이너를 primary로 테스트 환경에서 설정
GitJIHO 224d409
Test: Redis의 테스트 컨테이너를 primary로 테스트 환경에서 설정
GitJIHO bc9b442
Test: Redis 및 MySQL primary bean 설정용 TestConfig 구성
GitJIHO 98aa638
Test: gitignore된 환경변수들을 테스트환경에서 임의의 값으로 사용할 수 있도록 구성
GitJIHO b64fadd
Test: 테스트 환경에서 테스트컨테이너로 띄운 Redis와 MySQL의 정상 connection 여부 테스트
GitJIHO 3be685a
Test: SpringBootTest를 활용한 통합 테스트들 진행
GitJIHO 7a5283a
Test: DataJpaTest를 활용한 db 테스트 진행
GitJIHO 30dac83
Test: Auth의 비즈니스 로직 유닛 테스트
GitJIHO 9c03bf6
Test: Auth의 Domain단 유닛 테스트
GitJIHO 528ad02
Test: Interceptor 테스트
GitJIHO 794c9fd
Test: Auth의 인프라 repository단 유닛 테스트
GitJIHO 7c16c66
Test: Auth의 엔티티 유닛테스트
GitJIHO 1f49e3e
Test: Resolver 테스트
GitJIHO 4c133ad
Test: User의 비즈니스 로직 유닛 테스트
GitJIHO 06bb025
Test: User의 도메인단 유닛 테스트
GitJIHO bfcc5f4
Chore: PR_TEST단에서의 임시 컨테이너 제거 및 환경변수 임시 추가 제거
GitJIHO afdc166
Chore: Deploy 워크플로우에서 build중 test에 활용될 임시 env 제거
GitJIHO 39719ac
Fix: UserController UserApi의 구현체로 수정
GitJIHO 313892b
Chore: entity Builder의 접근제한자 public, dto Builder의 접근제한자 protected로 변경
GitJIHO File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
src/main/java/com/knu/ddip/auth/business/dto/JwtRefreshRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.knu.ddip.auth.business.dto; | ||
|
|
||
| public record JwtRefreshRequest( | ||
| String refreshToken, | ||
| String deviceType | ||
| ) { | ||
| } |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/knu/ddip/auth/business/dto/JwtResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.knu.ddip.auth.business.dto; | ||
|
|
||
| public record JwtResponse( | ||
| String accessToken, | ||
| String refreshToken | ||
| ) { | ||
| } |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/knu/ddip/auth/business/dto/OAuthContext.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.knu.ddip.auth.business.dto; | ||
|
|
||
| import com.knu.ddip.auth.domain.DeviceType; | ||
| import com.knu.ddip.auth.domain.OAuthProvider; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
|
|
||
| @Builder(access = AccessLevel.PRIVATE) | ||
| public record OAuthContext(OAuthProvider oAuthProvider, DeviceType deviceType) { | ||
| public static OAuthContext of(OAuthProvider oAuthProvider, DeviceType deviceType) { | ||
| return OAuthContext.builder() | ||
| .oAuthProvider(oAuthProvider) | ||
| .deviceType(deviceType) | ||
| .build(); | ||
| } | ||
| } |
29 changes: 29 additions & 0 deletions
29
src/main/java/com/knu/ddip/auth/business/dto/OAuthLoginResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.knu.ddip.auth.business.dto; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Builder(access = AccessLevel.PROTECTED) | ||
| public record OAuthLoginResponse( | ||
| String accessToken, | ||
| String refreshToken, | ||
| UUID OAuthMappingEntityId, | ||
| boolean needRegister | ||
| ) { | ||
| public static OAuthLoginResponse toJwt(String accessToken, String refreshToken) { | ||
| return OAuthLoginResponse.builder() | ||
| .accessToken(accessToken) | ||
| .refreshToken(refreshToken) | ||
| .needRegister(false) | ||
| .build(); | ||
| } | ||
|
|
||
| public static OAuthLoginResponse toSignUp(UUID OAuthMappingEntityId) { | ||
| return OAuthLoginResponse.builder() | ||
| .OAuthMappingEntityId(OAuthMappingEntityId) | ||
| .needRegister(true) | ||
| .build(); | ||
| } | ||
| } |
37 changes: 37 additions & 0 deletions
37
src/main/java/com/knu/ddip/auth/business/dto/OAuthMappingEntityDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.knu.ddip.auth.business.dto; | ||
|
|
||
| import com.knu.ddip.auth.domain.OAuthProvider; | ||
| import com.knu.ddip.auth.domain.OAuthToken; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Getter | ||
| @Builder(access = AccessLevel.PROTECTED) | ||
| public class OAuthMappingEntityDto { | ||
| private final UUID id; | ||
| private final String socialUserId; | ||
| private final String socialUserEmail; | ||
| private final String socialUserName; | ||
| private final OAuthProvider provider; | ||
| private final UUID userId; | ||
| private final OAuthToken oauthToken; | ||
| private final boolean temporary; | ||
|
|
||
| public static OAuthMappingEntityDto create(UUID id, String providerId, String providerEmail, | ||
| String providerName, OAuthProvider provider, | ||
| UUID userId, OAuthToken oauthToken, boolean temporary) { | ||
| return OAuthMappingEntityDto.builder() | ||
| .id(id) | ||
| .socialUserId(providerId) | ||
| .socialUserEmail(providerEmail) | ||
| .socialUserName(providerName) | ||
| .provider(provider) | ||
| .userId(userId) | ||
| .oauthToken(oauthToken) | ||
| .temporary(temporary) | ||
| .build(); | ||
| } | ||
| } |
20 changes: 20 additions & 0 deletions
20
src/main/java/com/knu/ddip/auth/business/dto/OAuthTokenDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.knu.ddip.auth.business.dto; | ||
|
|
||
| import com.knu.ddip.auth.domain.OAuthToken; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
|
|
||
| @Builder(access = AccessLevel.PRIVATE) | ||
| public record OAuthTokenDto( | ||
| String accessToken, | ||
| String refreshToken, | ||
| long expiresIn) { | ||
|
|
||
| public static OAuthTokenDto from(OAuthToken oAuthToken) { | ||
| return OAuthTokenDto.builder() | ||
| .accessToken(oAuthToken.getAccessToken()) | ||
| .refreshToken(oAuthToken.getRefreshToken()) | ||
| .expiresIn(oAuthToken.getExpiresIn()) | ||
| .build(); | ||
| } | ||
| } | ||
13 changes: 13 additions & 0 deletions
13
src/main/java/com/knu/ddip/auth/business/dto/TokenDTO.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.knu.ddip.auth.business.dto; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
|
|
||
| @Builder(access = AccessLevel.PRIVATE) | ||
| public record TokenDTO( | ||
| String value | ||
| ) { | ||
| public static TokenDTO from(String value) { | ||
| return TokenDTO.builder().value(value).build(); | ||
| } | ||
| } |
72 changes: 72 additions & 0 deletions
72
src/main/java/com/knu/ddip/auth/business/service/JwtFactory.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package com.knu.ddip.auth.business.service; | ||
|
|
||
| import com.knu.ddip.auth.domain.Token; | ||
| import com.knu.ddip.auth.domain.TokenType; | ||
| import io.jsonwebtoken.Claims; | ||
| import io.jsonwebtoken.Jwts; | ||
| import io.jsonwebtoken.security.Keys; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import javax.crypto.SecretKey; | ||
| import java.util.Date; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| @Component | ||
| public class JwtFactory { | ||
| public static final long REFRESH_TOKEN_VALIDITY_MILLISECONDS = 14 * 24 * 60 * 60 * 1000; | ||
| private static final long ACCESS_TOKEN_VALIDITY_MILLISECONDS = 30 * 60 * 1000; | ||
| private final SecretKey secretKey; | ||
|
|
||
| public JwtFactory(@Value("${SECRET_KEY}") String secret) { | ||
| this.secretKey = Keys.hmacShaKeyFor(secret.getBytes()); | ||
| } | ||
|
|
||
| public Token createAccessToken(UUID userId) { | ||
| return createToken(userId, TokenType.ACCESS, ACCESS_TOKEN_VALIDITY_MILLISECONDS); | ||
| } | ||
|
|
||
| public Token createRefreshToken(UUID userId) { | ||
| return createToken(userId, TokenType.REFRESH, REFRESH_TOKEN_VALIDITY_MILLISECONDS); | ||
| } | ||
|
|
||
| private Token createToken(UUID userId, TokenType tokenType, long validityInMilliseconds) { | ||
| Date now = new Date(); | ||
| Date validity = new Date(now.getTime() + validityInMilliseconds); | ||
|
|
||
| String tokenValue = Jwts.builder() | ||
| .subject(String.valueOf(userId)) | ||
| .issuedAt(now) | ||
| .expiration(validity) | ||
| .claim("type", tokenType.name()) | ||
| .signWith(secretKey) | ||
| .compact(); | ||
|
|
||
| return Token.of(tokenType, tokenValue, String.valueOf(userId), now, validity); | ||
| } | ||
|
|
||
| public Optional<Token> parseToken(String tokenValue) { | ||
| if (tokenValue == null || tokenValue.isEmpty()) { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| try { | ||
| Claims claims = Jwts.parser() | ||
| .verifyWith(secretKey) | ||
| .build() | ||
| .parseSignedClaims(tokenValue) | ||
| .getPayload(); | ||
|
|
||
| String subject = claims.getSubject(); | ||
| Date issuedAt = claims.getIssuedAt(); | ||
| Date expiration = claims.getExpiration(); | ||
| String tokenTypeStr = claims.get("type", String.class); | ||
| TokenType tokenType = TokenType.valueOf(tokenTypeStr); | ||
|
|
||
| return Optional.of(Token.of(tokenType, tokenValue, subject, issuedAt, expiration)); | ||
| } catch (Exception e) { | ||
| return Optional.empty(); | ||
| } | ||
| } | ||
| } |
46 changes: 46 additions & 0 deletions
46
src/main/java/com/knu/ddip/auth/business/service/JwtService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package com.knu.ddip.auth.business.service; | ||
|
|
||
| import com.knu.ddip.auth.business.dto.JwtRefreshRequest; | ||
| import com.knu.ddip.auth.business.dto.JwtResponse; | ||
| import com.knu.ddip.auth.business.dto.TokenDTO; | ||
| import com.knu.ddip.auth.business.validator.JwtValidator; | ||
| import com.knu.ddip.auth.domain.Token; | ||
| import com.knu.ddip.auth.exception.TokenBadRequestException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class JwtService { | ||
| private final JwtFactory jwtFactory; | ||
| private final TokenRepository tokenRepository; | ||
| private final JwtValidator JWTValidator; | ||
|
|
||
| public JwtResponse refreshAccessToken(JwtRefreshRequest request) { | ||
| String refreshTokenValue = request.refreshToken(); | ||
| String deviceType = request.deviceType(); | ||
|
|
||
| Token refreshToken = jwtFactory.parseToken(refreshTokenValue) | ||
| .orElseThrow(() -> new TokenBadRequestException("유효하지 않은 토큰입니다.")); | ||
|
|
||
| UUID userId = UUID.fromString(refreshToken.getSubject()); | ||
|
|
||
| JWTValidator.validateRefreshToken(refreshToken, userId, deviceType); | ||
|
|
||
| Token newAccessToken = jwtFactory.createAccessToken(userId); | ||
| Token newRefreshToken = jwtFactory.createRefreshToken(userId); | ||
|
|
||
| TokenDTO newRefreshTokenDTO = newRefreshToken.toTokenDTO(); | ||
|
|
||
| tokenRepository.saveToken(userId, deviceType, newRefreshTokenDTO); | ||
| tokenRepository.updateLastRefreshTime(userId, deviceType); | ||
|
|
||
| return new JwtResponse(newAccessToken.getValue(), newRefreshToken.getValue()); | ||
| } | ||
|
|
||
| public void logout(UUID userId, String deviceType) { | ||
| tokenRepository.removeToken(userId, deviceType); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
혹시 from을 사용하신 이유가 있으신가요? 제 생각엔 from은 입력값에 가공이나 변환이 필요한 경우에 사용하고 of는 그대로 입력값을 받아 생성할 때 사용하는게 자연스럽다 생각하는데 혹시 어떻게 생각하시나요?
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.
저는 from은 단일 인자값을 기반으로, of는 여러 인자값을 기반으로 static 생성자를 구성할 때 사용하는걸로 이해하여 사용했습니다. 기헌님같은 경우에는 위 코드의 경우 of로 구성하는것이 좀 더 자연스럽다고 생각하시는걸까요?
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.
아하 넵 그렇군요
https://docs.oracle.com/javase/tutorial/datetime/overview/naming.html
자바 네이밍 컨벤션에 이런 기준이 있긴 한데 또 다른 분들 의견을 참고해보니 적절하다 생각하는 네이밍을 사용하는 것이 더 중요한 것 같긴 하네요!
그냥 의미상 자연스러운 정적 펙토리 메서드로 만들어 쓰면 될 것 같습니다~!
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.
아하 좋은 자료 감사합니다 😄