-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: #1 애플로그인, 회원가입, 자동 로그인(토큰 refresh) 로직 구현
- Loading branch information
Showing
31 changed files
with
699 additions
and
120 deletions.
There are no files selected for viewing
This file contains 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 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
15 changes: 15 additions & 0 deletions
15
src/main/java/com/example/nzgeneration/domain/auth/AppleAuthApiClient.java
This file contains 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,15 @@ | ||
package com.example.nzgeneration.domain.auth; | ||
|
||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.OIDCPublicKeysResponse; | ||
import org.springframework.cloud.openfeign.FeignClient; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
|
||
@FeignClient(name = "AppleOAuthClient", url = "https://appleid.apple.com") | ||
public interface AppleAuthApiClient { | ||
|
||
@GetMapping("/auth/keys") | ||
OIDCPublicKeysResponse getAppleOIDCOpenKeys(); | ||
|
||
|
||
} |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/example/nzgeneration/domain/auth/AppleOauthHelper.java
This file contains 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,28 @@ | ||
package com.example.nzgeneration.domain.auth; | ||
|
||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.OIDCPublicKeysResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.OIDCDecodePayload; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class AppleOauthHelper { | ||
private final AppleAuthApiClient appleAuthApiClient; | ||
private final OauthOIDCHelper oauthOIDCHelper; | ||
|
||
@Value("${social-login.provider.apple.client-id}") | ||
private String aud; | ||
|
||
@Value("${social-login.provider.apple.issuer}") | ||
private String iss; | ||
|
||
public OIDCDecodePayload getOIDCDecodePayload(String token){ | ||
OIDCPublicKeysResponse oidcPublicKeysResponse = appleAuthApiClient.getAppleOIDCOpenKeys(); | ||
return oauthOIDCHelper.getPayloadFromIdToken( | ||
token, iss, aud, oidcPublicKeysResponse | ||
); | ||
} | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
src/main/java/com/example/nzgeneration/domain/auth/AuthController.java
This file contains 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,53 @@ | ||
package com.example.nzgeneration.domain.auth; | ||
|
||
import com.example.nzgeneration.domain.auth.dto.AuthRequestDto.CreateUserRequest; | ||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.LoginSimpleInfo; | ||
import com.example.nzgeneration.domain.auth.enums.ResponseType; | ||
import com.example.nzgeneration.global.common.response.ApiResponse; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
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.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/auth") | ||
@Tag(name="Auth", description = "인증(로그인, 회원가입) 관련") | ||
public class AuthController { | ||
|
||
private final AuthService authService; | ||
|
||
@PostMapping("/login") | ||
@Operation(summary = "로그인/회원가입 api", description = "code : Authorization code / 회원가입, 로그인 구분 없이 동일한 API 사용") | ||
public ApiResponse<LoginSimpleInfo> login(@RequestHeader("Authorization") String idToken){ | ||
if (idToken != null && idToken.startsWith("Bearer ")) { | ||
idToken = idToken.substring("Bearer ".length()); | ||
} | ||
LoginSimpleInfo loginSimpleInfo = authService.login(idToken); | ||
if(loginSimpleInfo.getResponseType()== ResponseType.SIGN_IN){ | ||
return ApiResponse.onSuccess(loginSimpleInfo); | ||
} | ||
return ApiResponse.onFailure(4003, "추가 정보 입력이 필요합니다", loginSimpleInfo); | ||
} | ||
|
||
@PostMapping("/signup/extra") | ||
@Operation(summary = "회원가입 추가 정보 입력 api") | ||
public ApiResponse<LoginSimpleInfo> signUp(@RequestHeader("Authorization") String token, @RequestBody CreateUserRequest createUserRequest){ | ||
if (token != null && token.startsWith("Bearer ")) { | ||
token = token.substring("Bearer ".length()); | ||
} | ||
return ApiResponse.onSuccess(authService.signUp(token, createUserRequest)); | ||
} | ||
|
||
@PostMapping("/refresh-token") | ||
@Operation(summary = "access token, refresh token 재발급") | ||
public ApiResponse<LoginSimpleInfo> refreshToken(@RequestParam String refreshToken){ | ||
return ApiResponse.onSuccess(authService.updateUserToken(refreshToken)); | ||
} | ||
|
||
} |
70 changes: 70 additions & 0 deletions
70
src/main/java/com/example/nzgeneration/domain/auth/AuthService.java
This file contains 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,70 @@ | ||
package com.example.nzgeneration.domain.auth; | ||
|
||
import com.example.nzgeneration.domain.auth.dto.AuthRequestDto.CreateUserRequest; | ||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.LoginSimpleInfo; | ||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.OIDCDecodePayload; | ||
import com.example.nzgeneration.domain.auth.enums.ResponseType; | ||
import com.example.nzgeneration.domain.user.User; | ||
import com.example.nzgeneration.domain.user.UserRepository; | ||
import com.example.nzgeneration.global.common.response.ApiResponse; | ||
import com.example.nzgeneration.global.common.response.code.status.ErrorStatus; | ||
import com.example.nzgeneration.global.common.response.exception.GeneralException; | ||
import com.example.nzgeneration.global.security.JwtTokenProvider; | ||
import java.util.Optional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class AuthService { | ||
|
||
private final AppleOauthHelper appleOauthHelper; | ||
private final UserRepository userRepository; | ||
private final JwtTokenProvider jwtTokenProvider; | ||
@Transactional | ||
public LoginSimpleInfo login(String idToken){ | ||
OIDCDecodePayload oidcDecodePayload = appleOauthHelper.getOIDCDecodePayload(idToken); | ||
String email = oidcDecodePayload.email(); | ||
Optional<User> optionalMember = userRepository.findByEmail(email); | ||
String accessToken, refreshToken; | ||
if(optionalMember.isPresent()){ //로그인 로직 | ||
accessToken = jwtTokenProvider.createAccessToken(optionalMember.get().getPayload()); | ||
refreshToken = jwtTokenProvider.createRefreshToken(optionalMember.get().getId()); | ||
return LoginSimpleInfo.toDTO(accessToken, refreshToken, ResponseType.SIGN_IN); | ||
} | ||
accessToken = jwtTokenProvider.generateTempToken(email); | ||
return LoginSimpleInfo.toDTO(accessToken, null, ResponseType.SIGN_UP); | ||
|
||
} | ||
|
||
@Transactional | ||
public LoginSimpleInfo signUp(String token, CreateUserRequest createUserRequest){ | ||
String email = jwtTokenProvider.validateTempTokenAndGetEmail(token); | ||
if(userRepository.findByEmail(email).isPresent()) | ||
throw new GeneralException(ErrorStatus._DUPLICATE_USER); | ||
User user = User.toEntity(email, createUserRequest); | ||
userRepository.save(user); | ||
String accessToken = jwtTokenProvider.createAccessToken(user.getPayload()); | ||
String refreshToken = jwtTokenProvider.createRefreshToken(user.getId()); | ||
user.updateToken(accessToken, refreshToken); | ||
return LoginSimpleInfo.toDTO(accessToken, refreshToken, ResponseType.SIGN_IN); | ||
} | ||
|
||
@Transactional | ||
public LoginSimpleInfo updateUserToken(String refreshToken) { | ||
if(userRepository.findByRefreshToken(refreshToken).isEmpty()){ | ||
throw new GeneralException(ErrorStatus._EXPIRED_JWT); | ||
} | ||
Long userId = Long.valueOf(jwtTokenProvider.getPayload(refreshToken)); | ||
User user = userRepository.findById(userId) | ||
.orElseThrow(() -> new GeneralException(ErrorStatus._INVALID_JWT)); | ||
String newRefreshToken = jwtTokenProvider.createRefreshToken(userId); | ||
String newAccesstoken = jwtTokenProvider.refreshAccessToken(refreshToken); | ||
user.updateToken(newAccesstoken, newRefreshToken); | ||
return new LoginSimpleInfo(newAccesstoken, newRefreshToken, ResponseType.TOKEN_REFRESH); | ||
|
||
} | ||
|
||
|
||
} |
35 changes: 35 additions & 0 deletions
35
src/main/java/com/example/nzgeneration/domain/auth/OauthOIDCHelper.java
This file contains 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,35 @@ | ||
package com.example.nzgeneration.domain.auth; | ||
|
||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.OIDCDecodePayload; | ||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.OIDCPublicKey; | ||
import com.example.nzgeneration.domain.auth.dto.AuthResponseDto.OIDCPublicKeysResponse; | ||
import com.example.nzgeneration.global.security.JwtOIDCProvider; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class OauthOIDCHelper { | ||
|
||
private final JwtOIDCProvider jwtOIDCProvider; | ||
|
||
//토큰에서 kid 가져온다 -> 가져온 kid는 공개키 결정에 사용 | ||
private String getKidFromUnsignedIdToken(String token, String iss, String aud){ | ||
return jwtOIDCProvider.getKidFromUnsignedTokenHeader(token, iss, aud); | ||
} | ||
|
||
public OIDCDecodePayload getPayloadFromIdToken(String token, String iss, String aud, OIDCPublicKeysResponse oidcPublicKeysResponse){ | ||
String kid = getKidFromUnsignedIdToken(token, iss, aud); | ||
|
||
//같은 Kid인 공개키 불러와서 토큰 검증에 사용 | ||
OIDCPublicKey oidcPublicKey = oidcPublicKeysResponse.keys().stream() | ||
.filter(o-> o.kid().equals(kid)) | ||
.findFirst() | ||
.orElseThrow(); | ||
|
||
//검증 된 토큰에서 바디를 꺼내온다 | ||
return jwtOIDCProvider.getOIDCTokenBody(token, oidcPublicKey.n(), oidcPublicKey.e()); | ||
} | ||
|
||
} | ||
|
21 changes: 21 additions & 0 deletions
21
src/main/java/com/example/nzgeneration/domain/auth/dto/AuthRequestDto.java
This file contains 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,21 @@ | ||
package com.example.nzgeneration.domain.auth.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
public class AuthRequestDto { | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public static class CreateUserRequest { | ||
private String nickName; | ||
private String walletAddress; | ||
private String profileImgUrl; | ||
private Boolean isAllowLocationInfo; | ||
private Boolean isAllowAdInfo; | ||
|
||
} | ||
|
||
} |
59 changes: 59 additions & 0 deletions
59
src/main/java/com/example/nzgeneration/domain/auth/dto/AuthResponseDto.java
This file contains 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,59 @@ | ||
package com.example.nzgeneration.domain.auth.dto; | ||
|
||
import com.example.nzgeneration.domain.auth.enums.ResponseType; | ||
import com.fasterxml.jackson.databind.PropertyNamingStrategies; | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
import java.util.List; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
public class AuthResponseDto { | ||
@Builder | ||
@Getter | ||
@AllArgsConstructor | ||
@NoArgsConstructor | ||
public static class LoginSimpleInfo{ | ||
private String accessToken; | ||
private String refreshToken; | ||
private ResponseType responseType; | ||
|
||
public static LoginSimpleInfo toDTO(String accessToken, String refreshToken, ResponseType responseType) { | ||
return LoginSimpleInfo.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.responseType(responseType) | ||
.build(); | ||
|
||
} | ||
|
||
} | ||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | ||
public record OIDCPublicKeysResponse(List<OIDCPublicKey> keys) { | ||
} | ||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | ||
public record OIDCPublicKey( | ||
String kty, | ||
String kid, | ||
String use, | ||
String alg, | ||
String n, | ||
String e | ||
) { | ||
} | ||
|
||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | ||
public record OIDCDecodePayload( | ||
String issuer, | ||
String audience, | ||
String subject, | ||
String email | ||
|
||
){ | ||
} | ||
|
||
|
||
|
||
|
||
} |
5 changes: 5 additions & 0 deletions
5
src/main/java/com/example/nzgeneration/domain/auth/enums/ResponseType.java
This file contains 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,5 @@ | ||
package com.example.nzgeneration.domain.auth.enums; | ||
|
||
public enum ResponseType { | ||
SIGN_IN, SIGN_UP, TOKEN_REFRESH | ||
} |
This file contains 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 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
Oops, something went wrong.