From b9c7a8fa1c153af606df8a5f36b431d9aa7f44be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Mon, 1 Jan 2024 18:11:45 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/auth/JwtProvider.java | 36 +++++-- .../koin/domain/auth/StudentAuth.java | 14 +++ .../auth/resolver/UserArgumentResolver.java | 44 +++++++++ .../user/controller/StudentController.java | 23 +++++ .../user/exception/UserNotFoundException.java | 11 +++ .../koin/domain/user/model/Student.java | 26 ++++- .../koreatech/koin/dto/StudentResponse.java | 35 +++++++ .../koin/global/config/WebConfig.java | 20 ++++ .../exception/GlobalExceptionHandler.java | 7 ++ .../koin/repository/StudentRepository.java | 14 +++ .../koin/service/StudentService.java | 21 +++++ .../koin/acceptance/AuthApiTest.java | 76 +++++++++++++++ .../koin/acceptance/UserApiTest.java | 94 +++++++++++-------- 13 files changed, 374 insertions(+), 47 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/auth/StudentAuth.java create mode 100644 src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java create mode 100644 src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java create mode 100644 src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/dto/StudentResponse.java create mode 100644 src/main/java/in/koreatech/koin/global/config/WebConfig.java create mode 100644 src/main/java/in/koreatech/koin/repository/StudentRepository.java create mode 100644 src/main/java/in/koreatech/koin/service/StudentService.java create mode 100644 src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java diff --git a/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java b/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java index e688cf60c7..64778c668a 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java +++ b/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java @@ -1,22 +1,24 @@ package in.koreatech.koin.domain.auth; +import in.koreatech.koin.domain.user.model.User; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; import java.security.Key; import java.time.Instant; import java.util.Base64; import java.util.Date; - import javax.crypto.SecretKey; - +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import in.koreatech.koin.domain.user.model.User; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; - @Component +@RequiredArgsConstructor public class JwtProvider { + private static final String BEARER_PREFIX = "BEARER "; + @Value("${jwt.secret-key}") private String secretKey; @@ -40,8 +42,30 @@ public String createToken(User user) { .compact(); } + public Long getUserId(String requestToken) { + if (requestToken == null || !requestToken.toUpperCase().startsWith(BEARER_PREFIX)) { + throw new IllegalArgumentException("잘못된 인증 정보입니다."); + } + String token = requestToken.substring(BEARER_PREFIX.length()); + + try { + String userId = Jwts.parser() + .verifyWith(getSecretKey()) + .build() + .parseSignedClaims(token) + .getPayload() + .get("id") + .toString(); + return Long.parseLong(userId); + + } catch (JwtException e) { + throw new IllegalArgumentException("잘못된 인증 정보입니다."); + } + } + private SecretKey getSecretKey() { String encoded = Base64.getEncoder().encodeToString(secretKey.getBytes()); return Keys.hmacShaKeyFor(encoded.getBytes()); } + } diff --git a/src/main/java/in/koreatech/koin/domain/auth/StudentAuth.java b/src/main/java/in/koreatech/koin/domain/auth/StudentAuth.java new file mode 100644 index 0000000000..2f49fb096f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/auth/StudentAuth.java @@ -0,0 +1,14 @@ +package in.koreatech.koin.domain.auth; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target({PARAMETER, FIELD}) +@Retention(RUNTIME) +public @interface StudentAuth { + +} diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java new file mode 100644 index 0000000000..61fd79f4f1 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java @@ -0,0 +1,44 @@ +package in.koreatech.koin.domain.auth.resolver; + +import in.koreatech.koin.domain.auth.JwtProvider; +import in.koreatech.koin.domain.auth.StudentAuth; +import in.koreatech.koin.repository.StudentRepository; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +@RequiredArgsConstructor +public class UserArgumentResolver implements HandlerMethodArgumentResolver { + + private final JwtProvider jwtProvider; + private final StudentRepository studentRepository; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(StudentAuth.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); + if (nativeRequest != null) { + String request = nativeRequest.getHeader("Authorization"); + + if (request != null) { + Long userId = jwtProvider.getUserId(request); + return studentRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("올바르지 않은 인증정보입니다.")); + } + } + + throw new IllegalArgumentException("올바르지 않은 인증정보입니다."); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java b/src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java new file mode 100644 index 0000000000..4e65e68595 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java @@ -0,0 +1,23 @@ +package in.koreatech.koin.domain.user.controller; + +import in.koreatech.koin.domain.auth.StudentAuth; +import in.koreatech.koin.domain.user.model.Student; +import in.koreatech.koin.dto.StudentResponse; +import in.koreatech.koin.service.StudentService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class StudentController { + + private final StudentService studentService; + + @GetMapping("/user/student/me") + public ResponseEntity getStudent(@StudentAuth Student student) { + StudentResponse studentResponse = studentService.getStudent(student); + return ResponseEntity.ok().body(studentResponse); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java new file mode 100644 index 0000000000..8222f4c888 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java @@ -0,0 +1,11 @@ +package in.koreatech.koin.domain.user.exception; + +public class UserNotFoundException extends RuntimeException { + + public UserNotFoundException() { + } + + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/user/model/Student.java b/src/main/java/in/koreatech/koin/domain/user/model/Student.java index eb0900893f..179add02e4 100644 --- a/src/main/java/in/koreatech/koin/domain/user/model/Student.java +++ b/src/main/java/in/koreatech/koin/domain/user/model/Student.java @@ -5,17 +5,25 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Entity @Table(name = "students") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Student { @Id - private Long userId; + @Column(name = "user_id") + private Long id; @Size(max = 255) @Column(name = "anonymous_nickname") @@ -35,4 +43,20 @@ public class Student { @Column(name = "is_graduated") private Boolean isGraduated; + + @OneToOne + @MapsId + @JoinColumn(name = "user_id") + private User user; + + @Builder + public Student(String anonymousNickname, String studentNumber, String department, UserIdentity userIdentity, + Boolean isGraduated, User user) { + this.anonymousNickname = anonymousNickname; + this.studentNumber = studentNumber; + this.department = department; + this.userIdentity = userIdentity; + this.isGraduated = isGraduated; + this.user = user; + } } diff --git a/src/main/java/in/koreatech/koin/dto/StudentResponse.java b/src/main/java/in/koreatech/koin/dto/StudentResponse.java new file mode 100644 index 0000000000..e7f656ff2c --- /dev/null +++ b/src/main/java/in/koreatech/koin/dto/StudentResponse.java @@ -0,0 +1,35 @@ +package in.koreatech.koin.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import in.koreatech.koin.domain.user.model.Student; +import in.koreatech.koin.domain.user.model.User; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@JsonNaming(value = SnakeCaseStrategy.class) +public record StudentResponse( + String anonymousNickname, + String email, + String gender, + String major, + String name, + String nickname, + String phoneNumber, + String studentNumber +) { + + public static StudentResponse from(Student student) { + User user = student.getUser(); + return new StudentResponse( + student.getAnonymousNickname(), + user.getEmail(), + user.getGender().name(), + student.getDepartment(), + user.getName(), + user.getNickname(), + user.getPhoneNumber(), + student.getStudentNumber() + ); + } +} diff --git a/src/main/java/in/koreatech/koin/global/config/WebConfig.java b/src/main/java/in/koreatech/koin/global/config/WebConfig.java new file mode 100644 index 0000000000..5a89e06adf --- /dev/null +++ b/src/main/java/in/koreatech/koin/global/config/WebConfig.java @@ -0,0 +1,20 @@ +package in.koreatech.koin.global.config; + +import in.koreatech.koin.domain.auth.resolver.UserArgumentResolver; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final UserArgumentResolver userArgumentResolver; + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(userArgumentResolver); + } +} diff --git a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java index c648df477e..d131c63104 100644 --- a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package in.koreatech.koin.global.exception; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -22,4 +23,10 @@ public ResponseEntity handleIllegalArgumentException(IllegalArgumentExce log.warn(e.getMessage()); return ResponseEntity.badRequest().body(e.getMessage()); } + + @ExceptionHandler + public ResponseEntity handleUserNotFoundException(UserNotFoundException e) { + log.warn(e.getMessage()); + return ResponseEntity.badRequest().body("사용자를 찾을 수 없습니다."); + } } diff --git a/src/main/java/in/koreatech/koin/repository/StudentRepository.java b/src/main/java/in/koreatech/koin/repository/StudentRepository.java new file mode 100644 index 0000000000..8ff9340750 --- /dev/null +++ b/src/main/java/in/koreatech/koin/repository/StudentRepository.java @@ -0,0 +1,14 @@ +package in.koreatech.koin.repository; + +import in.koreatech.koin.domain.user.model.Student; +import java.util.Optional; +import org.springframework.data.repository.Repository; + +public interface StudentRepository extends Repository { + + Student save(Student student); + + Optional findById(Long id); + + Boolean existsById(Long id); +} diff --git a/src/main/java/in/koreatech/koin/service/StudentService.java b/src/main/java/in/koreatech/koin/service/StudentService.java new file mode 100644 index 0000000000..98b2b50b93 --- /dev/null +++ b/src/main/java/in/koreatech/koin/service/StudentService.java @@ -0,0 +1,21 @@ +package in.koreatech.koin.service; + +import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import in.koreatech.koin.domain.user.model.Student; +import in.koreatech.koin.dto.StudentResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class StudentService { + + public StudentResponse getStudent(Student student) { + if (student == null || student.getId() == null) { + throw new UserNotFoundException("학생 정보가 비어있습니다."); + } + return StudentResponse.from(student); + } +} diff --git a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java new file mode 100644 index 0000000000..c3f4073fb7 --- /dev/null +++ b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java @@ -0,0 +1,76 @@ +package in.koreatech.koin.acceptance; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import in.koreatech.koin.AcceptanceTest; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.domain.user.model.UserToken; +import in.koreatech.koin.domain.user.model.UserType; +import in.koreatech.koin.domain.user.repository.UserRepository; +import in.koreatech.koin.domain.user.repository.UserTokenRepository; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; + +class AuthApiTest extends AcceptanceTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserTokenRepository tokenRepository; + + @Test + @DisplayName("사용자가 로그인을 수행한다") + void userLoginSuccess() { + User user = User.builder() + .password("1234") + .nickname("주노") + .name("최준호") + .phoneNumber("010-1234-5678") + .userType(UserType.STUDENT) + .email("test@koreatech.ac.kr") + .isAuthed(true) + .isDeleted(false) + .build(); + + userRepository.save(user); + + ExtractableResponse response = RestAssured + .given() + .log().all() + .body(""" + { + "email": "test@koreatech.ac.kr", + "password": "1234" + } + """) + .contentType(ContentType.JSON) + .when() + .log().all() + .post("/user/login") + .then() + .log().all() + .statusCode(HttpStatus.CREATED.value()) + .extract(); + + User userResult = userRepository.findById(user.getId()).get(); + UserToken token = tokenRepository.findById(userResult.getId()).get(); + + assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getString("token")).isNotNull(); + softly.assertThat(response.jsonPath().getString("refresh_token")).isNotNull(); + softly.assertThat(response.jsonPath().getString("refresh_token")) + .isEqualTo(token.getRefreshToken()); + softly.assertThat(response.jsonPath().getString("user_type")).isEqualTo("STUDENT"); + softly.assertThat(userResult.getLastLoggedAt()).isNotNull(); + } + ); + } +} diff --git a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java index a47f424c3e..063616dd10 100644 --- a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java @@ -2,75 +2,89 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; - import in.koreatech.koin.AcceptanceTest; +import in.koreatech.koin.domain.auth.JwtProvider; +import in.koreatech.koin.domain.user.model.Student; import in.koreatech.koin.domain.user.model.User; -import in.koreatech.koin.domain.user.model.UserToken; +import in.koreatech.koin.domain.user.model.UserGender; +import in.koreatech.koin.domain.user.model.UserIdentity; import in.koreatech.koin.domain.user.model.UserType; -import in.koreatech.koin.domain.user.repository.UserRepository; -import in.koreatech.koin.domain.user.repository.UserTokenRepository; +import in.koreatech.koin.repository.StudentRepository; import io.restassured.RestAssured; -import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; class UserApiTest extends AcceptanceTest { @Autowired - private UserRepository userRepository; + private StudentRepository studentRepository; @Autowired - private UserTokenRepository tokenRepository; + private JwtProvider jwtProvider; @Test - @DisplayName("사용자가 로그인을 수행한다") - void userLoginSuccess() { - User user = User.builder() - .password("1234") - .nickname("주노") - .name("최준호") - .phoneNumber("010-1234-5678") - .userType(UserType.STUDENT) - .email("test@koreatech.ac.kr") - .isAuthed(true) - .isDeleted(false) + @DisplayName("올바른 학생계정인지 확인한다") + void studentCheckMe() { + Student student = Student.builder() + .studentNumber("2019136135") + .anonymousNickname("익명") + .department("컴퓨터공학부") + .userIdentity(UserIdentity.UNDERGRADUATE) + .isGraduated(false) + .user( + User.builder() + .password("1234") + .nickname("주노") + .name("최준호") + .phoneNumber("010-1234-5678") + .userType(UserType.STUDENT) + .gender(UserGender.MAN) + .email("test@koreatech.ac.kr") + .isAuthed(true) + .isDeleted(false) + .build() + ) .build(); - userRepository.save(user); + studentRepository.save(student); + String token = jwtProvider.createToken(student.getUser()); ExtractableResponse response = RestAssured .given() .log().all() - .body(""" - { - "email": "test@koreatech.ac.kr", - "password": "1234" - } - """) - .contentType(ContentType.JSON) + .header("Authorization", "BEARER " + token) .when() .log().all() - .post("/user/login") + .get("/user/student/me") .then() .log().all() - .statusCode(HttpStatus.CREATED.value()) + .statusCode(HttpStatus.OK.value()) .extract(); - User userResult = userRepository.findById(user.getId()).get(); - UserToken token = tokenRepository.findById(userResult.getId()).get(); + User user = student.getUser(); assertSoftly( softly -> { - softly.assertThat(response.jsonPath().getString("token")).isNotNull(); - softly.assertThat(response.jsonPath().getString("refresh_token")).isNotNull(); - softly.assertThat(response.jsonPath().getString("refresh_token")) - .isEqualTo(token.getRefreshToken()); - softly.assertThat(response.jsonPath().getString("user_type")).isEqualTo("STUDENT"); - softly.assertThat(userResult.getLastLoggedAt()).isNotNull(); + softly.assertThat(response.body().jsonPath().getString("anonymous_nickname")) + .isEqualTo(student.getAnonymousNickname()); + softly.assertThat(response.body().jsonPath().getString("email")) + .isEqualTo(user.getEmail()); + softly.assertThat(response.body().jsonPath().getString("gender")) + .isEqualTo(user.getGender().name()); + softly.assertThat(response.body().jsonPath().getString("major")) + .isEqualTo(student.getDepartment()); + softly.assertThat(response.body().jsonPath().getString("name")) + .isEqualTo(user.getName()); + softly.assertThat(response.body().jsonPath().getString("nickname")) + .isEqualTo(user.getNickname()); + softly.assertThat(response.body().jsonPath().getString("phone_number")) + .isEqualTo(user.getPhoneNumber()); + softly.assertThat(response.body().jsonPath().getString("student_number")) + .isEqualTo(student.getStudentNumber()); } ); } From 2c8f10814c41101641ba38d1b69363337cf1a5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Mon, 1 Jan 2024 18:34:48 +0900 Subject: [PATCH 02/11] =?UTF-8?q?refactor:=20ArgumentResolver=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...erArgumentResolver.java => StudentArgumentResolver.java} | 2 +- .../java/in/koreatech/koin/global/config/WebConfig.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/in/koreatech/koin/domain/auth/resolver/{UserArgumentResolver.java => StudentArgumentResolver.java} (95%) diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java similarity index 95% rename from src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java rename to src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java index 61fd79f4f1..3d54a30575 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java @@ -14,7 +14,7 @@ @Component @RequiredArgsConstructor -public class UserArgumentResolver implements HandlerMethodArgumentResolver { +public class StudentArgumentResolver implements HandlerMethodArgumentResolver { private final JwtProvider jwtProvider; private final StudentRepository studentRepository; diff --git a/src/main/java/in/koreatech/koin/global/config/WebConfig.java b/src/main/java/in/koreatech/koin/global/config/WebConfig.java index 5a89e06adf..fc3c6f95e6 100644 --- a/src/main/java/in/koreatech/koin/global/config/WebConfig.java +++ b/src/main/java/in/koreatech/koin/global/config/WebConfig.java @@ -1,6 +1,6 @@ package in.koreatech.koin.global.config; -import in.koreatech.koin.domain.auth.resolver.UserArgumentResolver; +import in.koreatech.koin.domain.auth.resolver.StudentArgumentResolver; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; @@ -11,10 +11,10 @@ @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { - private final UserArgumentResolver userArgumentResolver; + private final StudentArgumentResolver studentArgumentResolver; @Override public void addArgumentResolvers(final List resolvers) { - resolvers.add(userArgumentResolver); + resolvers.add(studentArgumentResolver); } } From f29b6d4e16c1793ce21ceeab8e6342566796dbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Mon, 1 Jan 2024 19:16:38 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20CustomException=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/auth/JwtProvider.java | 8 +- .../domain/auth/exception/AuthException.java | 18 +++++ .../resolver/StudentArgumentResolver.java | 6 +- .../user/exception/UserNotFoundException.java | 7 ++ .../koin/domain/user/service/UserService.java | 5 +- .../koin/global/exception/ErrorResponse.java | 23 ++++++ .../exception/GlobalExceptionHandler.java | 23 ++++-- .../koin/repository/StudentRepository.java | 2 + .../koin/acceptance/UserApiTest.java | 76 +++++++++++++++++++ 9 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java create mode 100644 src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java b/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java index 64778c668a..cef142e293 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java +++ b/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java @@ -1,5 +1,7 @@ package in.koreatech.koin.domain.auth; +import in.koreatech.koin.domain.auth.exception.AuthException; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; import in.koreatech.koin.domain.user.model.User; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; @@ -27,7 +29,7 @@ public class JwtProvider { public String createToken(User user) { if (user == null) { - throw new IllegalArgumentException("존재하지 않는 사용자입니다."); + throw new UserNotFoundException("존재하지 않는 사용자입니다. user: " + user); } Key key = getSecretKey(); @@ -44,7 +46,7 @@ public String createToken(User user) { public Long getUserId(String requestToken) { if (requestToken == null || !requestToken.toUpperCase().startsWith(BEARER_PREFIX)) { - throw new IllegalArgumentException("잘못된 인증 정보입니다."); + throw AuthException.witDetail("token: " + requestToken); } String token = requestToken.substring(BEARER_PREFIX.length()); @@ -59,7 +61,7 @@ public Long getUserId(String requestToken) { return Long.parseLong(userId); } catch (JwtException e) { - throw new IllegalArgumentException("잘못된 인증 정보입니다."); + throw AuthException.witDetail("token: " + token); } } diff --git a/src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java b/src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java new file mode 100644 index 0000000000..4257f07e69 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java @@ -0,0 +1,18 @@ +package in.koreatech.koin.domain.auth.exception; + +public class AuthException extends RuntimeException { + + private static final String DEFAULT_MESSAGE = "올바르지 않은 인증정보입니다."; + + public AuthException() { + } + + public AuthException(String message) { + super(message); + } + + public static AuthException witDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new AuthException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java index 3d54a30575..9c6de830f0 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java @@ -2,6 +2,8 @@ import in.koreatech.koin.domain.auth.JwtProvider; import in.koreatech.koin.domain.auth.StudentAuth; +import in.koreatech.koin.domain.auth.exception.AuthException; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; import in.koreatech.koin.repository.StudentRepository; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -35,10 +37,10 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m if (request != null) { Long userId = jwtProvider.getUserId(request); return studentRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("올바르지 않은 인증정보입니다.")); + .orElseThrow(() -> UserNotFoundException.witDetail("request: " + request)); } } - throw new IllegalArgumentException("올바르지 않은 인증정보입니다."); + throw new AuthException("request: " + nativeRequest); } } diff --git a/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java index 8222f4c888..44002810a9 100644 --- a/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java +++ b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java @@ -2,10 +2,17 @@ public class UserNotFoundException extends RuntimeException { + private static final String DEFAULT_MESSAGE = "존재하지 않는 사용자입니다."; + public UserNotFoundException() { } public UserNotFoundException(String message) { super(message); } + + public static UserNotFoundException witDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new UserNotFoundException(message); + } } diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index bbe2053d21..ae6b4ee694 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.user.service; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; import java.time.LocalDateTime; import java.util.UUID; @@ -27,10 +28,10 @@ public class UserService { @Transactional public UserLoginResponse login(UserLoginRequest request) { User user = userRepository.findByEmail(request.email()) - .orElseThrow(() -> new IllegalArgumentException("잘못된 로그인 정보입니다.")); + .orElseThrow(() -> UserNotFoundException.witDetail("request: " + request)); if (!user.isSamePassword(request.password())) { - throw new IllegalArgumentException("잘못된 로그인 정보입니다."); + throw new IllegalArgumentException("잘못된 로그인 정보입니다. request: " + request); } String accessToken = jwtProvider.createToken(user); diff --git a/src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java b/src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java new file mode 100644 index 0000000000..ae066867da --- /dev/null +++ b/src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java @@ -0,0 +1,23 @@ +package in.koreatech.koin.global.exception; + +import lombok.Getter; + +@Getter +public class ErrorResponse { + + private final int code; + private final String message; + + private ErrorResponse(int code, String message) { + this.code = code; + this.message = message; + } + + public static ErrorResponse of(int code, String message) { + return new ErrorResponse(code, message); + } + + public static ErrorResponse from(String message) { + return new ErrorResponse(0, message); + } +} diff --git a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java index d131c63104..05875f41ae 100644 --- a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java @@ -1,32 +1,41 @@ package in.koreatech.koin.global.exception; +import in.koreatech.koin.domain.auth.exception.AuthException; import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import lombok.extern.slf4j.Slf4j; - @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.warn(e.getMessage()); - return ResponseEntity.badRequest().body(e.getMessage()); + return ResponseEntity.badRequest().body(ErrorResponse.from(e.getMessage())); } @ExceptionHandler public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { log.warn(e.getMessage()); - return ResponseEntity.badRequest().body(e.getMessage()); + return ResponseEntity.badRequest().body("잘못된 요청입니다."); + } + + @ExceptionHandler + public ResponseEntity handleUserNotFoundException(UserNotFoundException e) { + log.warn(e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ErrorResponse.from("사용자를 찾을 수 없습니다.")); } @ExceptionHandler - public ResponseEntity handleUserNotFoundException(UserNotFoundException e) { + public ResponseEntity handleAuthException(AuthException e) { log.warn(e.getMessage()); - return ResponseEntity.badRequest().body("사용자를 찾을 수 없습니다."); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ErrorResponse.from("잘못된 인증정보입니다.")); } } diff --git a/src/main/java/in/koreatech/koin/repository/StudentRepository.java b/src/main/java/in/koreatech/koin/repository/StudentRepository.java index 8ff9340750..0dfe3b23c5 100644 --- a/src/main/java/in/koreatech/koin/repository/StudentRepository.java +++ b/src/main/java/in/koreatech/koin/repository/StudentRepository.java @@ -11,4 +11,6 @@ public interface StudentRepository extends Repository { Optional findById(Long id); Boolean existsById(Long id); + + void delete(Student student); } diff --git a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java index 063616dd10..ccafa72d87 100644 --- a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java @@ -9,6 +9,7 @@ import in.koreatech.koin.domain.user.model.UserGender; import in.koreatech.koin.domain.user.model.UserIdentity; import in.koreatech.koin.domain.user.model.UserType; +import in.koreatech.koin.domain.user.repository.UserRepository; import in.koreatech.koin.repository.StudentRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; @@ -23,6 +24,9 @@ class UserApiTest extends AcceptanceTest { @Autowired private StudentRepository studentRepository; + @Autowired + private UserRepository userRepository; + @Autowired private JwtProvider jwtProvider; @@ -88,4 +92,76 @@ void studentCheckMe() { } ); } + + + @Test + @DisplayName("올바른 학생계정인지 확인한다 - 토큰 정보가 올바르지 않으면 401") + void studentCheckMeUnAuthorized() { + Student student = Student.builder() + .studentNumber("2019136135") + .anonymousNickname("익명") + .department("컴퓨터공학부") + .userIdentity(UserIdentity.UNDERGRADUATE) + .isGraduated(false) + .user( + User.builder() + .password("1234") + .nickname("주노") + .name("최준호") + .phoneNumber("010-1234-5678") + .userType(UserType.STUDENT) + .gender(UserGender.MAN) + .email("test@koreatech.ac.kr") + .isAuthed(true) + .isDeleted(false) + .build() + ) + .build(); + + studentRepository.save(student); + String token = "invalidToken"; + + ExtractableResponse response = RestAssured + .given() + .log().all() + .header("Authorization", "BEARER " + token) + .when() + .log().all() + .get("/user/student/me") + .then() + .log().all() + .statusCode(HttpStatus.UNAUTHORIZED.value()) + .extract(); + } + + @Test + @DisplayName("올바른 학생계정인지 확인한다 - 회원을 찾을 수 없으면 404") + void studentCheckMeNotFound() { + User user = User.builder() + .password("1234") + .nickname("주노") + .name("최준호") + .phoneNumber("010-1234-5678") + .userType(UserType.STUDENT) + .gender(UserGender.MAN) + .email("test@koreatech.ac.kr") + .isAuthed(true) + .isDeleted(false) + .build(); + + userRepository.save(user); + String token = jwtProvider.createToken(user); + + ExtractableResponse response = RestAssured + .given() + .log().all() + .header("Authorization", "BEARER " + token) + .when() + .log().all() + .get("/user/student/me") + .then() + .log().all() + .statusCode(HttpStatus.NOT_FOUND.value()) + .extract(); + } } From d183568326d1acd6237b7e4b2080202bdca3cc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Wed, 3 Jan 2024 16:32:44 +0900 Subject: [PATCH 04/11] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EC=83=81?= =?UTF-8?q?=ED=99=A9=20=EB=AA=85=EC=8B=9C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resolver/StudentArgumentResolver.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java index 9c6de830f0..b4b99e807a 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java @@ -31,16 +31,15 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); - if (nativeRequest != null) { - String request = nativeRequest.getHeader("Authorization"); - - if (request != null) { - Long userId = jwtProvider.getUserId(request); - return studentRepository.findById(userId) - .orElseThrow(() -> UserNotFoundException.witDetail("request: " + request)); - } + if (nativeRequest == null) { + throw new AuthException("요청 값이 비어있습니다."); } - - throw new AuthException("request: " + nativeRequest); + String request = nativeRequest.getHeader("Authorization"); + if (request == null) { + throw new AuthException("인증 헤더값이 비어있습니다. request: " + nativeRequest); + } + Long userId = jwtProvider.getUserId(request); + return studentRepository.findById(userId) + .orElseThrow(() -> UserNotFoundException.witDetail("request: " + request)); } } From d37815b11a3480b8e924c89f582862e4075c359b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Wed, 3 Jan 2024 16:34:37 +0900 Subject: [PATCH 05/11] =?UTF-8?q?refactor:=20=EC=83=81=EC=88=98=EC=B6=94?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/resolver/StudentArgumentResolver.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java index b4b99e807a..51c803b1b2 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java @@ -18,6 +18,8 @@ @RequiredArgsConstructor public class StudentArgumentResolver implements HandlerMethodArgumentResolver { + private static final String AUTHORIZATION = "Authorization"; + private final JwtProvider jwtProvider; private final StudentRepository studentRepository; @@ -34,12 +36,13 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m if (nativeRequest == null) { throw new AuthException("요청 값이 비어있습니다."); } - String request = nativeRequest.getHeader("Authorization"); - if (request == null) { - throw new AuthException("인증 헤더값이 비어있습니다. request: " + nativeRequest); + + String authorizationHeader = nativeRequest.getHeader(AUTHORIZATION); + if (authorizationHeader == null) { + throw new AuthException("인증 헤더값이 비어있습니다. authorizationHeader: " + nativeRequest); } - Long userId = jwtProvider.getUserId(request); + Long userId = jwtProvider.getUserId(authorizationHeader); return studentRepository.findById(userId) - .orElseThrow(() -> UserNotFoundException.witDetail("request: " + request)); + .orElseThrow(() -> UserNotFoundException.witDetail("authorizationHeader: " + authorizationHeader)); } } From c405f12c858d67b48e3b90e163eaa24ed8074e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Wed, 3 Jan 2024 16:36:05 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/user/exception/UserNotFoundException.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java index 44002810a9..f7e2a4e1cb 100644 --- a/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java +++ b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java @@ -4,9 +4,6 @@ public class UserNotFoundException extends RuntimeException { private static final String DEFAULT_MESSAGE = "존재하지 않는 사용자입니다."; - public UserNotFoundException() { - } - public UserNotFoundException(String message) { super(message); } From dc1d8d64396f5504f247ad0ba8ae77292552fbe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Wed, 3 Jan 2024 16:45:26 +0900 Subject: [PATCH 07/11] =?UTF-8?q?chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/auth/resolver/StudentArgumentResolver.java | 2 +- .../koin/domain/user/controller/StudentController.java | 4 ++-- .../koreatech/koin/{ => domain/user}/dto/StudentResponse.java | 2 +- .../java/in/koreatech/koin/domain/user/model/Student.java | 2 -- .../koin/{ => domain/user}/repository/StudentRepository.java | 2 +- .../koin/{ => domain/user}/service/StudentService.java | 4 ++-- src/test/java/in/koreatech/koin/acceptance/UserApiTest.java | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-) rename src/main/java/in/koreatech/koin/{ => domain/user}/dto/StudentResponse.java (95%) rename src/main/java/in/koreatech/koin/{ => domain/user}/repository/StudentRepository.java (87%) rename src/main/java/in/koreatech/koin/{ => domain/user}/service/StudentService.java (85%) diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java index 51c803b1b2..f168e111a9 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java @@ -4,7 +4,7 @@ import in.koreatech.koin.domain.auth.StudentAuth; import in.koreatech.koin.domain.auth.exception.AuthException; import in.koreatech.koin.domain.user.exception.UserNotFoundException; -import in.koreatech.koin.repository.StudentRepository; +import in.koreatech.koin.domain.user.repository.StudentRepository; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; diff --git a/src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java b/src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java index 4e65e68595..f25e16f044 100644 --- a/src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java +++ b/src/main/java/in/koreatech/koin/domain/user/controller/StudentController.java @@ -2,8 +2,8 @@ import in.koreatech.koin.domain.auth.StudentAuth; import in.koreatech.koin.domain.user.model.Student; -import in.koreatech.koin.dto.StudentResponse; -import in.koreatech.koin.service.StudentService; +import in.koreatech.koin.domain.user.dto.StudentResponse; +import in.koreatech.koin.domain.user.service.StudentService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/in/koreatech/koin/dto/StudentResponse.java b/src/main/java/in/koreatech/koin/domain/user/dto/StudentResponse.java similarity index 95% rename from src/main/java/in/koreatech/koin/dto/StudentResponse.java rename to src/main/java/in/koreatech/koin/domain/user/dto/StudentResponse.java index e7f656ff2c..9574ac2d56 100644 --- a/src/main/java/in/koreatech/koin/dto/StudentResponse.java +++ b/src/main/java/in/koreatech/koin/domain/user/dto/StudentResponse.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.dto; +package in.koreatech.koin.domain.user.dto; import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; diff --git a/src/main/java/in/koreatech/koin/domain/user/model/Student.java b/src/main/java/in/koreatech/koin/domain/user/model/Student.java index 179add02e4..28cdc65829 100644 --- a/src/main/java/in/koreatech/koin/domain/user/model/Student.java +++ b/src/main/java/in/koreatech/koin/domain/user/model/Student.java @@ -5,7 +5,6 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; import jakarta.persistence.MapsId; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; @@ -46,7 +45,6 @@ public class Student { @OneToOne @MapsId - @JoinColumn(name = "user_id") private User user; @Builder diff --git a/src/main/java/in/koreatech/koin/repository/StudentRepository.java b/src/main/java/in/koreatech/koin/domain/user/repository/StudentRepository.java similarity index 87% rename from src/main/java/in/koreatech/koin/repository/StudentRepository.java rename to src/main/java/in/koreatech/koin/domain/user/repository/StudentRepository.java index 0dfe3b23c5..1cb539a0d0 100644 --- a/src/main/java/in/koreatech/koin/repository/StudentRepository.java +++ b/src/main/java/in/koreatech/koin/domain/user/repository/StudentRepository.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.repository; +package in.koreatech.koin.domain.user.repository; import in.koreatech.koin.domain.user.model.Student; import java.util.Optional; diff --git a/src/main/java/in/koreatech/koin/service/StudentService.java b/src/main/java/in/koreatech/koin/domain/user/service/StudentService.java similarity index 85% rename from src/main/java/in/koreatech/koin/service/StudentService.java rename to src/main/java/in/koreatech/koin/domain/user/service/StudentService.java index 98b2b50b93..cc1f4874f2 100644 --- a/src/main/java/in/koreatech/koin/service/StudentService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/StudentService.java @@ -1,8 +1,8 @@ -package in.koreatech.koin.service; +package in.koreatech.koin.domain.user.service; import in.koreatech.koin.domain.user.exception.UserNotFoundException; import in.koreatech.koin.domain.user.model.Student; -import in.koreatech.koin.dto.StudentResponse; +import in.koreatech.koin.domain.user.dto.StudentResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java index ccafa72d87..760a9f27e2 100644 --- a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java @@ -10,7 +10,7 @@ import in.koreatech.koin.domain.user.model.UserIdentity; import in.koreatech.koin.domain.user.model.UserType; import in.koreatech.koin.domain.user.repository.UserRepository; -import in.koreatech.koin.repository.StudentRepository; +import in.koreatech.koin.domain.user.repository.StudentRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; From b5bb2fb814fd37b330d965e83554b9701bc048c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Wed, 3 Jan 2024 16:46:40 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/user/dto/StudentResponse.java | 2 -- src/test/java/in/koreatech/koin/acceptance/UserApiTest.java | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/user/dto/StudentResponse.java b/src/main/java/in/koreatech/koin/domain/user/dto/StudentResponse.java index 9574ac2d56..bf3e6d58cc 100644 --- a/src/main/java/in/koreatech/koin/domain/user/dto/StudentResponse.java +++ b/src/main/java/in/koreatech/koin/domain/user/dto/StudentResponse.java @@ -4,9 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import in.koreatech.koin.domain.user.model.Student; import in.koreatech.koin.domain.user.model.User; -import lombok.extern.slf4j.Slf4j; -@Slf4j @JsonNaming(value = SnakeCaseStrategy.class) public record StudentResponse( String anonymousNickname, diff --git a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java index 760a9f27e2..4e86d0f91d 100644 --- a/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/UserApiTest.java @@ -9,8 +9,8 @@ import in.koreatech.koin.domain.user.model.UserGender; import in.koreatech.koin.domain.user.model.UserIdentity; import in.koreatech.koin.domain.user.model.UserType; -import in.koreatech.koin.domain.user.repository.UserRepository; import in.koreatech.koin.domain.user.repository.StudentRepository; +import in.koreatech.koin.domain.user.repository.UserRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; @@ -93,7 +93,6 @@ void studentCheckMe() { ); } - @Test @DisplayName("올바른 학생계정인지 확인한다 - 토큰 정보가 올바르지 않으면 401") void studentCheckMeUnAuthorized() { From b3f501fdda18c72aa3b5cd5638cf25ac674fa578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Jan 2024 14:05:12 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor:=20=EC=98=A4=ED=83=80=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java | 4 ++-- .../koreatech/koin/domain/auth/exception/AuthException.java | 2 +- .../koin/domain/auth/resolver/StudentArgumentResolver.java | 2 +- .../koin/domain/user/exception/UserNotFoundException.java | 2 +- .../in/koreatech/koin/domain/user/service/UserService.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java b/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java index cef142e293..07299b10a0 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java +++ b/src/main/java/in/koreatech/koin/domain/auth/JwtProvider.java @@ -46,7 +46,7 @@ public String createToken(User user) { public Long getUserId(String requestToken) { if (requestToken == null || !requestToken.toUpperCase().startsWith(BEARER_PREFIX)) { - throw AuthException.witDetail("token: " + requestToken); + throw AuthException.withDetail("token: " + requestToken); } String token = requestToken.substring(BEARER_PREFIX.length()); @@ -61,7 +61,7 @@ public Long getUserId(String requestToken) { return Long.parseLong(userId); } catch (JwtException e) { - throw AuthException.witDetail("token: " + token); + throw AuthException.withDetail("token: " + token); } } diff --git a/src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java b/src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java index 4257f07e69..2b59d47766 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java +++ b/src/main/java/in/koreatech/koin/domain/auth/exception/AuthException.java @@ -11,7 +11,7 @@ public AuthException(String message) { super(message); } - public static AuthException witDetail(String detail) { + public static AuthException withDetail(String detail) { String message = String.format("%s %s", DEFAULT_MESSAGE, detail); return new AuthException(message); } diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java index f168e111a9..07c14221fc 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java @@ -43,6 +43,6 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m } Long userId = jwtProvider.getUserId(authorizationHeader); return studentRepository.findById(userId) - .orElseThrow(() -> UserNotFoundException.witDetail("authorizationHeader: " + authorizationHeader)); + .orElseThrow(() -> UserNotFoundException.withDetail("authorizationHeader: " + authorizationHeader)); } } diff --git a/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java index f7e2a4e1cb..a4be8555a8 100644 --- a/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java +++ b/src/main/java/in/koreatech/koin/domain/user/exception/UserNotFoundException.java @@ -8,7 +8,7 @@ public UserNotFoundException(String message) { super(message); } - public static UserNotFoundException witDetail(String detail) { + public static UserNotFoundException withDetail(String detail) { String message = String.format("%s %s", DEFAULT_MESSAGE, detail); return new UserNotFoundException(message); } diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index ae6b4ee694..fdd07baa72 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -28,7 +28,7 @@ public class UserService { @Transactional public UserLoginResponse login(UserLoginRequest request) { User user = userRepository.findByEmail(request.email()) - .orElseThrow(() -> UserNotFoundException.witDetail("request: " + request)); + .orElseThrow(() -> UserNotFoundException.withDetail("request: " + request)); if (!user.isSamePassword(request.password())) { throw new IllegalArgumentException("잘못된 로그인 정보입니다. request: " + request); From dae9bae092b5574443b4c4fecdcc6b5ef993aa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Jan 2024 14:07:34 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20=EC=98=A4=EB=A5=98=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EA=B5=AC=EC=B2=B4=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/user/service/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index fdd07baa72..1722a93a6d 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -31,7 +31,7 @@ public UserLoginResponse login(UserLoginRequest request) { .orElseThrow(() -> UserNotFoundException.withDetail("request: " + request)); if (!user.isSamePassword(request.password())) { - throw new IllegalArgumentException("잘못된 로그인 정보입니다. request: " + request); + throw new IllegalArgumentException("비밀번호가 틀렸습니다. request: " + request); } String accessToken = jwtProvider.createToken(user); From be0b7508559b3f6b916e04d4a15ca70a72490a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Jan 2024 14:08:24 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/auth/resolver/StudentArgumentResolver.java | 2 +- .../koin/domain/user/repository/StudentRepository.java | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java index 07c14221fc..962237823c 100644 --- a/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/StudentArgumentResolver.java @@ -30,7 +30,7 @@ public boolean supportsParameter(MethodParameter parameter) { @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); if (nativeRequest == null) { diff --git a/src/main/java/in/koreatech/koin/domain/user/repository/StudentRepository.java b/src/main/java/in/koreatech/koin/domain/user/repository/StudentRepository.java index 1cb539a0d0..96b4c36709 100644 --- a/src/main/java/in/koreatech/koin/domain/user/repository/StudentRepository.java +++ b/src/main/java/in/koreatech/koin/domain/user/repository/StudentRepository.java @@ -9,8 +9,4 @@ public interface StudentRepository extends Repository { Student save(Student student); Optional findById(Long id); - - Boolean existsById(Long id); - - void delete(Student student); }