-
Notifications
You must be signed in to change notification settings - Fork 38
[ian.lee2] step2 과제 제출입니다. #63
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
base: ianlee2
Are you sure you want to change the base?
Changes from all commits
97cfb2d
ad2ecce
f68f506
38cbdfa
4e1c51b
34a0bea
cea6ca1
76618a8
465a5d2
a1b54f0
f39f2da
506bb5b
9e30582
af9dda7
d62a74e
bc85cd6
ac04b1e
4f91205
d27586c
9327de7
6d2c920
3998ae6
b20a4e3
0679672
7feda3e
144e828
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package gift.auth.controller; | ||
|
|
||
| import gift.auth.exception.InvalidOAuthProviderException; | ||
| import gift.auth.service.OAuthService; | ||
| import gift.auth.dto.TokenResponse; | ||
| import gift.external.ExternalProvider; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping(path = "/api/auth") | ||
| @RequiredArgsConstructor | ||
| public class OAuthController { | ||
| private final OAuthService oAuthService; | ||
|
|
||
| @GetMapping(path = "/{provider}/login") | ||
| public ResponseEntity<Void> login(@PathVariable String provider) { | ||
| ExternalProvider externalProvider = parseProvider(provider); | ||
| return ResponseEntity.status(HttpStatus.FOUND) | ||
| .header(HttpHeaders.LOCATION, oAuthService.getLoginUri(externalProvider).toString()) | ||
| .build(); | ||
| } | ||
|
|
||
| @GetMapping(path = "/{provider}/callback") | ||
| public ResponseEntity<TokenResponse> callback( | ||
| @PathVariable String provider, | ||
| @RequestParam("code") String code | ||
| ) { | ||
| ExternalProvider externalProvider = parseProvider(provider); | ||
| return ResponseEntity.ok(oAuthService.handleCallback(externalProvider, code)); | ||
| } | ||
|
|
||
| private ExternalProvider parseProvider(String provider) { | ||
| try { | ||
| return ExternalProvider.valueOf(provider.toUpperCase()); | ||
| } catch (IllegalArgumentException e) { | ||
| throw new InvalidOAuthProviderException(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package gift.auth; | ||
| package gift.auth.dto; | ||
|
|
||
| /** | ||
| * Response containing a JWT access token. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package gift.auth.exception; | ||
|
|
||
| import gift.common.BaseErrorCode; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public enum AuthErrorCode implements BaseErrorCode { | ||
| AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED, "인증에 실패했습니다."), | ||
| ACCESS_DENIED(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."), | ||
| INVALID_OAUTH_PROVIDER(HttpStatus.BAD_REQUEST, "지원하지 않는 OAuth Provider입니다."); | ||
|
|
||
| private final HttpStatus httpStatus; | ||
| private final String message; | ||
|
|
||
| AuthErrorCode(HttpStatus httpStatus, String message) { | ||
| this.httpStatus = httpStatus; | ||
| this.message = message; | ||
| } | ||
|
|
||
| @Override | ||
| public HttpStatus getHttpStatus() { | ||
| return httpStatus; | ||
| } | ||
|
|
||
| @Override | ||
| public String getMessage() { | ||
| return message; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package gift.auth.exception; | ||
|
|
||
| import gift.common.BaseException; | ||
|
|
||
| public class AuthenticationException extends BaseException { | ||
| public AuthenticationException() { | ||
| super(AuthErrorCode.AUTHENTICATION_FAILED); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package gift.auth.exception; | ||
|
|
||
| import gift.common.BaseException; | ||
|
|
||
| public class ForbiddenException extends BaseException { | ||
| public ForbiddenException() { | ||
| super(AuthErrorCode.ACCESS_DENIED); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package gift.auth.exception; | ||
|
|
||
| import gift.common.BaseException; | ||
|
|
||
| public class InvalidOAuthProviderException extends BaseException { | ||
| public InvalidOAuthProviderException() { | ||
| super(AuthErrorCode.INVALID_OAUTH_PROVIDER); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package gift.auth; | ||
| package gift.auth.jwt; | ||
|
|
||
| import io.jsonwebtoken.Jwts; | ||
| import io.jsonwebtoken.security.Keys; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package gift.auth.oauth; | ||
|
|
||
| import gift.external.ExternalProvider; | ||
|
|
||
| import java.net.URI; | ||
|
|
||
| public interface OAuthClient { | ||
| ExternalProvider provider(); | ||
|
|
||
| URI getLoginUri(); | ||
|
|
||
| OAuthUserInfo getUserInfo(String code); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package gift.auth.oauth; | ||
|
|
||
| import gift.external.ExternalProvider; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Component | ||
| public class OAuthClientRegistry { | ||
| private final Map<ExternalProvider, OAuthClient> clients; | ||
|
|
||
| public OAuthClientRegistry(List<OAuthClient> clients) { | ||
| this.clients = clients.stream() | ||
| .collect(Collectors.toMap(OAuthClient::provider, Function.identity())); | ||
| } | ||
|
|
||
| public OAuthClient get(ExternalProvider provider) { | ||
| OAuthClient client = clients.get(provider); | ||
| if (client == null) { | ||
| throw new IllegalStateException("OAuth client not found. provider=" + provider); | ||
| } | ||
| return client; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package gift.auth.oauth; | ||
|
|
||
| public record OAuthUserInfo(String email, String accessToken) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package gift.auth.service; | ||
|
|
||
| import gift.auth.dto.TokenResponse; | ||
| import gift.auth.jwt.JwtProvider; | ||
| import gift.auth.oauth.OAuthClient; | ||
| import gift.auth.oauth.OAuthClientRegistry; | ||
| import gift.auth.oauth.OAuthUserInfo; | ||
| import gift.external.ExternalProvider; | ||
| import gift.member.entity.Member; | ||
| import gift.member.repository.MemberRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.net.URI; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional(readOnly = true) | ||
| public class OAuthService { | ||
| private final OAuthClientRegistry oAuthClientRegistry; | ||
| private final MemberRepository memberRepository; | ||
|
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. 144e828 커밋에서 서비스 간 교차 Repository 의존을 제거했는데 |
||
| private final JwtProvider jwtProvider; | ||
|
|
||
| public URI getLoginUri(ExternalProvider provider) { | ||
| OAuthClient client = oAuthClientRegistry.get(provider); | ||
| return client.getLoginUri(); | ||
| } | ||
|
|
||
| @Transactional | ||
| public TokenResponse handleCallback(ExternalProvider provider, String code) { | ||
| OAuthClient client = oAuthClientRegistry.get(provider); | ||
| OAuthUserInfo userInfo = client.getUserInfo(code); | ||
| String email = userInfo.email(); | ||
|
|
||
| Member member = memberRepository.findByEmail(email) | ||
| .orElseGet(() -> new Member(email)); | ||
| if (provider == ExternalProvider.KAKAO) { | ||
| member.updateKakaoAccessToken(userInfo.accessToken()); | ||
| } | ||
| memberRepository.save(member); | ||
|
|
||
| String token = jwtProvider.createToken(member.getEmail()); | ||
| return new TokenResponse(token); | ||
| } | ||
| } | ||
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.
structural-change-plan.md에서 "Javadoc 삭제 — 나머지 엔티티에도 없으므로 통일한다"고 명시했는데 여기에는 Javadoc이 남아 있네요?