Skip to content

Commit

Permalink
✨ 로그인 API (#23)
Browse files Browse the repository at this point in the history
* feat: 일반 로그인 요청 dto 작성

* feat: user repository find-by-username 메서드 추가

* feat: user service read-user-by-username 메서드 구현

* feat: 유저 비밀번호 예외 추가

* feat: user sync helper read-user-if-valid 메서드

* feat: auth user case내 sign in 메서드 추가

* feat: sign in api 추가

* test: sign in test case 추가

* fix: sign in dto 정규표현식 검사 제거
psychology50 authored Mar 27, 2024
1 parent 39f97e9 commit f39cdb7
Showing 8 changed files with 122 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import kr.co.pennyway.api.apis.auth.dto.PhoneVerificationDto;
import kr.co.pennyway.api.apis.auth.dto.SignInReq;
import kr.co.pennyway.api.apis.auth.dto.SignUpReq;
import kr.co.pennyway.api.apis.auth.usecase.AuthUseCase;
import kr.co.pennyway.api.common.response.SuccessResponse;
@@ -60,4 +61,18 @@ public ResponseEntity<?> signUp(@RequestBody @Validated SignUpReq.General reques
.body(SuccessResponse.from("user", Map.of("id", jwts.getKey())))
;
}

@Operation(summary = "일반 로그인")
@PostMapping("/sign-in")
@PreAuthorize("isAnonymous()")
public ResponseEntity<?> signIn(@RequestBody @Validated SignInReq.General request) {
Pair<Long, Jwts> jwts = authUseCase.signIn(request);
ResponseCookie cookie = cookieUtil.createCookie("refreshToken", jwts.getValue().refreshToken(), Duration.ofDays(7).toSeconds());

return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.header(HttpHeaders.AUTHORIZATION, jwts.getValue().accessToken())
.body(SuccessResponse.from("user", Map.of("id", jwts.getKey())))
;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.co.pennyway.api.apis.auth.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SignInReq {
public record General(
@Schema(description = "아이디", example = "pennyway")
@NotBlank(message = "아이디를 입력해주세요")
String username,
@Schema(description = "비밀번호", example = "pennyway1234")
@NotBlank(message = "비밀번호를 입력해주세요")
String password
) {
}
}
Original file line number Diff line number Diff line change
@@ -9,13 +9,17 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Helper
@RequiredArgsConstructor
public class UserSyncHelper {
private final UserService userService;

private final PasswordEncoder bCryptPasswordEncoder;

/**
* 일반 회원가입 시 이미 가입된 회원인지 확인
*
@@ -40,4 +44,25 @@ public Pair<Boolean, String> isSignedUserWhenGeneral(String phone) {

return Pair.of(Boolean.TRUE, user.getUsername());
}

/**
* 로그인 시 유저가 존재하고 비밀번호가 일치하는지 확인
*/
@Transactional(readOnly = true)
public User readUserIfValid(String username, String password) {
User user;

try {
user = userService.readUserByUsername(username);

if (!bCryptPasswordEncoder.matches(password, user.getPassword())) {
throw new UserErrorException(UserErrorCode.NOT_MATCHED_PASSWORD);
}
} catch (UserErrorException e) {
log.warn("request not valid : {} : {}", username, e.getExplainError());
throw new UserErrorException(UserErrorCode.INVALID_USERNAME_OR_PASSWORD);
}

return user;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kr.co.pennyway.api.apis.auth.usecase;

import kr.co.pennyway.api.apis.auth.dto.PhoneVerificationDto;
import kr.co.pennyway.api.apis.auth.dto.SignInReq;
import kr.co.pennyway.api.apis.auth.dto.SignUpReq;
import kr.co.pennyway.api.apis.auth.helper.UserSyncHelper;
import kr.co.pennyway.api.apis.auth.mapper.JwtAuthMapper;
@@ -28,6 +29,7 @@ public class AuthUseCase {
private final PhoneVerificationMapper phoneVerificationMapper;
private final PhoneVerificationService phoneVerificationService;


public PhoneVerificationDto.PushCodeRes sendCode(PhoneVerificationDto.PushCodeReq request) {
return phoneVerificationMapper.sendCode(request, PhoneVerificationType.SIGN_UP);
}
@@ -51,6 +53,13 @@ public Pair<Long, Jwts> signUp(SignUpReq.General request) {
return Pair.of(user.getId(), jwtAuthMapper.createToken(user));
}

@Transactional(readOnly = true)
public Pair<Long, Jwts> signIn(SignInReq.General request) {
User user = userSyncHelper.readUserIfValid(request.username(), request.password());

return Pair.of(user.getId(), jwtAuthMapper.createToken(user));
}

private Pair<Boolean, String> checkOauthUser(String phone) {
try {
return userSyncHelper.isSignedUserWhenGeneral(phone);
Original file line number Diff line number Diff line change
@@ -10,8 +10,10 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.BDDMockito.given;

@ExtendWith(MockitoExtension.class)
@@ -20,10 +22,12 @@ public class UserSyncHelperTest {
private UserSyncHelper userSyncHelper;
@Mock
private UserService userService;
@Mock
private BCryptPasswordEncoder bCryptPasswordEncoder;

@BeforeEach
void setUp() {
userSyncHelper = new UserSyncHelper(userService);
userSyncHelper = new UserSyncHelper(userService, bCryptPasswordEncoder);
}

@DisplayName("일반 회원가입 시, 회원 정보가 없으면 FALSE를 반환한다.")
@@ -65,4 +69,45 @@ void isSignedUserWhenGeneralThrowUserErrorException() {
UserErrorException.class, () -> userSyncHelper.isSignedUserWhenGeneral(phone));
System.out.println(exception.getExplainError());
}

@DisplayName("로그인 시, 유저가 존재하고 비밀번호가 일치하면 User를 반환한다.")
@Test
void readUserIfValidReturnUser() {
// given
User user = User.builder().username("pennyway").password("password").build();
given(userService.readUserByUsername("pennyway")).willReturn(user);
given(bCryptPasswordEncoder.matches("password", user.getPassword())).willReturn(true);

// when
User result = userSyncHelper.readUserIfValid("pennyway", "password");

// then
assertEquals(result, user);
}

@DisplayName("로그인 시, username에 해당하는 유저가 존재하지 않으면 UserErrorException을 발생시킨다.")
@Test
void readUserIfNotFound() {
// given
User user = User.builder().username("pennyway").password("password").build();
given(userService.readUserByUsername("pennyway")).willThrow(
new UserErrorException(UserErrorCode.NOT_FOUND));

// when - then
UserErrorException exception = assertThrows(UserErrorException.class, () -> userSyncHelper.readUserIfValid("pennyway", "password"));
System.out.println(exception.getExplainError());
}

@DisplayName("로그인 시, 비밀번호가 일치하지 않으면 UserErrorException을 발생시킨다.")
@Test
void readUserIfNotMatchedPassword() {
// given
User user = User.builder().username("pennyway").password("password").build();
given(userService.readUserByUsername("pennyway")).willReturn(user);
given(bCryptPasswordEncoder.matches("password", user.getPassword())).willReturn(false);

// when - then
UserErrorException exception = assertThrows(UserErrorException.class, () -> userSyncHelper.readUserIfValid("pennyway", "password"));
System.out.println(exception.getExplainError());
}
}
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ public enum UserErrorCode implements BaseErrorCode {
ALREADY_SIGNUP(StatusCode.BAD_REQUEST, ReasonCode.INVALID_REQUEST, "이미 회원가입한 유저입니다."),

/* 401 UNAUTHORIZED */
NOT_MATCHED_PASSWORD(StatusCode.UNAUTHORIZED, ReasonCode.MISSING_OR_INVALID_AUTHENTICATION_CREDENTIALS, "비밀번호가 일치하지 않습니다."),
INVALID_USERNAME_OR_PASSWORD(StatusCode.UNAUTHORIZED, ReasonCode.MISSING_OR_INVALID_AUTHENTICATION_CREDENTIALS, "유효하지 않은 아이디 또는 비밀번호입니다."),

/* 403 FORBIDDEN */
Original file line number Diff line number Diff line change
@@ -7,4 +7,6 @@

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByPhone(String phone);

Optional<User> findByUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -28,6 +28,11 @@ public User readUserByPhone(String phone) {
return userRepository.findByPhone(phone).orElseThrow(() -> new UserErrorException(UserErrorCode.NOT_FOUND));
}

@Transactional(readOnly = true)
public User readUserByUsername(String username) {
return userRepository.findByUsername(username).orElseThrow(() -> new UserErrorException(UserErrorCode.NOT_FOUND));
}

@Transactional(readOnly = true)
public boolean isExistUser(Long id) {
return userRepository.existsById(id);

0 comments on commit f39cdb7

Please sign in to comment.