From 0dc617857d4309cc216c3b9ebeb400b302c9655d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=EC=9A=B0?= Date: Mon, 11 Aug 2025 22:51:31 +0900 Subject: [PATCH 01/24] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B8=B0=EB=B3=B8=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberController.java | 10 ++++++++++ .../member/controller/code/MemberExceptionCode.java | 2 ++ .../member/controller/code/MemberSuccessCode.java | 1 + .../domain/member/dto/request/MemberLoginRequest.java | 4 ++++ .../domain/member/repository/MemberRepository.java | 4 ++++ .../domain/member/service/MemberService.java | 11 +++++++++++ 6 files changed, 32 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java index ce1a57e..9ac0cf6 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java @@ -3,6 +3,7 @@ import jakarta.validation.Valid; import kr.ac.ks.cs_web_back.domain.member.controller.code.MemberSuccessCode; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberLoginRequest; import kr.ac.ks.cs_web_back.domain.member.service.MemberService; import kr.ac.ks.cs_web_back.global.response.CsResponse; import lombok.RequiredArgsConstructor; @@ -24,4 +25,13 @@ public CsResponse register( Long id = memberService.createMember(request); return CsResponse.of(MemberSuccessCode.GENERATED_REGISTERED, id); } + + @PostMapping("/login") + @ResponseStatus(HttpStatus.OK) + public CsResponse login( + @Valid @RequestBody MemberLoginRequest request + ) { + String token = memberService.loginMember(request); + return CsResponse.of(MemberSuccessCode.LOGIN_SUCCESS, token); + } } diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java index 1ac9747..06ee8e1 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java @@ -13,6 +13,8 @@ public enum MemberExceptionCode implements ExceptionCode { BAD_REQUEST_NO_USERNAME(9012, "사용자명이 입력되지 않았습니다."), CONFLICT_EMAIL(5010, "이미 존재하는 이메일입니다."), CONFLICT_USERNAME(5012, "이미 존재하는 사용자명입니다."), + NOT_FOUND_USER(6001, "일치하는 사용자가 존재하지 않습니다."), + UNAUTHORIZED_PASSWORD(8011, "비밀번호가 일치하지 않습니다."), ; private final int code; diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java index 1c7cab9..af5a9d5 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java @@ -8,6 +8,7 @@ @AllArgsConstructor public enum MemberSuccessCode implements SuccessCode { + LOGIN_SUCCESS(2001, "로그인에 성공했습니다."), GENERATED_REGISTERED(2101, "회원가입에 성공했습니다."), OK_WITHDRAWN(2102,"회원 탈퇴에 성공했습니다."), OK_FOUND_USER_PROFILE(2103, "사용자 정보 조회에 성공했습니다."), diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java new file mode 100644 index 0000000..efbbec1 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java @@ -0,0 +1,4 @@ +package kr.ac.ks.cs_web_back.domain.member.dto.request; + +public record MemberLoginRequest(String email, String password) { +} diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java index 3e5c7f9..ec1ed55 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java @@ -3,7 +3,11 @@ import kr.ac.ks.cs_web_back.domain.member.model.Member; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface MemberRepository extends JpaRepository { boolean existsByEmail(String email); boolean existsByUsername(String username); + + Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java index a8b60d6..c23a225 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java @@ -2,10 +2,14 @@ import kr.ac.ks.cs_web_back.domain.member.controller.code.MemberExceptionCode; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberLoginRequest; import kr.ac.ks.cs_web_back.domain.member.model.Member; import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; import kr.ac.ks.cs_web_back.global.exeption.domain.ConflictException; +import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; +import kr.ac.ks.cs_web_back.global.exeption.domain.UnauthorizedException; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.parameters.P; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -35,4 +39,11 @@ public Long createMember(MemberCreateRequest request) { return memberRepository.save(member).getId(); } + public String loginMember(MemberLoginRequest request) { + Member member = memberRepository.findByEmail(request.email()).orElseThrow(() -> new NotFoundException(MemberExceptionCode.NOT_FOUND_USER)); + if(!passwordEncoder.matches(request.password(), member.getPassword())) + throw new UnauthorizedException(MemberExceptionCode.UNAUTHORIZED_PASSWORD); + long tokenExpireTime = 1000L * 60 * 60; + + } } From 3c654338b9644aa94cae6eadb93362125ed84240 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:26:55 +0900 Subject: [PATCH 02/24] =?UTF-8?q?feat(#11):=20JWT=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 0b75a7b..6030f9e 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,9 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' } tasks.named('test') { From 0afd832e6071a8b740dde972a4baf21fc47905b1 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:27:18 +0900 Subject: [PATCH 03/24] =?UTF-8?q?feat(#11):=20SecurityConfig=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java b/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java index 8dc49ea..f0e2c3a 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java @@ -29,7 +29,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authz -> authz .requestMatchers(PathRequest.toH2Console()).permitAll() // H2 콘솔 경로 허용 - .requestMatchers("/member/register", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/member/register", "/auth/login", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ); From 9e1a23a8da874bb068c04dec105b609069e7e674 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:27:40 +0900 Subject: [PATCH 04/24] =?UTF-8?q?feat(#11):=20JWT=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ac/ks/cs_web_back/global/jwt/JwtUtil.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java b/src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java new file mode 100644 index 0000000..e3d0667 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java @@ -0,0 +1,30 @@ +package kr.ac.ks.cs_web_back.global.jwt; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.stereotype.Component; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.util.Base64; +import java.util.Date; + +@Component +public class JwtUtil { + private final String secret = "RXZlbiBhIHF1YW50dW0gY29tcHV0ZXIgd291bGQgaGF2ZSB0byBjYWxjdWxhdGUgdW50aWwgdGhlIGVuZCBvZiB0aGUgdW5pdmVyc2UgdG8gYnJlYWsgdGhpcy4="; + private Key hmacKey; + public JwtUtil() { + byte[] decodedKey = Base64.getDecoder().decode(secret); + this.hmacKey = new SecretKeySpec(decodedKey, SignatureAlgorithm.HS256.getJcaName()); + } + + public String generateToken(String email, Long expireTime) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + expireTime); + return Jwts.builder().setSubject(email) + .setIssuedAt(now) + .setExpiration(expiryDate) + .signWith(hmacKey) + .compact(); + } +} \ No newline at end of file From ab34f73a45b3210306b154f8f295d7edf96d1b3f Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:29:16 +0900 Subject: [PATCH 05/24] =?UTF-8?q?refactor(#11):=20Member=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=97=90=EC=84=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=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 --- .../domain/member/controller/MemberController.java | 11 +---------- .../member/controller/code/MemberExceptionCode.java | 1 - .../member/controller/code/MemberSuccessCode.java | 1 - .../domain/member/service/MemberService.java | 11 ++--------- 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java index 9ac0cf6..68e1023 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java @@ -3,7 +3,7 @@ import jakarta.validation.Valid; import kr.ac.ks.cs_web_back.domain.member.controller.code.MemberSuccessCode; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; -import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; import kr.ac.ks.cs_web_back.domain.member.service.MemberService; import kr.ac.ks.cs_web_back.global.response.CsResponse; import lombok.RequiredArgsConstructor; @@ -25,13 +25,4 @@ public CsResponse register( Long id = memberService.createMember(request); return CsResponse.of(MemberSuccessCode.GENERATED_REGISTERED, id); } - - @PostMapping("/login") - @ResponseStatus(HttpStatus.OK) - public CsResponse login( - @Valid @RequestBody MemberLoginRequest request - ) { - String token = memberService.loginMember(request); - return CsResponse.of(MemberSuccessCode.LOGIN_SUCCESS, token); - } } diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java index 06ee8e1..2dd961a 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java @@ -14,7 +14,6 @@ public enum MemberExceptionCode implements ExceptionCode { CONFLICT_EMAIL(5010, "이미 존재하는 이메일입니다."), CONFLICT_USERNAME(5012, "이미 존재하는 사용자명입니다."), NOT_FOUND_USER(6001, "일치하는 사용자가 존재하지 않습니다."), - UNAUTHORIZED_PASSWORD(8011, "비밀번호가 일치하지 않습니다."), ; private final int code; diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java index af5a9d5..1c7cab9 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java @@ -8,7 +8,6 @@ @AllArgsConstructor public enum MemberSuccessCode implements SuccessCode { - LOGIN_SUCCESS(2001, "로그인에 성공했습니다."), GENERATED_REGISTERED(2101, "회원가입에 성공했습니다."), OK_WITHDRAWN(2102,"회원 탈퇴에 성공했습니다."), OK_FOUND_USER_PROFILE(2103, "사용자 정보 조회에 성공했습니다."), diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java index c23a225..21bef06 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java @@ -2,14 +2,14 @@ import kr.ac.ks.cs_web_back.domain.member.controller.code.MemberExceptionCode; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; -import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; import kr.ac.ks.cs_web_back.domain.member.model.Member; import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; import kr.ac.ks.cs_web_back.global.exeption.domain.ConflictException; import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; import kr.ac.ks.cs_web_back.global.exeption.domain.UnauthorizedException; +import kr.ac.ks.cs_web_back.global.jwt.JwtUtil; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.parameters.P; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -39,11 +39,4 @@ public Long createMember(MemberCreateRequest request) { return memberRepository.save(member).getId(); } - public String loginMember(MemberLoginRequest request) { - Member member = memberRepository.findByEmail(request.email()).orElseThrow(() -> new NotFoundException(MemberExceptionCode.NOT_FOUND_USER)); - if(!passwordEncoder.matches(request.password(), member.getPassword())) - throw new UnauthorizedException(MemberExceptionCode.UNAUTHORIZED_PASSWORD); - long tokenExpireTime = 1000L * 60 * 60; - - } } From b99c46b68e62d61643ce2c616bb066e39fcd7d5c Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:29:32 +0900 Subject: [PATCH 06/24] =?UTF-8?q?feat(#11):=20=EC=9D=B8=EC=A6=9D=EC=9A=A9?= =?UTF-8?q?=20DTO(AuthLoginRequest)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/dto/request/AuthLoginRequest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/request/AuthLoginRequest.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/request/AuthLoginRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/request/AuthLoginRequest.java new file mode 100644 index 0000000..65bebe9 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/request/AuthLoginRequest.java @@ -0,0 +1,15 @@ +package kr.ac.ks.cs_web_back.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public record AuthLoginRequest( + @Schema(description = "이메일", example = "example@ks.ac.kr") + @NotBlank(message = "이메일이 입력되지 않았습니다.") + @Email(message = "이메일 형식이 올바르지 않습니다.") + String email, + @Schema(description = "비밀번호", example = "examplePassword123") + @NotBlank(message = "비밀번호가 입력되지 않았습니다.") + String password +) {} From 11ff918f7f8100b7c6e4c4731562ac4aaea86713 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:29:41 +0900 Subject: [PATCH 07/24] =?UTF-8?q?feat(#11):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8A=94=20AuthService?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthService.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java new file mode 100644 index 0000000..ded26b1 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java @@ -0,0 +1,34 @@ +package kr.ac.ks.cs_web_back.domain.auth.service; + +import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthExceptionCode; +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.member.model.Member; +import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; +import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; +import kr.ac.ks.cs_web_back.global.exeption.domain.UnauthorizedException; +import kr.ac.ks.cs_web_back.global.jwt.JwtUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class AuthService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; + + public String loginMember(AuthLoginRequest request) { + Member member = memberRepository.findByEmail(request.email()) + .orElseThrow(() -> new NotFoundException(AuthExceptionCode.NOT_FOUND_USER)); + + if(!passwordEncoder.matches(request.password(), member.getPassword())) + throw new UnauthorizedException(AuthExceptionCode.UNAUTHORIZED_PASSWORD); + + long tokenExpireTime = 1000L * 60 * 60; + return jwtUtil.generateToken(member.getEmail(), tokenExpireTime); + } +} From 818dc3128f2644c20b1c26a372d2126e0bc816b7 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:30:13 +0900 Subject: [PATCH 08/24] =?UTF-8?q?feat(#11):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20API=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=EB=A5=BC?= =?UTF-8?q?=20=EC=9C=84=ED=95=9C=20AuthController=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 28 ++++++++++++++++ .../controller/SpringDocAuthController.java | 32 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java new file mode 100644 index 0000000..d14edb3 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java @@ -0,0 +1,28 @@ +package kr.ac.ks.cs_web_back.domain.auth.controller; + +import jakarta.validation.Valid; +import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthSuccessCode; +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.service.AuthService; +import kr.ac.ks.cs_web_back.global.response.CsResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthController implements SpringDocAuthController{ + + private final AuthService authService; + + @Override + @PostMapping("/login") + @ResponseStatus(HttpStatus.OK) + public CsResponse login( + @Valid @RequestBody AuthLoginRequest request + ) { + String token = authService.loginMember(request); + return CsResponse.of(AuthSuccessCode.LOGIN_SUCCESS, token); + } +} diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java new file mode 100644 index 0000000..a2c8168 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java @@ -0,0 +1,32 @@ +package kr.ac.ks.cs_web_back.domain.auth.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.global.response.CsResponse; +import kr.ac.ks.cs_web_back.global.swagger.error.ApiErrorResponse; +import kr.ac.ks.cs_web_back.global.swagger.error.ErrorCase; +import org.springframework.http.HttpStatus; + +@Tag(name = "인증 API", description = "인증 관련 API") +public interface SpringDocAuthController { + + @Operation(summary = "로그인", description = "사용자의 이메일과 비밀번호로 로그인하고, 성공시 JWT 토큰을 반환합니다.") + @ApiResponse(responseCode = "200", description = "로그인에 성공했습니다.") + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/auth/login", errorCases = { + @ErrorCase(description = "이메일 없음", code = 9010, exampleMessage = "이메일이 입력되지 않았습니다."), + @ErrorCase(description = "비밀번호 없음", code = 9011, exampleMessage = "비밀번호가 입력되지 않았습니다.") + }) + @ApiErrorResponse(status = HttpStatus.UNAUTHORIZED, instance = "/auth/login", errorCases = { + @ErrorCase(description = "비밀번호 불일치", code = 8011, exampleMessage = "비밀번호가 일치하지 않습니다.") + }) + @ApiErrorResponse(status = HttpStatus.NOT_FOUND, instance = "/auth/login", errorCases = { + @ErrorCase(description = "사용자 없음", code = 6001, exampleMessage = "일치하는 사용자가 존재하지 않습니다.") + }) + CsResponse login( + @RequestBody AuthLoginRequest request + ); + +} From ed1289283ed46048031d40ac775234c82a5dad9d Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:30:20 +0900 Subject: [PATCH 09/24] =?UTF-8?q?feat(#11):=20Auth=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20Success/Exception=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/code/AuthExceptionCode.java | 19 +++++++++++++++++++ .../auth/controller/code/AuthSuccessCode.java | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthSuccessCode.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java new file mode 100644 index 0000000..3c23e07 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java @@ -0,0 +1,19 @@ +package kr.ac.ks.cs_web_back.domain.auth.controller.code; + +import kr.ac.ks.cs_web_back.global.exeption.dto.ExceptionCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum AuthExceptionCode implements ExceptionCode { + + BAD_REQUEST_NO_EMAIL(9010, "이메일이 입력되지 않았습니다."), + BAD_REQUEST_NO_PASSWORD(9011, "비밀번호가 입력되지 않았습니다."), + UNAUTHORIZED_PASSWORD(8011, "비밀번호가 일치하지 않습니다."), + NOT_FOUND_USER(6001, "일치하는 사용자가 존재하지 않습니다."), + ; + + private final int code; + private final String message; +} diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthSuccessCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthSuccessCode.java new file mode 100644 index 0000000..7ce75e5 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthSuccessCode.java @@ -0,0 +1,16 @@ +package kr.ac.ks.cs_web_back.domain.auth.controller.code; + +import kr.ac.ks.cs_web_back.global.response.SuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum AuthSuccessCode implements SuccessCode { + + LOGIN_SUCCESS(2001, "로그인에 성공했습니다."), + ; + + private final int code; + private final String message; +} From 0cd2a08fe8975d78ecf840513400e3f64172b336 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:31:30 +0900 Subject: [PATCH 10/24] =?UTF-8?q?test(#11):=20AuthFixture=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/fixture/AuthFixture.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/test/java/kr/ac/ks/cs_web_back/domain/auth/fixture/AuthFixture.java diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/fixture/AuthFixture.java b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/fixture/AuthFixture.java new file mode 100644 index 0000000..d044f07 --- /dev/null +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/fixture/AuthFixture.java @@ -0,0 +1,24 @@ +package kr.ac.ks.cs_web_back.domain.auth.fixture; + +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; + +public class AuthFixture { + public static AuthLoginRequest successLoginRequest() { + return new AuthLoginRequest( + "example@ks.ac.kr", + "examplePassword1234!" + ); + } + public static AuthLoginRequest wrongPasswordLoginRequest() { + return new AuthLoginRequest( + "example@ks.ac.kr", + "wrongPassword1234!" + ); + } + public static AuthLoginRequest NonExistentEmailLoginRequest() { + return new AuthLoginRequest( + "nonexistent@ks.ac.kr", + "wrongPassword1234!" + ); + } +} From 27a01f8d6f521f86385e380e236028eedca8ba47 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:32:33 +0900 Subject: [PATCH 11/24] =?UTF-8?q?test(#11):=20AuthService=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthServiceTest.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java new file mode 100644 index 0000000..8f4dc4d --- /dev/null +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java @@ -0,0 +1,90 @@ +package kr.ac.ks.cs_web_back.domain.auth.service; + +import jakarta.transaction.Transactional; +import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthExceptionCode; +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.fixture.AuthFixture; +import kr.ac.ks.cs_web_back.domain.member.fixture.MemberFixture; +import kr.ac.ks.cs_web_back.domain.member.model.Member; +import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; +import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; +import kr.ac.ks.cs_web_back.global.exeption.domain.UnauthorizedException; +import org.aspectj.weaver.ast.Not; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +@SpringBootTest +@Transactional +public class AuthServiceTest { + @Autowired + private AuthService authService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @BeforeEach // 각각의 테스트가 실행되기 직전에 항상 실행되는 메소드(테스트용 회원을 만들어 DB에 저장) + void setUp() { + Member testMember = MemberFixture.memberFixture(); + String rawPassword = "examplePassword1234!"; + String encodedPassword = passwordEncoder.encode(rawPassword); + memberRepository.save(Member.builder() + .email(testMember.getEmail()) + .password(encodedPassword) + .username(testMember.getUsername()) + .build() + ); + } + + @Test + @DisplayName("로그인 성공: 올바른 이메일과 비밀번호로 로그인 시 200 OK와 토큰을 반환한다.") + void shouldLoginSuccessfullyAndReturnToken() { + // given + AuthLoginRequest request = AuthFixture.successLoginRequest(); + + // when + String token = authService.loginMember(request); + + // then + assert token != null && !token.isEmpty(); + } + + @Test + @DisplayName("로그인 실패: 잘못된 비밀번호로 로그인 시 예외가 발생한다.") + void shouldThrowExceptionWhenLoginWithWrongPassword() { + // given + AuthLoginRequest request = AuthFixture.wrongPasswordLoginRequest(); + + // when & then + assertThatThrownBy(() -> authService.loginMember(request)) + .isInstanceOf(UnauthorizedException.class) + .satisfies(e -> { + UnauthorizedException exception = (UnauthorizedException) e; + assertThat(exception.getExceptionCode()).isEqualTo(AuthExceptionCode.UNAUTHORIZED_PASSWORD); + }); + } + + @Test + @DisplayName("로그인 실패: 존재하지 않는 이메일로 로그인 시 예외가 발생한다.") + void shouldThrowExceptionWhenLoginWithNonExistentEmail() { + // given + AuthLoginRequest request = AuthFixture.NonExistentEmailLoginRequest(); + + // when & then + assertThatThrownBy(() -> authService.loginMember(request)) + .isInstanceOf(NotFoundException.class) + .satisfies(e -> { + NotFoundException exception = (NotFoundException) e; + assertThat(exception.getExceptionCode()).isEqualTo(AuthExceptionCode.NOT_FOUND_USER); + }); + } +} From 63c34e5d4f6a1d0a6ec132b80b3476e5a599fcfe Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:32:44 +0900 Subject: [PATCH 12/24] =?UTF-8?q?test(#11):=20AuthController=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthControllerTest.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java new file mode 100644 index 0000000..758cb92 --- /dev/null +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java @@ -0,0 +1,117 @@ +package kr.ac.ks.cs_web_back.domain.auth.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthExceptionCode; +import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthSuccessCode; +import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.fixture.AuthFixture; +import kr.ac.ks.cs_web_back.domain.auth.service.AuthService; +import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; +import kr.ac.ks.cs_web_back.global.exeption.domain.UnauthorizedException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = AuthController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) +public class AuthControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private AuthService authService; + + @Test + @DisplayName("로그인 성공: 올바른 이메일과 비밀번호로 로그인 시 200 OK와 토큰을 반환한다.") + void loginSuccessReturns200OkWithToken() throws Exception { + // given + AuthLoginRequest request = AuthFixture.successLoginRequest(); + + when(authService.loginMember(any(AuthLoginRequest.class))) + .thenReturn("fake-jwt-token"); + + // when & then + mockMvc.perform(post("/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(AuthSuccessCode.LOGIN_SUCCESS.getCode())) + .andExpect(jsonPath("$.message").value(AuthSuccessCode.LOGIN_SUCCESS.getMessage())) + .andExpect(jsonPath("$.data").value("fake-jwt-token")); + } + + @Test + @DisplayName("로그인 실패: 요청 본문에 비밀번호가 없으면 400 Bad Request를 반환한다.") + void loginWithoutPasswordReturns400BadRequest() throws Exception { + // given + AuthLoginRequest request = new AuthLoginRequest("example@ks.ac.kr", ""); + + // when & then + mockMvc.perform(post("/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("로그인 실패: 요청 본문에 이메일이 없으면 400 Bad Request를 반홚나다.") + void loginWithoutEmailReturns400BadRequest() throws Exception { + // given + AuthLoginRequest request = new AuthLoginRequest("", "examplePassword1234!"); + + // when & then + mockMvc.perform(post("/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("로그인 실패: 비밀번호가 일치하지 않으면 401 Unauthorized를 반환한다.") + void loginWithWrongPasswordReturns401Unauthorized() throws Exception { + // given + AuthLoginRequest request = AuthFixture.wrongPasswordLoginRequest(); + + when(authService.loginMember(any(AuthLoginRequest.class))) + .thenThrow(new UnauthorizedException(AuthExceptionCode.UNAUTHORIZED_PASSWORD)); + + // when & then + mockMvc.perform(post("/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value(AuthExceptionCode.UNAUTHORIZED_PASSWORD.getCode())) + .andExpect(jsonPath("$.message").value(AuthExceptionCode.UNAUTHORIZED_PASSWORD.getMessage())); + } + + @Test + @DisplayName("로그인 실패: 존재하지 않는 이메일이면 404 Not Found를 반환한다.") + void loginWithNonExistentEmailReturns404NotFound() throws Exception { + // given + AuthLoginRequest request = AuthFixture.NonExistentEmailLoginRequest(); + + when(authService.loginMember(any(AuthLoginRequest.class))) + .thenThrow(new NotFoundException(AuthExceptionCode.NOT_FOUND_USER)); + + // when & then + mockMvc.perform(post("/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(AuthExceptionCode.NOT_FOUND_USER.getCode())) + .andExpect(jsonPath("$.message").value(AuthExceptionCode.NOT_FOUND_USER.getMessage())); + } +} From 0887b95546e38d18a0c17256313afaca8b5266d4 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:33:48 +0900 Subject: [PATCH 13/24] =?UTF-8?q?refactor(#11):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=B4=EC=A7=84=20MemberLoginRequest=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/dto/request/MemberLoginRequest.java | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java deleted file mode 100644 index efbbec1..0000000 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberLoginRequest.java +++ /dev/null @@ -1,4 +0,0 @@ -package kr.ac.ks.cs_web_back.domain.member.dto.request; - -public record MemberLoginRequest(String email, String password) { -} From c51cd1b23b7511f69dfb7d40e8d556cab80d6707 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Sun, 17 Aug 2025 19:45:55 +0900 Subject: [PATCH 14/24] =?UTF-8?q?style(#11):=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java index 8f4dc4d..8784c41 100644 --- a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java @@ -9,7 +9,6 @@ import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; import kr.ac.ks.cs_web_back.global.exeption.domain.UnauthorizedException; -import org.aspectj.weaver.ast.Not; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 3fdc645c54050eba1cd047c5934a457bacc33ee4 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 11:48:03 +0900 Subject: [PATCH 15/24] =?UTF-8?q?feat(#11):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20API=20=EC=9D=91=EB=8B=B5=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/dto/response/AuthLoginResponse.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/response/AuthLoginResponse.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/response/AuthLoginResponse.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/response/AuthLoginResponse.java new file mode 100644 index 0000000..1235558 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/dto/response/AuthLoginResponse.java @@ -0,0 +1,13 @@ +package kr.ac.ks.cs_web_back.domain.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Builder +public record AuthLoginResponse( + @Schema(description = "액세스 토큰", example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJle...") + String authorization, + @Schema(description = "리프레시 토큰", example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJle...") + String authorizationRefresh +) { +} \ No newline at end of file From 93936c47a9cdfe6ce40e89660138a92bbed911f9 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 11:48:14 +0900 Subject: [PATCH 16/24] =?UTF-8?q?feat(#11):=20=EC=95=A1=EC=84=B8=EC=8A=A4/?= =?UTF-8?q?=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthService.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java index ded26b1..a936fc9 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java @@ -2,6 +2,7 @@ import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthExceptionCode; import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.dto.response.AuthLoginResponse; import kr.ac.ks.cs_web_back.domain.member.model.Member; import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; @@ -21,14 +22,20 @@ public class AuthService { private final PasswordEncoder passwordEncoder; private final JwtUtil jwtUtil; - public String loginMember(AuthLoginRequest request) { + public AuthLoginResponse loginMember(AuthLoginRequest request) { Member member = memberRepository.findByEmail(request.email()) .orElseThrow(() -> new NotFoundException(AuthExceptionCode.NOT_FOUND_USER)); if(!passwordEncoder.matches(request.password(), member.getPassword())) throw new UnauthorizedException(AuthExceptionCode.UNAUTHORIZED_PASSWORD); - long tokenExpireTime = 1000L * 60 * 60; - return jwtUtil.generateToken(member.getEmail(), tokenExpireTime); + long accessTokenExpireTime = 1000L * 60 * 60; // 1 hour + long refreshTokenExpireTime = 1000L * 60 * 60; // 24 hours + String accessToken = jwtUtil.generateToken(member.getEmail(), accessTokenExpireTime); + String refreshToken = jwtUtil.generateToken(member.getEmail(), refreshTokenExpireTime); + return AuthLoginResponse.builder() + .authorization(accessToken) + .authorizationRefresh(refreshToken) + .build(); } } From a9c13828b605d747df9f31dc45858c61222c86bd Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 11:48:27 +0900 Subject: [PATCH 17/24] =?UTF-8?q?feat(#11):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20API=20=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs_web_back/domain/auth/controller/AuthController.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java index d14edb3..e0504a7 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java @@ -3,6 +3,7 @@ import jakarta.validation.Valid; import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthSuccessCode; import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.dto.response.AuthLoginResponse; import kr.ac.ks.cs_web_back.domain.auth.service.AuthService; import kr.ac.ks.cs_web_back.global.response.CsResponse; import lombok.RequiredArgsConstructor; @@ -16,13 +17,11 @@ public class AuthController implements SpringDocAuthController{ private final AuthService authService; - @Override @PostMapping("/login") - @ResponseStatus(HttpStatus.OK) - public CsResponse login( + public CsResponse login( @Valid @RequestBody AuthLoginRequest request ) { - String token = authService.loginMember(request); + AuthLoginResponse token = authService.loginMember(request); return CsResponse.of(AuthSuccessCode.LOGIN_SUCCESS, token); } } From 199f6926c6e671cb84629bbc4093fa9e1dbea333 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 11:48:36 +0900 Subject: [PATCH 18/24] =?UTF-8?q?docs(#11):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20API=20=EB=AC=B8=EC=84=9C=20=EC=9D=91=EB=8B=B5=20DTO=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/SpringDocAuthController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java index a2c8168..d132ac5 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/SpringDocAuthController.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.dto.response.AuthLoginResponse; import kr.ac.ks.cs_web_back.global.response.CsResponse; import kr.ac.ks.cs_web_back.global.swagger.error.ApiErrorResponse; import kr.ac.ks.cs_web_back.global.swagger.error.ErrorCase; @@ -25,7 +26,7 @@ public interface SpringDocAuthController { @ApiErrorResponse(status = HttpStatus.NOT_FOUND, instance = "/auth/login", errorCases = { @ErrorCase(description = "사용자 없음", code = 6001, exampleMessage = "일치하는 사용자가 존재하지 않습니다.") }) - CsResponse login( + CsResponse login( @RequestBody AuthLoginRequest request ); From afbd3a3975584a14eecb71b547fe799941cc1282 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 11:48:56 +0900 Subject: [PATCH 19/24] =?UTF-8?q?test(#11):=20AuthService=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs_web_back/domain/auth/service/AuthServiceTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java index 8784c41..1828974 100644 --- a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthServiceTest.java @@ -3,6 +3,7 @@ import jakarta.transaction.Transactional; import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthExceptionCode; import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.dto.response.AuthLoginResponse; import kr.ac.ks.cs_web_back.domain.auth.fixture.AuthFixture; import kr.ac.ks.cs_web_back.domain.member.fixture.MemberFixture; import kr.ac.ks.cs_web_back.domain.member.model.Member; @@ -51,10 +52,12 @@ void shouldLoginSuccessfullyAndReturnToken() { AuthLoginRequest request = AuthFixture.successLoginRequest(); // when - String token = authService.loginMember(request); + AuthLoginResponse response = authService.loginMember(request); // then - assert token != null && !token.isEmpty(); + assertThat(response).isNotNull(); + assertThat(response.authorization()).isNotNull().isNotBlank(); + assertThat(response.authorizationRefresh()).isNotNull().isNotBlank(); } @Test From 2dc09b50fe4fae72ad53f14624bbfb4457fa75ca Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 11:49:03 +0900 Subject: [PATCH 20/24] =?UTF-8?q?test(#11):=20AuthController=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthControllerTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java index 758cb92..815661a 100644 --- a/src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthControllerTest.java @@ -4,6 +4,7 @@ import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthExceptionCode; import kr.ac.ks.cs_web_back.domain.auth.controller.code.AuthSuccessCode; import kr.ac.ks.cs_web_back.domain.auth.dto.request.AuthLoginRequest; +import kr.ac.ks.cs_web_back.domain.auth.dto.response.AuthLoginResponse; import kr.ac.ks.cs_web_back.domain.auth.fixture.AuthFixture; import kr.ac.ks.cs_web_back.domain.auth.service.AuthService; import kr.ac.ks.cs_web_back.global.exeption.domain.NotFoundException; @@ -39,9 +40,13 @@ public class AuthControllerTest { void loginSuccessReturns200OkWithToken() throws Exception { // given AuthLoginRequest request = AuthFixture.successLoginRequest(); + AuthLoginResponse response = AuthLoginResponse.builder() + .authorization("fake-access-token") + .authorizationRefresh("fake-refresh-token") + .build(); when(authService.loginMember(any(AuthLoginRequest.class))) - .thenReturn("fake-jwt-token"); + .thenReturn(response); // when & then mockMvc.perform(post("/auth/login") @@ -50,7 +55,8 @@ void loginSuccessReturns200OkWithToken() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(AuthSuccessCode.LOGIN_SUCCESS.getCode())) .andExpect(jsonPath("$.message").value(AuthSuccessCode.LOGIN_SUCCESS.getMessage())) - .andExpect(jsonPath("$.data").value("fake-jwt-token")); + .andExpect(jsonPath("$.data.authorization").value("fake-access-token")) + .andExpect(jsonPath("$.data.authorizationRefresh").value("fake-refresh-token")); } @Test From 17f733b157d26e12606ffd254dccafdeaa1cbd50 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 11:51:35 +0900 Subject: [PATCH 21/24] =?UTF-8?q?refactor(#11):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20import=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ac/ks/cs_web_back/domain/auth/controller/AuthController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java index e0504a7..2a1e1c2 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/AuthController.java @@ -7,7 +7,6 @@ import kr.ac.ks.cs_web_back.domain.auth.service.AuthService; import kr.ac.ks.cs_web_back.global.response.CsResponse; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController From 75978b6d4d6dba6a636d6e725bcd41129b4b3d39 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 12:02:37 +0900 Subject: [PATCH 22/24] =?UTF-8?q?refactor(#11):=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/code/AuthExceptionCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java index 3c23e07..f538830 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/controller/code/AuthExceptionCode.java @@ -9,7 +9,7 @@ public enum AuthExceptionCode implements ExceptionCode { BAD_REQUEST_NO_EMAIL(9010, "이메일이 입력되지 않았습니다."), - BAD_REQUEST_NO_PASSWORD(9011, "비밀번호가 입력되지 않았습니다."), + BAD_REQUEST_NO_PASSWORD(9010, "비밀번호가 입력되지 않았습니다."), UNAUTHORIZED_PASSWORD(8011, "비밀번호가 일치하지 않습니다."), NOT_FOUND_USER(6001, "일치하는 사용자가 존재하지 않습니다."), ; From 70e81ed829cd9ef415a8a3d8d15103b821f84ba1 Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 12:32:30 +0900 Subject: [PATCH 23/24] =?UTF-8?q?refactor(#11):=20JwtUtil=EC=9D=B4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=82=A4=EC=99=80=20=EB=A7=8C=EB=A3=8C=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9D=84=20=EC=9D=BD=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ac/ks/cs_web_back/global/jwt/JwtUtil.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java b/src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java index e3d0667..adb5c8d 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/jwt/JwtUtil.java @@ -2,6 +2,8 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.spec.SecretKeySpec; @@ -11,12 +13,29 @@ @Component public class JwtUtil { - private final String secret = "RXZlbiBhIHF1YW50dW0gY29tcHV0ZXIgd291bGQgaGF2ZSB0byBjYWxjdWxhdGUgdW50aWwgdGhlIGVuZCBvZiB0aGUgdW5pdmVyc2UgdG8gYnJlYWsgdGhpcy4="; + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.accessTokenExpireTime}") + private Long accessTokenExpireTime; + + @Value("${jwt.refreshTokenExpireTime}") + private Long refreshTokenExpireTime; + private Key hmacKey; - public JwtUtil() { + + @PostConstruct + public void init() { byte[] decodedKey = Base64.getDecoder().decode(secret); this.hmacKey = new SecretKeySpec(decodedKey, SignatureAlgorithm.HS256.getJcaName()); } + public String generateAccessToken(String email) { + return generateToken(email, accessTokenExpireTime); + } + + public String generateRefreshToken(String email) { + return generateToken(email, refreshTokenExpireTime); + } public String generateToken(String email, Long expireTime) { Date now = new Date(); From 21017057b4ac377def6d870830a0f1efea7b8d8c Mon Sep 17 00:00:00 2001 From: lgw323 Date: Tue, 26 Aug 2025 12:32:48 +0900 Subject: [PATCH 24/24] =?UTF-8?q?refactor(#11):=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20JwtUtil=20=ED=86=A0=ED=81=B0=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ks/cs_web_back/domain/auth/service/AuthService.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java index a936fc9..ee4dd47 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/auth/service/AuthService.java @@ -28,11 +28,9 @@ public AuthLoginResponse loginMember(AuthLoginRequest request) { if(!passwordEncoder.matches(request.password(), member.getPassword())) throw new UnauthorizedException(AuthExceptionCode.UNAUTHORIZED_PASSWORD); - - long accessTokenExpireTime = 1000L * 60 * 60; // 1 hour - long refreshTokenExpireTime = 1000L * 60 * 60; // 24 hours - String accessToken = jwtUtil.generateToken(member.getEmail(), accessTokenExpireTime); - String refreshToken = jwtUtil.generateToken(member.getEmail(), refreshTokenExpireTime); + // 24 hours + String accessToken = jwtUtil.generateAccessToken(member.getEmail()); + String refreshToken = jwtUtil.generateRefreshToken(member.getEmail()); return AuthLoginResponse.builder() .authorization(accessToken) .authorizationRefresh(refreshToken)