-
Notifications
You must be signed in to change notification settings - Fork 2
[FEAT] 소셜 로그인 및 인증 api 구현 #61
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
43bf093
670d712
26e1c8a
990e644
838133b
4a2f0e3
90241bb
f09ca1f
b7a362a
40b4527
9c26a57
5f1bf67
12c45bc
e834072
503d89e
65eb092
d952845
867caa2
5d2aed4
ac7e912
19c7bd0
ec853a0
4006ee9
dec0289
4dd0fa7
cdc38d5
7c465ca
bd6d519
64b49f9
9ab33ff
634bccb
d6ba3c0
12aad78
029a1ee
22b11bc
cf0788f
660efa3
5336c26
189cf55
1be422d
f13ce01
92027d5
363a09c
894c00c
d793aa9
cfa6b37
2af091b
2a3f24e
ad6c4f0
18d69dd
b274c10
17e4dc8
fc257ef
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 |
|---|---|---|
| @@ -1,35 +1,95 @@ | ||
| package com.mr.domain.auth.controller; | ||
|
|
||
| import com.mr.domain.auth.dto.AuthRequestDTO; | ||
| import com.mr.domain.auth.dto.AuthResponseDTO; | ||
| import com.mr.domain.auth.dto.req.AuthRequestDTO; | ||
| import com.mr.domain.auth.dto.res.AuthResponseDTO; | ||
| import com.mr.domain.auth.entity.enums.SocialType; | ||
| import com.mr.domain.auth.service.AuthService; | ||
| import com.mr.global.apipayload.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.security.SecurityRequirements; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.annotation.Profile; | ||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestHeader; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import com.mr.global.security.SecurityUtil; | ||
|
|
||
| @Tag(name = "Auth API", description = "인증 및 소셜 로그인 관련 API") | ||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/auth") | ||
| @Profile({"local", "dev"}) | ||
| public class AuthController { | ||
|
|
||
| private final AuthService authService; | ||
|
|
||
| @SecurityRequirements | ||
| @Operation( | ||
| summary = "소셜 로그인 / 회원가입 API", | ||
| description = "카카오 및 구글 OAuth Access Token을 받아 로그인을 진행하고, 서비스 전용 JWT 토큰을 발급합니다." | ||
| ) | ||
| @PostMapping("/login/{socialType}") | ||
| public ApiResponse<AuthResponseDTO.LoginResponse> socialLogin( | ||
| @Parameter(description = "소셜 로그인 제공자 (KAKAO, GOOGLE)", example = "KAKAO") | ||
| @PathVariable(name = "socialType") SocialType socialType, | ||
| @RequestBody @Valid AuthRequestDTO.SocialLoginRequest request | ||
| @RequestBody @Valid AuthRequestDTO.SocialLoginRequest request, | ||
| @RequestHeader(value = HttpHeaders.USER_AGENT, defaultValue = "Unknown Device") String deviceInfo | ||
| ) { | ||
| AuthResponseDTO.LoginResponse response = authService.socialLogin(socialType, request.accessToken()); | ||
| AuthResponseDTO.LoginResponse response = authService.socialLogin(socialType, request.accessToken(), deviceInfo); | ||
| return ApiResponse.onSuccess(response); | ||
| } | ||
| @SecurityRequirements | ||
| @Operation(summary = "토큰 재발급 API", description = "만료된 Access Token을 Refresh Token을 이용해 재발급합니다.") | ||
| @PostMapping("/reissue") | ||
| public ApiResponse<AuthResponseDTO.TokenInfo> reissue( | ||
| @RequestBody @Valid AuthRequestDTO.TokenRefreshRequest request | ||
| ) { | ||
| AuthResponseDTO.TokenInfo tokenInfo = authService.reissueToken(request.refreshToken()); | ||
| return ApiResponse.onSuccess(tokenInfo); | ||
| } | ||
|
kimyw1018 marked this conversation as resolved.
|
||
|
|
||
| @Operation( | ||
| summary = "소셜 계정 추가 연동 API", | ||
| description = "현재 로그인된 사용자의 계정에 새로운 소셜 계정(카카오/구글)을 추가로 연동합니다." | ||
| ) | ||
| @PostMapping("/link/{socialType}") | ||
| public ApiResponse<AuthResponseDTO.TokenInfo> linkSocialAccount( | ||
| @Parameter(description = "연동할 소셜 제공자 (KAKAO, GOOGLE)", example = "GOOGLE") | ||
| @PathVariable(name = "socialType") SocialType socialType, | ||
| @RequestBody @Valid AuthRequestDTO.SocialLoginRequest request, | ||
| @RequestHeader(value = HttpHeaders.USER_AGENT, defaultValue = "Unknown Device") String deviceInfo | ||
| ) { | ||
| Long userId = SecurityUtil.getCurrentUserId(); | ||
| AuthResponseDTO.TokenInfo tokenInfo = authService.linkSocialAccount(userId, socialType, request.accessToken(), deviceInfo); | ||
| return ApiResponse.onSuccess(tokenInfo); | ||
| } | ||
|
|
||
| @Operation( | ||
| summary = "로그아웃 API", | ||
| description = "현재 요청 기기의 Refresh Token 세션을 선택적으로 만료 처리합니다." | ||
| ) | ||
| @PostMapping("/logout") | ||
| public ApiResponse<Void> logout( | ||
| @RequestBody @Valid AuthRequestDTO.LogoutRequest request | ||
| ) { | ||
| Long userId = SecurityUtil.getCurrentUserId(); | ||
| authService.logout(userId, request.refreshToken()); | ||
| return ApiResponse.onSuccess(null); | ||
| } | ||
|
|
||
| @Operation( | ||
| summary = "회원 탈퇴 API", | ||
| description = "사용자 계정을 탈퇴 처리하고 저장된 소셜 인증 정보 및 Refresh Token 세션을 완전히 삭제합니다." | ||
| ) | ||
| @PostMapping("/withdraw") | ||
| public ApiResponse<Void> withdraw() { | ||
| Long userId = SecurityUtil.getCurrentUserId(); | ||
| authService.withdraw(userId); | ||
| return ApiResponse.onSuccess(null); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.mr.domain.auth.dto; | ||
|
|
||
| import lombok.Builder; | ||
|
|
||
| @Builder | ||
| public record OAuthUserInfo( | ||
| String socialId, | ||
| String profileImgUrl | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.mr.domain.auth.dto.res; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
|
||
| public record GoogleUserResponse( | ||
| String id, | ||
| String picture, | ||
| String email | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.mr.domain.auth.dto.res; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
|
||
| public record KakaoUserResponse( | ||
| Long id, | ||
| @JsonProperty("kakao_account") | ||
| KakaoAccount kakaoAccount | ||
| ) { | ||
| public record KakaoAccount( | ||
| Profile profile | ||
| ) {} | ||
|
|
||
| public record Profile( | ||
| @JsonProperty("profile_image_url") | ||
| String profileImageUrl | ||
| ) {} | ||
| } |
|
Contributor
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. 지금 소셜 계정 매핑 정보와 서비스 Refresh Token 세션 정보를 동시에 관리하고 있는 걸로 보이는데, 이 구조에서는 같은 사용자가 다른 기기에서 로그인할 때 기존 Refresh Token이 덮어써져 기존 기기의 세션이 자동으로 만료될 것 같아요! 사용자당 단일 세션 정책이 의도된 것인지 궁금합니다~
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. 뮤즈리뷰가 멀티 디바이스 지원이 필요하다 생각하지 않아서 단일 세션으로 구현했는데 이부분 논의해보면 좋을 것 같습니다! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| package com.mr.domain.auth.entity.enums; | ||
|
|
||
| public enum SocialType { | ||
| kakao, | ||
| KAKAO, | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.