Skip to content

Commit

Permalink
refactor: 로그인된 유저 조회가 DB에서 유저를 조회하도록 수정한다
Browse files Browse the repository at this point in the history
  • Loading branch information
devxb committed Mar 8, 2024
1 parent 8ad65e1 commit caf8e7a
Show file tree
Hide file tree
Showing 16 changed files with 147 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import me.nalab.core.data.user.UserOAuthInfoEntity;
import me.nalab.core.idgenerator.idcore.IdGenerator;
import me.nalab.user.domain.user.Provider;
import me.nalab.user.domain.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -22,7 +23,7 @@ public class UserInitializer {
private IdGenerator idGenerator;

@Transactional
public void saveUserWithOAuth(Provider provider, String name, String email, Instant date) {
public Long saveUserWithOAuth(Provider provider, String name, String email, Instant date) {
var userEntity = UserEntity.builder()
.id(idGenerator.generate())
.nickname(name)
Expand All @@ -42,6 +43,7 @@ public void saveUserWithOAuth(Provider provider, String name, String email, Inst

entityManager.persist(userEntity);
entityManager.persist(userOauthInfoEntity);
return userEntity.getId();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

public class UserAcceptanceValidator {

public static void assertIsLogined(ResultActions resultActions, Long targetId, String nickname) throws Exception {
public static void assertIsLogined(ResultActions resultActions, Long targetId, String nickname, String email) throws Exception {
resultActions.andExpectAll(
status().isOk(),
jsonPath("$.target_id").value(targetId),
jsonPath("$.nickname").value(nickname)
jsonPath("$.nickname").value(nickname),
jsonPath("$.email").value(email)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.time.Instant;
import java.util.Set;

import me.nalab.luffy.api.acceptance.test.UserInitializer;
import me.nalab.user.domain.user.Provider;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -40,22 +42,27 @@ class LoginedTargetFindAcceptanceTest extends UserAcceptanceTestSupporter {
@Autowired
private JwtUtils jwtUtils;

@Autowired
private UserInitializer userInitializer;

@Test
@DisplayName("로그인된 유저 정보 조회 성공 테스트")
void GET_LOGINED_USER_SUCCESS() throws Exception {
// given
String nickname = "devxb";
String email = "email";
Long userId = userInitializer.saveUserWithOAuth(Provider.DEFAULT, nickname, "email", Instant.now());
Long targetId = targetInitializer.saveTargetAndGetId(nickname, Instant.now());
String token = jwtUtils.createAccessToken(Set.of(new Payload(Payload.Key.NICKNAME, nickname),
new Payload(Payload.Key.USER_ID, 12345 + ""), new Payload(Payload.Key.TARGET_ID, targetId + "")));
new Payload(Payload.Key.USER_ID, String.valueOf(userId)), new Payload(Payload.Key.TARGET_ID, String.valueOf(targetId))));
applicationEventPublisher.publishEvent(
MockUserRegisterEvent.builder().expectedToken("bearer " + token).expectedId(targetId).build());

// when
ResultActions resultActions = getLoginedUser("bearer " + token);

// then
assertIsLogined(resultActions, targetId, nickname);
assertIsLogined(resultActions, targetId, nickname, email);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import me.nalab.auth.application.common.dto.Payload;
import me.nalab.auth.application.common.utils.JwtUtils;
import me.nalab.auth.application.port.in.web.TargetIdGetPort;
import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.common.dto.TokenInfo;
import me.nalab.user.application.port.out.persistence.LoginedUserGetByTokenPort;

@Service
Expand All @@ -19,14 +19,14 @@ public class JwtLoginedDecryptService implements LoginedUserGetByTokenPort, Targ
private final JwtUtils jwtUtils;

@Override
public LoginedInfo decryptToken(String encryptedToken) {
public TokenInfo decryptToken(String encryptedToken) {
Assert.isTrue(encryptedToken != null && !encryptedToken.isBlank(),
"encryptedToken 으로 blank나 null 값이 들어올 수 없습니다.");
DecodedJWT decodedJWT = jwtUtils.verify(encryptedToken);
String nickName = decodedJWT.getClaim(Payload.Key.NICKNAME.name()).asString();
Long userId = Long.valueOf(decodedJWT.getClaim(Payload.Key.USER_ID.name()).asString());
Long targetId = Long.valueOf(decodedJWT.getClaim(Payload.Key.TARGET_ID.name()).asString());
return new LoginedInfo(nickName, targetId, userId);
return new TokenInfo(nickName, targetId, userId);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import me.nalab.auth.application.common.dto.Payload;
import me.nalab.auth.application.common.property.JwtProperties;
import me.nalab.auth.application.common.utils.JwtUtils;
import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.common.dto.TokenInfo;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {JwtLoginedDecryptService.class, JwtUtils.class, JwtProperties.class})
Expand All @@ -42,7 +42,7 @@ void JWT_DECRYPT_SUCCESS() {
new Payload(Payload.Key.TARGET_ID, targetId + "")));

// when
LoginedInfo response = jwtLoginedDecryptService.decryptToken(requestToken);
TokenInfo response = jwtLoginedDecryptService.decryptToken(requestToken);

// then
assertDecryptedInfo(response, nickName, userId, targetId);
Expand All @@ -59,7 +59,7 @@ void THROW_EXCEPTION_WHEN_TOKEN_IS_NULL_OR_EMPTY(String token) {
assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
}

private void assertDecryptedInfo(LoginedInfo response, String expectedName, Long expectedUserId,
private void assertDecryptedInfo(TokenInfo response, String expectedName, Long expectedUserId,
Long expectedTargetId) {
Assertions.assertEquals(response.getNickName(), expectedName);
Assertions.assertEquals(response.getUserId(), expectedUserId);
Expand Down
29 changes: 29 additions & 0 deletions support/e2e/v1_13_get_logined_target.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
POST http://nalab-server:8080/v1/oauth/default # Default provider를 통해서 로그인 진행
{
"nickname": "devxb",
"email": "[email protected]"
}

HTTP 200
[Asserts]
header "Content-type" == "application/json"

jsonpath "$.access_token" exists
jsonpath "$.token_type" exists

[Captures]
token_type: jsonpath "$.token_type"
auth_token: jsonpath "$.access_token"

#######

GET http://nalab-server:8080/v1/users/logins # Token에 해당하는 유저 조회
Authorization: {{ token_type }} {{ auth_token }}

HTTP 200
[Asserts]
header "Content-type" == "application/json"

jsonpath "$.target_id" exists
jsonpath "$.nickname" == "devxb"
jsonpath "$.email" == "[email protected]"
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package me.nalab.user.application.common.dto;

import lombok.Data;

@Data
public class LoginedInfo {

private final String nickName;
private final Long targetId;
private final Long userId;

import me.nalab.user.domain.user.User;

public record LoginedInfo(
Long id,
Long targetId,
String nickname,
String email
) {

public static LoginedInfo from(Long targetId, User user) {
return new LoginedInfo(user.getId(), targetId, user.getNickname(), user.getEmail());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.nalab.user.application.common.dto;

import lombok.Data;

@Data
public class TokenInfo {

private final String nickName;
private final Long targetId;
private final Long userId;

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public interface LoginedUserGetByTokenUseCase {
* @param encryptedToken 암호화된 토큰
* @return 복호화된 정보
*/
LoginedInfo decryptToken(String encryptedToken);
LoginedInfo getLoginedInfoByToken(String encryptedToken);

}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package me.nalab.user.application.port.out.persistence;

import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.common.dto.TokenInfo;

/**
* token을 받아 decrypt된 유저의 정보를 반환하는 유즈케이스
*/
public interface LoginedUserGetByTokenPort {

/**
* 암호화된 유저의 토큰을 받아, 복호화된 유저의 정보를 반환합니다.
* 암호화된 유저의 토큰을 받아, 복호화된 토큰의 정보를 반환합니다.
*
* @param encryptedToken 암호화된 토큰
* @return 복호화된 정보
*/
LoginedInfo decryptToken(String encryptedToken);
TokenInfo decryptToken(String encryptedToken);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.nalab.user.application.port.out.persistence;

import me.nalab.user.domain.user.User;

public interface UserGetPort {

User getById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@

import java.util.Objects;

import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.port.out.persistence.UserGetPort;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.exception.InvalidTokenException;
import me.nalab.user.application.port.in.LoginedUserGetByTokenUseCase;
import me.nalab.user.application.port.out.persistence.LoginedUserGetByTokenPort;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class LoginedUserGetByTokenService implements LoginedUserGetByTokenUseCase {

private final LoginedUserGetByTokenPort loginedUserGetByTokenPort;
private final UserGetPort userGetPort;

@Override
public LoginedInfo decryptToken(String encryptedToken) {
@Transactional(readOnly = true)
public LoginedInfo getLoginedInfoByToken(String encryptedToken) {
Objects.requireNonNull(encryptedToken, "encryptedToken은 null이 되면 안됩니다.");
String[] split = encryptedToken.split(" ");
throwIfInvalidToken(split);
return loginedUserGetByTokenPort.decryptToken(split[1]);
var tokenInfo = loginedUserGetByTokenPort.decryptToken(split[1]);
var user = userGetPort.getById(tokenInfo.getUserId());
return LoginedInfo.from(tokenInfo.getTargetId(), user);
}

private void throwIfInvalidToken(String[] split) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package me.nalab.user.application.service;

import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.port.out.persistence.UserGetPort;
import me.nalab.user.domain.user.User;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.common.dto.TokenInfo;
import me.nalab.user.application.exception.InvalidTokenException;
import me.nalab.user.application.port.in.LoginedUserGetByTokenUseCase;
import me.nalab.user.application.port.out.persistence.LoginedUserGetByTokenPort;
Expand All @@ -21,23 +25,34 @@
@ContextConfiguration(classes = {LoginedUserGetByTokenService.class})
class LoginedUserGetByTokenServiceTest {

private static final User DEFAULT_USER = User.builder()
.id(54321L)
.email("email")
.nickname("hello")
.build();

@Autowired
private LoginedUserGetByTokenUseCase loginedUserGetByTokenUseCase;

@MockBean
private LoginedUserGetByTokenPort loginedUserGetByTokenPort;

@MockBean
private UserGetPort userGetPort;

@Test
@DisplayName("토큰을 이용해 로그인된 유저의 정보를 조회 성공 테스트")
void GET_LOGINED_INFO_BY_TOKEN_SUCCESS() {
// given
LoginedInfo expected = new LoginedInfo("hello", 12345L, 54321L);
LoginedInfo expected = new LoginedInfo(DEFAULT_USER.getId(), 12345L, DEFAULT_USER.getNickname(), DEFAULT_USER.getEmail());
TokenInfo tokenInfo = new TokenInfo(DEFAULT_USER.getNickname(), 12345L, DEFAULT_USER.getId());
String token = "hello token";

Mockito.when(loginedUserGetByTokenPort.decryptToken(token.split(" ")[1])).thenReturn(expected);
Mockito.when(loginedUserGetByTokenPort.decryptToken(token.split(" ")[1])).thenReturn(tokenInfo);
Mockito.when(userGetPort.getById(54321L)).thenReturn(DEFAULT_USER);

// when
LoginedInfo result = loginedUserGetByTokenUseCase.decryptToken(token);
LoginedInfo result = loginedUserGetByTokenUseCase.getLoginedInfoByToken(token);

// then
Assertions.assertThat(result).isEqualTo(expected);
Expand All @@ -47,7 +62,7 @@ void GET_LOGINED_INFO_BY_TOKEN_SUCCESS() {
@NullSource
void NULL_PARAMETER_TEST(String token) {
// when
Throwable result = Assertions.catchThrowable(() -> loginedUserGetByTokenUseCase.decryptToken(token));
Throwable result = Assertions.catchThrowable(() -> loginedUserGetByTokenUseCase.getLoginedInfoByToken(token));

// then
Assertions.assertThat(result).isInstanceOf(NullPointerException.class);
Expand All @@ -60,7 +75,7 @@ void DECRYPT_INVALID_TOKEN() {
String token = "invalid";

// when
Throwable result = Assertions.catchThrowable(() -> loginedUserGetByTokenUseCase.decryptToken(token));
Throwable result = Assertions.catchThrowable(() -> loginedUserGetByTokenUseCase.getLoginedInfoByToken(token));

// then
Assertions.assertThat(result).isInstanceOf(InvalidTokenException.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.nalab.user.jpa.adaptor.create;

import lombok.RequiredArgsConstructor;
import me.nalab.user.application.port.out.persistence.UserGetPort;
import me.nalab.user.domain.user.User;
import me.nalab.user.jpa.adaptor.create.common.mapper.UserObjectMapper;
import me.nalab.user.jpa.adaptor.create.repository.UserJpaRepository;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class UserGetAdaptor implements UserGetPort {

private final UserJpaRepository userJpaRepository;

@Override
public User getById(Long id) {
var user = userJpaRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException(String.format("Cannot find exists user \"%d\"", id)));

return UserObjectMapper.toDomain(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.port.in.LoginedUserGetByTokenUseCase;
import me.nalab.user.web.adaptor.logined.response.LoginedInfoResponse;

Expand All @@ -20,9 +19,7 @@ public class LoginedUserGetController {

@GetMapping("/users/logined")
public LoginedInfoResponse getLoginedUserByToken(@RequestHeader(HttpHeaders.AUTHORIZATION) String token) {
LoginedInfo loginedInfo = loginedUserGetByTokenUseCase.decryptToken(token);

return new LoginedInfoResponse(String.valueOf(loginedInfo.getTargetId()), loginedInfo.getNickName());
return LoginedInfoResponse.of(loginedUserGetByTokenUseCase.getLoginedInfoByToken(token));
}

}
Loading

0 comments on commit caf8e7a

Please sign in to comment.