From fba63da853a62241bce0dec37323446651399080 Mon Sep 17 00:00:00 2001 From: yongyong213 Date: Wed, 27 May 2026 16:38:55 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=208=EC=A3=BC=EC=B0=A8=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 3 ++- .../umc/member/converter/MemberConverter.java | 8 ++++---- .../java/umc/member/dto/MemberReqDTO.java | 13 ++++++++++-- src/main/java/umc/member/entity/Member.java | 2 +- .../umc/member/service/MemberService.java | 20 ++++++++++++++++--- src/main/java/umc/term/enums/TermName.java | 17 +++++++++++++--- .../term/exception/code/TermErrorCode.java | 3 ++- .../umc/term/repository/TermRepository.java | 4 ++++ 8 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/main/java/umc/member/controller/MemberController.java b/src/main/java/umc/member/controller/MemberController.java index a3df2cd7..5f9e9160 100644 --- a/src/main/java/umc/member/controller/MemberController.java +++ b/src/main/java/umc/member/controller/MemberController.java @@ -1,6 +1,7 @@ package umc.member.controller; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import umc.global.apiPayload.ApiResponse; @@ -42,7 +43,7 @@ public ApiResponse getHomeInfo( @PostMapping("/v1/auth/signup") @Operation(summary = "회원가입") public ApiResponse signUp( - @RequestBody MemberReqDTO.SingUpDTO request + @Valid @RequestBody MemberReqDTO.SingUpDTO request ) { MemberResDTO.AuthResDTO.SignUpResultDTO result = memberService.signUp(request); diff --git a/src/main/java/umc/member/converter/MemberConverter.java b/src/main/java/umc/member/converter/MemberConverter.java index 83e72c17..da09183f 100644 --- a/src/main/java/umc/member/converter/MemberConverter.java +++ b/src/main/java/umc/member/converter/MemberConverter.java @@ -26,16 +26,16 @@ public static MemberResDTO.GetInfo toGetInfo( .build(); } - public static Member toMember(MemberReqDTO.SingUpDTO request, String password){ + public static Member toMember(MemberReqDTO.SingUpDTO request, String password, Gender gender, SocialType socialType){ return Member.builder() .email(request.email()) .password(password) .name(request.name()) - .gender(Gender.valueOf(request.gender())) + .gender(gender) .birth(request.birth()) .address(request.address()) - .socialUid(request.socailUid()) - .socialType(SocialType.valueOf(request.socailType())) + .socialUid(request.socialUid()) + .socialType(socialType) .point(0) .build(); } diff --git a/src/main/java/umc/member/dto/MemberReqDTO.java b/src/main/java/umc/member/dto/MemberReqDTO.java index ab0caa5f..0e9bd488 100644 --- a/src/main/java/umc/member/dto/MemberReqDTO.java +++ b/src/main/java/umc/member/dto/MemberReqDTO.java @@ -1,5 +1,7 @@ package umc.member.dto; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import umc.member.enums.SocialType; import java.time.LocalDate; @@ -12,14 +14,21 @@ public record GetInfo( ){} public record SingUpDTO( + @NotBlank(message = "이메일은 필수 입력 값입니다.") + @Email(message = "올바른 이메일 형식이 아닙니다.") String email, + @NotBlank(message = "비밀번호는 필수 입력 값입니다.") String password, + @NotBlank(message = "이름은 필수 입력 값입니다.") String name, + @NotBlank(message = "성별은 필수 입력 값입니다.") String gender, + @NotBlank(message = "생년월일은 필수 입력 값입니다.") LocalDate birth, + @NotBlank(message = "주소는 필수 입력 값입니다.") String address, - String socailUid, - String socailType, + String socialUid, + String socialType, List agreedTerms, List userFoods ) {} diff --git a/src/main/java/umc/member/entity/Member.java b/src/main/java/umc/member/entity/Member.java index b1edf9d9..0bf37d98 100644 --- a/src/main/java/umc/member/entity/Member.java +++ b/src/main/java/umc/member/entity/Member.java @@ -41,7 +41,7 @@ public class Member extends BaseEntity { @Column(name = "address", nullable = false) private String address; - @Column(name = "email", nullable = false) + @Column(name = "email", nullable = false, unique = true) private String email; @Column(name = "password") diff --git a/src/main/java/umc/member/service/MemberService.java b/src/main/java/umc/member/service/MemberService.java index 04e4aa12..521f3240 100644 --- a/src/main/java/umc/member/service/MemberService.java +++ b/src/main/java/umc/member/service/MemberService.java @@ -1,9 +1,9 @@ package umc.member.service; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import umc.food.entity.Food; import umc.food.exeption.FoodException; import umc.food.exeption.code.FoodErrorCode; @@ -22,6 +22,7 @@ import umc.member.repository.MemberRepository; import umc.member.repository.MemberTermRepository; import umc.term.entity.Term; +import umc.term.enums.TermName; import umc.term.exception.TermException; import umc.term.exception.code.TermErrorCode; import umc.term.repository.TermRepository; @@ -39,6 +40,7 @@ public class MemberService { private final TermRepository termRepository; private final PasswordEncoder passwordEncoder; + @Transactional(readOnly = true) public MemberResDTO.GetInfo getInfo(MemberReqDTO.GetInfo dto) { Long memberId = dto.id(); @@ -57,7 +59,7 @@ public MemberResDTO.AuthResDTO.SignUpResultDTO signUp(MemberReqDTO.SingUpDTO req .orElseThrow(() -> new MemberException(MemberErrorCode.INVALID_GENDER_TYPE)); SocialType socialType = Arrays.stream(SocialType.values()) - .filter(s -> s.name().equalsIgnoreCase(request.socailType())) + .filter(s -> s.name().equalsIgnoreCase(request.socialType())) .findFirst() .orElseThrow(() -> new MemberException(MemberErrorCode.INVALID_SOCIAL_TYPE)); @@ -67,7 +69,8 @@ public MemberResDTO.AuthResDTO.SignUpResultDTO signUp(MemberReqDTO.SingUpDTO req String encordPassword = passwordEncoder.encode(request.password()); - Member newMember = MemberConverter.toMember(request, encordPassword); + // 검증된 enum 값 converter로 넘기기 + Member newMember = MemberConverter.toMember(request, encordPassword, gender, socialType); List foodList = request.userFoods().stream() .map(foodId -> foodRepository.findById(foodId) @@ -77,6 +80,17 @@ public MemberResDTO.AuthResDTO.SignUpResultDTO signUp(MemberReqDTO.SingUpDTO req List memberFoodList = MemberConverter.toMemberFoodList(foodList); memberFoodList.forEach(memberFood -> memberFood.mappingMember(newMember)); + // 필수 약관 검증 추가 + List requiredTermNames = TermName.getRequiredTermNames(); + + List requiredTermIds = termRepository.findAllByNameIn(requiredTermNames).stream() + .map(Term::getId) + .toList(); + + if(request.agreedTerms() == null || !request.agreedTerms().containsAll(requiredTermIds)){ + throw new TermException(TermErrorCode.REQUIRED_TERM_NOT_AGREE); + } + List termList = request.agreedTerms().stream() .map(termId -> termRepository.findById(termId) .orElseThrow(() -> new TermException(TermErrorCode.TERM_NOT_FOUND))) diff --git a/src/main/java/umc/term/enums/TermName.java b/src/main/java/umc/term/enums/TermName.java index 9ddbe3e8..ac8d8dac 100644 --- a/src/main/java/umc/term/enums/TermName.java +++ b/src/main/java/umc/term/enums/TermName.java @@ -3,12 +3,23 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.util.Arrays; +import java.util.List; + @Getter @RequiredArgsConstructor public enum TermName { - SERVICE_TERMS("서비스 이용약관"), - PRIVACY_POLICY("개인정보 수집 및 이용"), - LOCATION_TERMS("위치정보 이용약관"); + SERVICE_TERMS("서비스 이용약관", true), + PRIVACY_POLICY("개인정보 수집 및 이용", true), + LOCATION_TERMS("위치정보 이용약관", false), + MARKETING_TERMS("마케팅 수신 동의", false); private final String label; + private final boolean isRequired; + + public static List getRequiredTermNames() { + return Arrays.stream(values()) + .filter(TermName::isRequired) + .toList(); + } } diff --git a/src/main/java/umc/term/exception/code/TermErrorCode.java b/src/main/java/umc/term/exception/code/TermErrorCode.java index 191799fc..224790b8 100644 --- a/src/main/java/umc/term/exception/code/TermErrorCode.java +++ b/src/main/java/umc/term/exception/code/TermErrorCode.java @@ -8,7 +8,8 @@ @Getter @RequiredArgsConstructor public enum TermErrorCode implements BaseErrorCode { - TERM_NOT_FOUND(HttpStatus.NOT_FOUND, "TERM404_1", "존재하지 않는 약관입니다."); + TERM_NOT_FOUND(HttpStatus.NOT_FOUND, "TERM404_1", "존재하지 않는 약관입니다."), + REQUIRED_TERM_NOT_AGREE(HttpStatus.BAD_REQUEST, "TERM400_1", "필수 약관에 모두 동의하지 않았습니다."); private final HttpStatus status; private final String code; diff --git a/src/main/java/umc/term/repository/TermRepository.java b/src/main/java/umc/term/repository/TermRepository.java index f7e5a033..965d5f54 100644 --- a/src/main/java/umc/term/repository/TermRepository.java +++ b/src/main/java/umc/term/repository/TermRepository.java @@ -2,6 +2,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import umc.term.entity.Term; +import umc.term.enums.TermName; + +import java.util.List; public interface TermRepository extends JpaRepository { + List findAllByNameIn(List names); } From 1846df3a56e88b58d57b31c791e284792d02470c Mon Sep 17 00:00:00 2001 From: yongyong213 Date: Thu, 28 May 2026 12:45:59 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20jwt=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++ src/main/java/umc/config/SecurityConfig.java | 25 ++++-- .../global/security/filter/JwtAuthFilter.java | 73 ++++++++++++++++ .../umc/global/security/util/JwtUtil.java | 83 +++++++++++++++++++ .../member/controller/MemberController.java | 8 ++ .../java/umc/member/dto/MemberReqDTO.java | 8 ++ .../java/umc/member/dto/MemberResDTO.java | 7 ++ .../exception/code/MemberErrorCode.java | 3 +- .../exception/code/MemberSuccessCode.java | 3 +- .../umc/member/service/MemberService.java | 20 +++++ src/main/resources/application.yaml | 6 ++ 11 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 src/main/java/umc/global/security/filter/JwtAuthFilter.java create mode 100644 src/main/java/umc/global/security/util/JwtUtil.java diff --git a/build.gradle b/build.gradle index 38170618..49754125 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,12 @@ dependencies { // Security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + + // Jwt + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + implementation 'org.springframework.boot:spring-boot-configuration-processor' } tasks.named('test') { diff --git a/src/main/java/umc/config/SecurityConfig.java b/src/main/java/umc/config/SecurityConfig.java index 05d8bbde..1acb632e 100644 --- a/src/main/java/umc/config/SecurityConfig.java +++ b/src/main/java/umc/config/SecurityConfig.java @@ -1,5 +1,6 @@ package umc.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -8,19 +9,29 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import umc.global.security.filter.JwtAuthFilter; import umc.global.security.handler.CustomAccessDenied; import umc.global.security.handler.CustomEntryPoint; +import umc.global.security.service.CustomUserDetailService; +import umc.global.security.util.JwtUtil; @EnableWebSecurity @Configuration +@RequiredArgsConstructor public class SecurityConfig { + + private final JwtUtil jwtUtil; + private final CustomUserDetailService customUserDetailService; + private final String[] allowUris = { // Swagger 허용 "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**", "/auth/**", - "/api/v1/auth/signup" + "/api/v1/auth/signup", + "/api/v1/login" }; @Bean @@ -31,10 +42,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(allowUris).permitAll() .anyRequest().authenticated() ) - .formLogin(form -> form - .defaultSuccessUrl("/swagger-ui/index.html", true) - .permitAll() - ) + .formLogin(AbstractHttpConfigurer::disable) + .sessionManagement(AbstractHttpConfigurer::disable) + .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") @@ -61,4 +71,9 @@ public CustomAccessDenied customAccessDenied(){ public CustomEntryPoint customEntryPoint(){ return new CustomEntryPoint(); } + + @Bean + public JwtAuthFilter jwtAuthFilter(){ + return new JwtAuthFilter(jwtUtil, customUserDetailService); + } } diff --git a/src/main/java/umc/global/security/filter/JwtAuthFilter.java b/src/main/java/umc/global/security/filter/JwtAuthFilter.java new file mode 100644 index 00000000..69d63f90 --- /dev/null +++ b/src/main/java/umc/global/security/filter/JwtAuthFilter.java @@ -0,0 +1,73 @@ +package umc.global.security.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; +import umc.global.apiPayload.ApiResponse; +import umc.global.apiPayload.code.BaseErrorCode; +import umc.global.apiPayload.code.GeneralErrorCode; +import umc.global.security.service.CustomUserDetailService; +import umc.global.security.util.JwtUtil; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final CustomUserDetailService customUserDetailsService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + + try { + // 토큰 가져오기 + String token = request.getHeader("Authorization"); + // token이 없거나 Bearer가 아니면 넘기기 + if (token == null || !token.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + // Bearer이면 추출 + token = token.replace("Bearer ", ""); + // AccessToken 검증하기: 올바른 토큰이면 + if (jwtUtil.isValid(token)) { + // 토큰에서 이메일 추출 + String email = jwtUtil.getEmail(token); + // 인증 객체 생성: 이메일로 찾아온 뒤, 인증 객체 생성 + UserDetails user = customUserDetailsService.loadUserByUsername(email); + Authentication auth = new UsernamePasswordAuthenticationToken( + user, + null, + user.getAuthorities() + ); + // 인증 완료 후 SecurityContextHolder에 넣기 + SecurityContextHolder.getContext().setAuthentication(auth); + } + filterChain.doFilter(request, response); + } catch (Exception e) { + ObjectMapper mapper = new ObjectMapper(); + BaseErrorCode code = GeneralErrorCode.UNAUTHORIZED; + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(code.getStatus().value()); + + ApiResponse errorResponse = ApiResponse.onFailure(code,null); + + mapper.writeValue(response.getOutputStream(), errorResponse); + } + } +} diff --git a/src/main/java/umc/global/security/util/JwtUtil.java b/src/main/java/umc/global/security/util/JwtUtil.java new file mode 100644 index 00000000..6eac4f55 --- /dev/null +++ b/src/main/java/umc/global/security/util/JwtUtil.java @@ -0,0 +1,83 @@ +package umc.global.security.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; +import umc.global.security.entity.AuthMember; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.stream.Collectors; + +@Component +public class JwtUtil { + private final SecretKey secretKey; + private final Duration accessExpiration; + + public JwtUtil( + @Value("${jwt.token.secretKey}") String secret, + @Value("${jwt.token.expiration.access}") Long accessExpiration + ) { + this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + this.accessExpiration = Duration.ofMillis(accessExpiration); + } + + // AccessToken 생성 + public String createAccessToken(AuthMember member) { + return createToken(member, accessExpiration); + } + + public String getEmail(String token) { + try { + return getClaims(token).getPayload().getSubject(); // Parsing해서 Subject 가져오기 + } catch (JwtException e) { + return null; + } + } + + public boolean isValid(String token) { + try { + getClaims(token); + return true; + } catch (JwtException e) { + return false; + } + } + + // 토큰 생성 + private String createToken(AuthMember member, Duration expiration) { + Instant now = Instant.now(); + + // 인가 정보 + String authorities = member.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + return Jwts.builder() + .subject(member.getUsername()) // User 이메일을 Subject로 + .claim("role", authorities) + .claim("email", member.getUsername()) + .issuedAt(Date.from(now)) // 언제 발급한지 + .expiration(Date.from(now.plus(expiration))) // 언제까지 유효한지 + .signWith(secretKey) // sign할 Key + .compact(); + } + + // 토큰 정보 가져오기 + private Jws getClaims(String token) throws JwtException { + return Jwts.parser() + .verifyWith(secretKey) + .clockSkewSeconds(60) + .build() + .parseSignedClaims(token); + } +} diff --git a/src/main/java/umc/member/controller/MemberController.java b/src/main/java/umc/member/controller/MemberController.java index 5f9e9160..1adfad8c 100644 --- a/src/main/java/umc/member/controller/MemberController.java +++ b/src/main/java/umc/member/controller/MemberController.java @@ -49,4 +49,12 @@ public ApiResponse signUp( return ApiResponse.onSuccess(MemberSuccessCode.JOIN_OK, result); } + + @PostMapping("/v1/login") + @Operation(summary = "로그인") + public ApiResponse login( + @Valid @RequestBody MemberReqDTO.LoginDTO request + ) { + return ApiResponse.onSuccess(MemberSuccessCode.LOGIN_OK, memberService.login(request)); + } } diff --git a/src/main/java/umc/member/dto/MemberReqDTO.java b/src/main/java/umc/member/dto/MemberReqDTO.java index 0e9bd488..a4f962d4 100644 --- a/src/main/java/umc/member/dto/MemberReqDTO.java +++ b/src/main/java/umc/member/dto/MemberReqDTO.java @@ -32,4 +32,12 @@ public record SingUpDTO( List agreedTerms, List userFoods ) {} + + public record LoginDTO( + @NotBlank(message = "이메일은 필수 입력 값입니다.") + @Email(message = "올바른 이메일 형식이 아닙니다.") + String email, + @NotBlank(message = "비밀번호는 필수 입력 값입니다.") + String password + ) {} } diff --git a/src/main/java/umc/member/dto/MemberResDTO.java b/src/main/java/umc/member/dto/MemberResDTO.java index 3476b5bd..3ced15e4 100644 --- a/src/main/java/umc/member/dto/MemberResDTO.java +++ b/src/main/java/umc/member/dto/MemberResDTO.java @@ -1,6 +1,7 @@ package umc.member.dto; import lombok.Builder; +import lombok.Getter; import java.time.LocalDateTime; @@ -28,4 +29,10 @@ public record SignUpResultDTO( LocalDateTime createdAt ) {} } + + @Builder + @Getter + public static class LoginResDTO{ + String accessToken; + } } diff --git a/src/main/java/umc/member/exception/code/MemberErrorCode.java b/src/main/java/umc/member/exception/code/MemberErrorCode.java index 7b42ab93..93ac5825 100644 --- a/src/main/java/umc/member/exception/code/MemberErrorCode.java +++ b/src/main/java/umc/member/exception/code/MemberErrorCode.java @@ -12,7 +12,8 @@ public enum MemberErrorCode implements BaseErrorCode { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER404_1", "해당 사용자를 찾을 수 없습니다."), DUPLICATE_EMAIL(HttpStatus.CONFLICT, "MEMBER409_1", "중복된 이메일입니다."), INVALID_GENDER_TYPE(HttpStatus.BAD_REQUEST, "GENDER400_1", "유효하지 않은 성별입니다."), - INVALID_SOCIAL_TYPE(HttpStatus.BAD_REQUEST, "SOCIAL400_1", "유효하지 않은 소셜 타입입니다."); + INVALID_SOCIAL_TYPE(HttpStatus.BAD_REQUEST, "SOCIAL400_1", "유효하지 않은 소셜 타입입니다."), + INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "MEMBER400_1", "일치하지 않는 비밀번호입니다."); private final HttpStatus status; private final String code; diff --git a/src/main/java/umc/member/exception/code/MemberSuccessCode.java b/src/main/java/umc/member/exception/code/MemberSuccessCode.java index 186c3cea..03ad7851 100644 --- a/src/main/java/umc/member/exception/code/MemberSuccessCode.java +++ b/src/main/java/umc/member/exception/code/MemberSuccessCode.java @@ -11,7 +11,8 @@ public enum MemberSuccessCode implements BaseSuccessCode { OK(HttpStatus.OK, "MEMBER200_1", "성공적으로 유저를 조회했습니다."), - JOIN_OK(HttpStatus.OK, "MEMBER200_2", "성공적으로 가입을 완료했습니다."); + JOIN_OK(HttpStatus.OK, "MEMBER200_2", "성공적으로 가입을 완료했습니다."), + LOGIN_OK(HttpStatus.OK, "MEMBER200_3", "성공적으로 로그인했습니다."),; private final HttpStatus status; private final String code; diff --git a/src/main/java/umc/member/service/MemberService.java b/src/main/java/umc/member/service/MemberService.java index 521f3240..355c5689 100644 --- a/src/main/java/umc/member/service/MemberService.java +++ b/src/main/java/umc/member/service/MemberService.java @@ -8,6 +8,8 @@ import umc.food.exeption.FoodException; import umc.food.exeption.code.FoodErrorCode; import umc.food.repository.FoodRepository; +import umc.global.security.entity.AuthMember; +import umc.global.security.util.JwtUtil; import umc.member.converter.MemberConverter; import umc.member.dto.MemberReqDTO; import umc.member.dto.MemberResDTO; @@ -39,6 +41,7 @@ public class MemberService { private final FoodRepository foodRepository; private final TermRepository termRepository; private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; @Transactional(readOnly = true) public MemberResDTO.GetInfo getInfo(MemberReqDTO.GetInfo dto) { @@ -106,4 +109,21 @@ public MemberResDTO.AuthResDTO.SignUpResultDTO signUp(MemberReqDTO.SingUpDTO req return MemberConverter.toSignUpResultDTO(savedMember); } + + @Transactional + public MemberResDTO.LoginResDTO login(MemberReqDTO.LoginDTO request){ + Member member = memberRepository.findByEmail(request.email()) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + + if(!passwordEncoder.matches(request.password(), member.getPassword())){ + throw new MemberException(MemberErrorCode.INVALID_PASSWORD); + } + + AuthMember authMember = new AuthMember(member); + + String accessToken = jwtUtil.createAccessToken(authMember); + return MemberResDTO.LoginResDTO.builder() + .accessToken(accessToken) + .build(); + } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 47347440..3a4158dd 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -17,3 +17,9 @@ spring: properties: hibernate: format_sql: true + +jwt: + token: + secretKey: ${JWT_SECRET_KEY} + expiration: + access: 1800000 # 30분 \ No newline at end of file From 780ac7100c1626c8f9cb0edb2e50975cda0814c7 Mon Sep 17 00:00:00 2001 From: yongyong213 Date: Thu, 28 May 2026 13:00:59 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20api=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/umc/member/controller/MemberController.java | 8 +++++--- src/main/java/umc/member/service/MemberService.java | 11 ++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/umc/member/controller/MemberController.java b/src/main/java/umc/member/controller/MemberController.java index 1adfad8c..0ad51506 100644 --- a/src/main/java/umc/member/controller/MemberController.java +++ b/src/main/java/umc/member/controller/MemberController.java @@ -3,9 +3,11 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import umc.global.apiPayload.ApiResponse; import umc.global.apiPayload.code.BaseSuccessCode; +import umc.global.security.entity.AuthMember; import umc.member.dto.MemberReqDTO; import umc.member.dto.MemberResDTO; import umc.member.exception.code.MemberSuccessCode; @@ -21,13 +23,13 @@ public class MemberController { private final MemberService memberService; private final MissionService missionService; - @PostMapping("/v1/users/me") + @GetMapping("/v2/users/me") @Operation(summary = "마이페이지 조회") public ApiResponse getInfo( - @RequestBody MemberReqDTO.GetInfo dto + @AuthenticationPrincipal AuthMember member ){ BaseSuccessCode code = MemberSuccessCode.OK; - return ApiResponse.onSuccess(code, memberService.getInfo(dto)); + return ApiResponse.onSuccess(code, memberService.getInfo(member)); } @GetMapping("/v1/home") diff --git a/src/main/java/umc/member/service/MemberService.java b/src/main/java/umc/member/service/MemberService.java index 355c5689..1bebdca9 100644 --- a/src/main/java/umc/member/service/MemberService.java +++ b/src/main/java/umc/member/service/MemberService.java @@ -44,13 +44,10 @@ public class MemberService { private final JwtUtil jwtUtil; @Transactional(readOnly = true) - public MemberResDTO.GetInfo getInfo(MemberReqDTO.GetInfo dto) { - Long memberId = dto.id(); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); - - return MemberConverter.toGetInfo(member); + public MemberResDTO.GetInfo getInfo( + AuthMember member + ) { + return MemberConverter.toGetInfo(member.getMember()); } @Transactional