Skip to content
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

[feat] 소셜 로그인 API 구현 #15

Merged
merged 11 commits into from
Jan 7, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.doorip.user.api;

import lombok.RequiredArgsConstructor;
import org.doorip.common.ApiResponse;
import org.doorip.common.ApiResponseUtil;
import org.doorip.message.SuccessMessage;
import org.doorip.user.dto.request.UserSignInRequest;
import org.doorip.user.dto.response.UserResponse;
import org.doorip.user.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RequestMapping("/api/users")
@Controller
public class UserApiController {
private final UserService userService;

@PostMapping("/signin")
public ResponseEntity<ApiResponse<?>> signIn(@RequestHeader("Authorization") final String token,
@RequestParam final UserSignInRequest request) {
final UserResponse response = userService.signIn(token, request);
return ApiResponseUtil.success(SuccessMessage.OK, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.doorip.user.dto.request;

public record UserSignInRequest(
String platform
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.doorip.user.dto.response;

import org.doorip.auth.jwt.Token;

public record UserResponse(
String accessToken,
String refreshToken
) {

public static UserResponse of(Token token) {
return new UserResponse(
token.accessToken(),
token.refreshToken()
);
}
}
60 changes: 60 additions & 0 deletions doorip-api/src/main/java/org/doorip/user/service/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.doorip.user.service;

import lombok.RequiredArgsConstructor;
import org.doorip.auth.jwt.JwtProvider;
import org.doorip.auth.jwt.Token;
import org.doorip.exception.EntityNotFoundException;
import org.doorip.message.ErrorMessage;
import org.doorip.openfeign.apple.AppleOAuthProvider;
import org.doorip.openfeign.kakao.KakaoOAuthProvider;
import org.doorip.user.domain.Platform;
import org.doorip.user.domain.RefreshToken;
import org.doorip.user.domain.User;
import org.doorip.user.dto.request.UserSignInRequest;
import org.doorip.user.dto.response.UserResponse;
import org.doorip.user.repository.RefreshTokenRepository;
import org.doorip.user.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static org.doorip.user.domain.Platform.APPLE;
import static org.doorip.user.domain.Platform.getEnumPlatformFromStringPlatform;

@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class UserService {
private final UserRepository userRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final JwtProvider jwtProvider;
private final AppleOAuthProvider appleOAuthProvider;
private final KakaoOAuthProvider kakaoOAuthProvider;

@Transactional
public UserResponse signIn(String token, UserSignInRequest request) {
Platform enumPlatform = getEnumPlatformFromStringPlatform(request.platform());
String platformId = getPlatformId(token, enumPlatform);
User findUser = getUser(enumPlatform, platformId);
Token issueToken = jwtProvider.issueToken(findUser.getId());
updateRefreshToken(issueToken.refreshToken(), findUser);

return UserResponse.of(issueToken);
}
Comment on lines +34 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스 레벨에 Transactional 어노테이션이 read only 옵션으로 적용되어 있어서 해당 클래스의 모든 public 메서드에서 변경 감지 동작이 이루어지지 않게 됩니다. 따라서 User 엔티티의 refresh token을 update 하는 과정이 정상 수행되지 않을 것 같습니다. 엔티티 데이터의 변경이 필요한 메서드 위에 read only 옵션이 적용되지 않은 Transactional 어노테이션을 추가적으로 적용하여 해결하면 좋을 것 같습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉.. 오류 발견 감사합니다 :)


private String getPlatformId(String token, Platform platform) {
if (platform == APPLE) {
return appleOAuthProvider.getApplePlatformId(token);
}
return kakaoOAuthProvider.getKakaoPlatformId(token);
}

private User getUser(Platform platform, String platformId) {
return userRepository.findUserByPlatformAndPlatformId(platform, platformId)
.orElseThrow(() -> new EntityNotFoundException(ErrorMessage.USER_NOT_FOUND));
}

private void updateRefreshToken(String refreshToken, User user) {
user.updateRefreshToken(refreshToken);
refreshTokenRepository.save(RefreshToken.createRefreshToken(user.getId(), refreshToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum ErrorMessage {
* 400 Bad Request
*/
BAD_REQUEST(HttpStatus.BAD_REQUEST, "e4000", "잘못된 요청입니다."),
INVALID_PLATFORM_TYPE(HttpStatus.BAD_REQUEST, "e4001", "유효하지 않은 플랫폼 타입입니다."),

/**
* 401 Unauthorized
Expand Down Expand Up @@ -40,6 +41,7 @@ public enum ErrorMessage {
* 404 Not Found
*/
ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "e4040", "대상을 찾을 수 없습니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "e4041", "존재하지 않는 회원입니다."),

/**
* 405 Method Not Allowed
Expand Down
1 change: 1 addition & 0 deletions doorip-domain/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation project(path: ':doorip-common')
Expand Down
22 changes: 20 additions & 2 deletions doorip-domain/src/main/java/org/doorip/user/domain/Platform.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
package org.doorip.user.domain;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.doorip.exception.InvalidValueException;
import org.doorip.message.ErrorMessage;

import java.util.Arrays;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public enum Platform {
KAKAO, APPLE
}
APPLE("apple"),
KAKAO("kakao");

private final String stringPlatform;

public static Platform getEnumPlatformFromStringPlatform(String stringPlatform) {
return Arrays.stream(values())
.filter(platform -> platform.stringPlatform.equals(stringPlatform))
.findFirst()
.orElseThrow(() -> new InvalidValueException(ErrorMessage.INVALID_PLATFORM_TYPE));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.doorip.user.domain;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(access = AccessLevel.PRIVATE)
@Getter
@RedisHash(value = "refreshToken", timeToLive = 604800000)
public class RefreshToken {
@Id
private Long id;
private String refreshToken;

public static RefreshToken createRefreshToken(Long userId, String refreshToken) {
return RefreshToken.builder()
.id(userId)
.refreshToken(refreshToken)
.build();
}
}
4 changes: 4 additions & 0 deletions doorip-domain/src/main/java/org/doorip/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ public class User extends BaseTimeEntity {
@Builder.Default
@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Participant> participants = new ArrayList<>();

public void updateRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.doorip.user.repository;

import org.doorip.user.domain.RefreshToken;
import org.springframework.data.repository.CrudRepository;

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.doorip.user.repository;

import org.doorip.user.domain.Platform;
import org.doorip.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findUserByPlatformAndPlatformId(Platform platform, String platformId);
}