-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat/#83] OAuth 로그인 auth 도메인과 계정 연결 구성 #88
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
143de60
254ff19
afcf1d8
f495503
a9298f7
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,23 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.client; | ||
|
|
||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthLoginCommand; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthProvider; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthUserInfo; | ||
|
|
||
| /** | ||
| * provider별 OAuth 로그인 전략. 구현체는 provider마다 하나씩 모두 빈으로 등록하고, | ||
| * 요청의 provider에 맞는 구현체를 OAuthClientRegistry가 고른다. | ||
| */ | ||
| public interface OAuthClient { | ||
|
|
||
| OAuthProvider provider(); | ||
|
|
||
| /** provider에 등록한 redirect URI와 정확히 일치하는지. 외부 호출 전에 거르는 용도다. */ | ||
| boolean isAllowedRedirectUri(String redirectUri); | ||
|
|
||
| /** | ||
| * code를 provider 토큰으로 교환하고 사용자 정보를 조회한다. | ||
| * provider 토큰은 이 메서드 안에서만 쓰고 밖으로 내보내지 않는다. | ||
| */ | ||
| OAuthUserInfo fetchUserInfo(OAuthLoginCommand command); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package kr.ac.kookmin.stream.auth.domain.oauth.domain; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AccessLevel; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AllArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.EqualsAndHashCode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * provider 계정과 회원의 연결. 한 회원은 provider마다 계정을 하나씩 연결할 수 있다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @EqualsAndHashCode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AllArgsConstructor(access = AccessLevel.PRIVATE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class OAuthAccount { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long memberId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private OAuthProvider provider; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String providerUserId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static OAuthAccount create(Long memberId, OAuthProvider provider, String providerUserId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new OAuthAccount(null, memberId, provider, providerUserId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static OAuthAccount of(Long id, Long memberId, OAuthProvider provider, String providerUserId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new OAuthAccount(id, memberId, provider, providerUserId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+28
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. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: git rev-parse HEAD
rg -n 'record|도메인 객체|Domain objects|불변' docs/conventions/coding-style.md
find core/domain/auth -name AGENTS.md -print
sed -n '1,100p' core/domain/auth/src/main/java/kr/ac/kookmin/stream/auth/domain/oauth/domain/OAuthAccount.java
sed -n '1,100p' infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/auth/OAuthAccountJpaEntity.java
sed -n '1,100p' core/domain/auth/src/main/java/kr/ac/kookmin/stream/auth/domain/oauth/service/impl/OAuthServiceImpl.javaRepository: billilge/stream-server Length of output: 7417 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- convention sections ---'
sed -n '28,48p' docs/conventions/coding-style.md
sed -n '140,165p' docs/conventions/coding-style.md
sed -n '438,468p' docs/conventions/coding-style.md
printf '%s\n' '--- OAuthAccount references ---'
rg -n -C 2 'OAuthAccount|oauthAccount|getId\\(\\)|getMemberId\\(\\)|getProvider\\(\\)|getProviderUserId\\(\\)' core infrastructure api 2>/dev/null
printf '%s\n' '--- changed file history-independent diff ---'
git diff --no-ext-diff --unified=20 54e0a9b8fec146af048ac06f9dd6cf663b868998 f4955038ba2a2009b0d02a0a91502dec392b3009 -- core/domain/auth/src/main/java/kr/ac/kookmin/stream/auth/domain/oauth/domain/OAuthAccount.java infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/auth/OAuthAccountJpaEntity.java core/domain/auth/src/main/java/kr/ac/kookmin/stream/auth/domain/oauth/service/impl/OAuthServiceImpl.javaRepository: billilge/stream-server Length of output: 24386
이 변경은 런타임 장애를 수정하는 작업은 아니지만, 도메인 객체의 불변성과 저장소 전체의 접근자 규칙을 일관되게 유지합니다. 기존 ♻️ 제안-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-
/**
* provider 계정과 회원의 연결. 한 회원은 provider마다 계정을 하나씩 연결할 수 있다.
*/
-@Getter
-@EqualsAndHashCode
-@AllArgsConstructor(access = AccessLevel.PRIVATE)
-public class OAuthAccount {
-
- private Long id;
- private Long memberId;
- private OAuthProvider provider;
- private String providerUserId;
+public record OAuthAccount(
+ Long id,
+ Long memberId,
+ OAuthProvider provider,
+ String providerUserId
+) {- this.id = oauthAccount.getId();
- this.memberId = oauthAccount.getMemberId();
- this.provider = oauthAccount.getProvider();
- this.providerUserId = oauthAccount.getProviderUserId();
+ this.id = oauthAccount.id();
+ this.memberId = oauthAccount.memberId();
+ this.provider = oauthAccount.provider();
+ this.providerUserId = oauthAccount.providerUserId();- .map(OAuthAccount::getMemberId);
+ .map(OAuthAccount::memberId);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.domain; | ||
|
|
||
| import kr.ac.kookmin.stream.common.ErrorCode; | ||
| import kr.ac.kookmin.stream.common.ErrorStatus; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.experimental.Accessors; | ||
|
|
||
| @Getter | ||
| @Accessors(fluent = true) | ||
| @AllArgsConstructor | ||
| public enum OAuthErrorCode implements ErrorCode { | ||
|
|
||
| UNSUPPORTED_OAUTH_PROVIDER(ErrorStatus.BAD_REQUEST, "지원하지 않는 로그인 방식입니다."), | ||
| REDIRECT_URI_NOT_ALLOWED(ErrorStatus.BAD_REQUEST, "허용되지 않은 redirect URI입니다."), | ||
| INVALID_AUTHORIZATION_CODE(ErrorStatus.UNAUTHORIZED, "로그인이 만료되었습니다. 다시 로그인해 주세요."), | ||
| OAUTH_PROVIDER_UNAVAILABLE(ErrorStatus.BAD_GATEWAY, "로그인 서버에 연결할 수 없습니다. 잠시 후 다시 시도해 주세요."); | ||
|
|
||
| private final int status; | ||
| private final String message; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.domain; | ||
|
|
||
| public record OAuthLoginCommand( | ||
| OAuthProvider provider, | ||
| String code, | ||
| String codeVerifier, | ||
| String redirectUri | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.domain; | ||
|
|
||
| import java.util.Arrays; | ||
| import kr.ac.kookmin.stream.common.BusinessException; | ||
|
|
||
| public enum OAuthProvider { | ||
| KCONNECT; | ||
|
|
||
| /** 로그인 경로의 provider 값(예: "kconnect")을 대소문자 구분 없이 변환한다. */ | ||
| public static OAuthProvider from(String value) { | ||
|
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. 주석대로 단순히 로그인 경로의 provider 값을 대소문자 구분 없이 변환하는 로직이라면, toUpperCase()와 같은 메서드를 사용하지 않고 arrray로 변환하여 처리하는 복잡한 과정을 거친 이유가 궁금합니다!
Member
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. 지금 from 메서드는 아래 Java 코드와 같은 동작을 합니다. 대소문자 구분없이 비교는 손으로 작성한 코드라서 오류가 있을 수도 있습니다. 참고 부탁드려요~ for (OAuthProvider provider : values()) {
if (provider.name().equalsIgnoreCase(value)) {
return provider;
}
}
throw new BusinessException(OAuthErrorCode.UNSUPPORTED_OAUTH_PROVIDER);
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. 현재 enum에 있는 값이 하나뿐이라서 array로 순회하는 구조가 의문이었던건데, 나중에 다른 enum값이 들어간다고 생각하니 이해가 되었습니다. 감사합니다! |
||
| return Arrays.stream(values()) | ||
| .filter(provider -> provider.name().equalsIgnoreCase(value)) | ||
| .findFirst() | ||
| .orElseThrow(() -> new BusinessException(OAuthErrorCode.UNSUPPORTED_OAUTH_PROVIDER)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.domain; | ||
|
|
||
| /** | ||
| * provider에서 확인한 사용자 정보. | ||
| * <p> | ||
| * providerUserId는 provider 안에서 바뀌지 않는 고유 ID로, 회원을 찾는 키가 된다. | ||
| * studentId·major·academicStatus는 학번·학과·학적을 주지 않는 provider(구글·카카오)에서 null일 수 있다. | ||
| * academicStatus는 provider가 주는 학적 상태 값을 그대로 담는다. | ||
| */ | ||
| public record OAuthUserInfo( | ||
| OAuthProvider provider, | ||
| String providerUserId, | ||
| String studentId, | ||
| String name, | ||
| String major, | ||
| String academicStatus | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.repository; | ||
|
|
||
| import java.util.Optional; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthAccount; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthProvider; | ||
|
|
||
| public interface OAuthAccountRepository { | ||
| Optional<OAuthAccount> findByProviderAndProviderUserId(OAuthProvider provider, String providerUserId); | ||
| OAuthAccount save(OAuthAccount oauthAccount); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.service; | ||
|
|
||
| import java.util.Optional; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthLoginCommand; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthProvider; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthUserInfo; | ||
|
|
||
| public interface OAuthService { | ||
| OAuthUserInfo authenticate(OAuthLoginCommand command); | ||
| Optional<Long> findMemberId(OAuthProvider provider, String providerUserId); | ||
| void link(OAuthProvider provider, String providerUserId, Long memberId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.service.impl; | ||
|
|
||
| import java.util.EnumMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.client.OAuthClient; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthErrorCode; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthProvider; | ||
| import kr.ac.kookmin.stream.common.BusinessException; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| /** | ||
| * 빈으로 등록된 OAuthClient 구현체를 provider별로 모아 두고, 요청의 provider에 맞는 구현체를 돌려준다. | ||
| * 같은 provider의 구현체가 둘이면 기동 시 실패한다. | ||
| */ | ||
| @Component | ||
| class OAuthClientRegistry { | ||
|
|
||
| private final Map<OAuthProvider, OAuthClient> clients; | ||
|
|
||
| OAuthClientRegistry(List<OAuthClient> clients) { | ||
|
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. provider마다 구현체를 하나씩 빈으로 등록해 두고 요청 경로의 provider 값에 맞는 구현체를 레지스트리에서 꺼내 쓰는 구조로 이해했습니다! 그래서 나중에 새로운 provider가 추가돼도 구현체만 하나 더 만들면 되고, 서비스나 컨트롤러는 손대지 않아도 되는 점이 좋은거 같다고 생각합니다 |
||
| this.clients = clients.stream() | ||
| .collect(Collectors.toMap( | ||
| OAuthClient::provider, | ||
| Function.identity(), | ||
| (first, second) -> { | ||
| throw new IllegalStateException("OAuthClient 구현체가 중복됐습니다: " + first.provider()); | ||
| }, | ||
| () -> new EnumMap<>(OAuthProvider.class) | ||
| )); | ||
| } | ||
|
|
||
| OAuthClient get(OAuthProvider provider) { | ||
| OAuthClient client = clients.get(provider); | ||
| if (client == null) { | ||
| throw new BusinessException(OAuthErrorCode.UNSUPPORTED_OAUTH_PROVIDER); | ||
| } | ||
| return client; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package kr.ac.kookmin.stream.auth.domain.oauth.service.impl; | ||
|
|
||
| import java.util.Optional; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.client.OAuthClient; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthAccount; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthErrorCode; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthLoginCommand; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthProvider; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthUserInfo; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.repository.OAuthAccountRepository; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.service.OAuthService; | ||
| import kr.ac.kookmin.stream.common.BusinessException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| class OAuthServiceImpl implements OAuthService { | ||
|
|
||
| private final OAuthClientRegistry oauthClientRegistry; | ||
| private final OAuthAccountRepository oauthAccountRepository; | ||
|
|
||
| // 외부 호출만 하므로 트랜잭션을 걸지 않는다 | ||
| @Override | ||
| public OAuthUserInfo authenticate(OAuthLoginCommand command) { | ||
| OAuthClient client = oauthClientRegistry.get(command.provider()); | ||
| if (!client.isAllowedRedirectUri(command.redirectUri())) { | ||
| throw new BusinessException(OAuthErrorCode.REDIRECT_URI_NOT_ALLOWED); | ||
| } | ||
|
|
||
| return client.fetchUserInfo(command); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Long> findMemberId(OAuthProvider provider, String providerUserId) { | ||
| return oauthAccountRepository.findByProviderAndProviderUserId(provider, providerUserId) | ||
| .map(OAuthAccount::getMemberId); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void link(OAuthProvider provider, String providerUserId, Long memberId) { | ||
| oauthAccountRepository.save(OAuthAccount.create(memberId, provider, providerUserId)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** | ||
| * 계층 패키지(domain/{도메인}/{domain|repository|service})를 그대로 공개하기 위해 OPEN으로 둔다. | ||
| * service.impl 접근 차단은 bootstrap의 DomainImplAccessTests(ArchUnit)가 담당한다. | ||
| */ | ||
| @ApplicationModule(type = ApplicationModule.Type.OPEN) | ||
| package kr.ac.kookmin.stream.auth; | ||
|
|
||
| import org.springframework.modulith.ApplicationModule; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package kr.ac.kookmin.stream.db.auth; | ||
|
|
||
| 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 jakarta.persistence.UniqueConstraint; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthAccount; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthProvider; | ||
| import kr.ac.kookmin.stream.db.common.BaseTimeEntity; | ||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Entity | ||
| @Table( | ||
| name = "oauth_accounts", | ||
| uniqueConstraints = { | ||
| @UniqueConstraint( | ||
| name = "uk_oauth_accounts_provider_provider_user_id", | ||
| columnNames = {"provider", "provider_user_id"} | ||
| ), | ||
| @UniqueConstraint( | ||
| name = "uk_oauth_accounts_member_id_provider", | ||
| columnNames = {"member_id", "provider"} | ||
| ) | ||
| } | ||
| ) | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class OAuthAccountJpaEntity extends BaseTimeEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "oauth_account_id") | ||
| private Long id; | ||
|
|
||
| @Column(name = "member_id", nullable = false) | ||
| private Long memberId; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(nullable = false, length = 20) | ||
| private OAuthProvider provider; | ||
|
|
||
| @Column(name = "provider_user_id", nullable = false) | ||
| private String providerUserId; | ||
|
|
||
| private OAuthAccountJpaEntity(OAuthAccount oauthAccount) { | ||
| this.id = oauthAccount.getId(); | ||
| this.memberId = oauthAccount.getMemberId(); | ||
| this.provider = oauthAccount.getProvider(); | ||
| this.providerUserId = oauthAccount.getProviderUserId(); | ||
| } | ||
|
|
||
| public static OAuthAccountJpaEntity from(OAuthAccount oauthAccount) { | ||
| return new OAuthAccountJpaEntity(oauthAccount); | ||
| } | ||
|
|
||
| public OAuthAccount toDomain() { | ||
| return OAuthAccount.of(id, memberId, provider, providerUserId); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package kr.ac.kookmin.stream.db.auth; | ||
|
|
||
| import java.util.Optional; | ||
| import kr.ac.kookmin.stream.auth.domain.oauth.domain.OAuthProvider; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface OAuthAccountJpaRepository extends JpaRepository<OAuthAccountJpaEntity, Long> { | ||
| Optional<OAuthAccountJpaEntity> findByProviderAndProviderUserId(OAuthProvider provider, String providerUserId); | ||
| } |
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.
provider 토큰을 아예 저장하지 않는 것으로 이해했는데 로그인할 때 한 번만 쓰고 버리는 구조인건지 궁금합니다!!
-> 후속 PR을 통해 이해됐습니다!!