From 68661accbc0a78750d2508f9024e80c214ace096 Mon Sep 17 00:00:00 2001 From: Suhun0331 Date: Tue, 10 Dec 2024 22:35:19 +0900 Subject: [PATCH] =?UTF-8?q?Feat=20&=20Refactor=20:=20security,=20jwt=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=B9=B4?= =?UTF-8?q?=EC=B9=B4=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=EC=A4=91=20/=20member=20=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20/=20config=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/KakaoAuthController.java | 79 ++ .../kkijuk/server/auth/dto/AuthResponse.java | 16 + .../server/auth/dto/CustomOAuth2User.java | 38 + .../kkijuk/server/auth/dto/KakaoResponse.java | 38 + .../server/auth/dto/OAuth2Response.java | 10 + .../server/auth/dto/OAuth2ResponseImpl.java | 23 + .../server/auth/dto/RefreshTokenRequest.java | 11 + .../auth/handler/CustomSuccessHandler.java | 38 + .../server/auth/jwt/JwtCustomFilter.java | 34 + .../umc/kkijuk/server/auth/jwt/JwtFilter.java | 83 ++ .../umc/kkijuk/server/auth/jwt/JwtUtil.java | 94 +++ .../auth/service/CustomOAuth2UserService.java | 133 ++++ .../server/auth/service/KakaoAuthService.java | 79 ++ .../common/template/ResponseTemplate.java | 22 + .../server/config/RestTemplateConfig.java | 13 + .../kkijuk/server/config/SecurityConfig.java | 67 ++ .../Config.java => config/SwaggerConfig.java} | 4 +- .../controller/EmailAuthController.java | 18 +- .../member/controller/MemberController.java | 36 +- .../kkijuk/server/member/domain/Member.java | 17 +- .../server/member/domain/MemberJob.java | 12 + .../umc/kkijuk/server/member/domain/Role.java | 6 + .../server/member/dto/MemberJoinDto.java | 134 ++-- .../repository/MemberJpaRepository.java | 1 + .../member/repository/MemberRepository.java | 1 + .../repository/MemberRepositoryImpl.java | 5 +- .../server/member/service/MemberService.java | 11 +- .../member/service/MemberServiceImpl.java | 150 ++-- .../server/security/SecurityConfig.java | 12 - .../server/security/jwt/TokenProvider.java | 115 +++ .../kkijuk/server/security/jwt/TokenUtil.java | 25 + .../server/security/jwt/dto/TokenDto.java | 20 + .../jwt/filter/JwtAuthorizationFilter.java | 49 ++ .../jwt/filter/JwtExceptionFilter.java | 47 ++ .../handler/JwtAccessDeniedHandler.java | 19 + .../handler/JwtAuthenticationEntryPoint.java | 19 + .../jwt/response/JwtExceptionResponse.java | 11 + .../security/oauth/kakao/KakaoApiClient.java | 67 ++ .../security/oauth/kakao/KakaoLoginParam.java | 22 + .../security/oauth/kakao/KakaoToken.java | 13 + .../security/oauth/kakao/KakaoUserInfo.java | 22 + .../server/security/util/CookieUtil.java | 45 ++ .../member/mock/FakeMemberRepository.java | 6 + .../member/service/MailServiceTest.java | 2 +- .../member/service/MemberServiceTest.java | 732 +++++++++--------- .../unitTest/mock/FakeMemberRepository.java | 105 ++- .../recruit/service/RecruitServiceTest.java | 2 +- .../review/service/ReviewServiceTest.java | 2 +- .../unitTest/tag/service/TagServiceTest.java | 4 +- 49 files changed, 1905 insertions(+), 607 deletions(-) create mode 100644 src/main/java/umc/kkijuk/server/auth/controller/KakaoAuthController.java create mode 100644 src/main/java/umc/kkijuk/server/auth/dto/AuthResponse.java create mode 100644 src/main/java/umc/kkijuk/server/auth/dto/CustomOAuth2User.java create mode 100644 src/main/java/umc/kkijuk/server/auth/dto/KakaoResponse.java create mode 100644 src/main/java/umc/kkijuk/server/auth/dto/OAuth2Response.java create mode 100644 src/main/java/umc/kkijuk/server/auth/dto/OAuth2ResponseImpl.java create mode 100644 src/main/java/umc/kkijuk/server/auth/dto/RefreshTokenRequest.java create mode 100644 src/main/java/umc/kkijuk/server/auth/handler/CustomSuccessHandler.java create mode 100644 src/main/java/umc/kkijuk/server/auth/jwt/JwtCustomFilter.java create mode 100644 src/main/java/umc/kkijuk/server/auth/jwt/JwtFilter.java create mode 100644 src/main/java/umc/kkijuk/server/auth/jwt/JwtUtil.java create mode 100644 src/main/java/umc/kkijuk/server/auth/service/CustomOAuth2UserService.java create mode 100644 src/main/java/umc/kkijuk/server/auth/service/KakaoAuthService.java create mode 100644 src/main/java/umc/kkijuk/server/common/template/ResponseTemplate.java create mode 100644 src/main/java/umc/kkijuk/server/config/RestTemplateConfig.java create mode 100644 src/main/java/umc/kkijuk/server/config/SecurityConfig.java rename src/main/java/umc/kkijuk/server/{swagger/Config.java => config/SwaggerConfig.java} (91%) create mode 100644 src/main/java/umc/kkijuk/server/member/domain/MemberJob.java create mode 100644 src/main/java/umc/kkijuk/server/member/domain/Role.java delete mode 100644 src/main/java/umc/kkijuk/server/security/SecurityConfig.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/TokenProvider.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/TokenUtil.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/dto/TokenDto.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/filter/JwtAuthorizationFilter.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/filter/JwtExceptionFilter.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAccessDeniedHandler.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAuthenticationEntryPoint.java create mode 100644 src/main/java/umc/kkijuk/server/security/jwt/response/JwtExceptionResponse.java create mode 100644 src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoApiClient.java create mode 100644 src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoLoginParam.java create mode 100644 src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoToken.java create mode 100644 src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoUserInfo.java create mode 100644 src/main/java/umc/kkijuk/server/security/util/CookieUtil.java diff --git a/src/main/java/umc/kkijuk/server/auth/controller/KakaoAuthController.java b/src/main/java/umc/kkijuk/server/auth/controller/KakaoAuthController.java new file mode 100644 index 00000000..8f86622b --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/controller/KakaoAuthController.java @@ -0,0 +1,79 @@ +package umc.kkijuk.server.auth.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import umc.kkijuk.server.auth.jwt.JwtUtil; +import umc.kkijuk.server.auth.service.KakaoAuthService; +import umc.kkijuk.server.member.domain.Member; +import umc.kkijuk.server.member.service.MemberService; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/auth/kakao") +@RequiredArgsConstructor +@Slf4j +public class KakaoAuthController { + + private final KakaoAuthService kakaoAuthService; + private final MemberService memberService; + private final JwtUtil jwtUtil; + + @PostMapping("/login") + public ResponseEntity> loginWithKakao(@RequestHeader("Authorization") String kakaoAccessToken) { + try { + // Bearer 제거 + if (kakaoAccessToken.toLowerCase().startsWith("bearer ")) { + kakaoAccessToken = kakaoAccessToken.substring(7).trim(); + } + + // 카카오 사용자 정보 가져오기 + Map kakaoUserInfo = kakaoAuthService.getKakaoUserInfo(kakaoAccessToken); + log.info("카카오 사용자 정보: {}", kakaoUserInfo); + + // 사용자 정보 추출 + String email = kakaoAuthService.extractEmail(kakaoUserInfo); + String name = kakaoAuthService.extractName(kakaoUserInfo); + String phoneNumber = kakaoAuthService.extractPhoneNumber(kakaoUserInfo); + LocalDate birthDate = kakaoAuthService.extractBirthDate(kakaoUserInfo); + +// // 이메일 검증 +// if (email == null || email.isEmpty()) { +// log.error("카카오 사용자 정보에 이메일이 없습니다."); +// return ResponseEntity.badRequest().body(Map.of("error", "카카오 사용자 정보에 이메일이 없습니다.")); +// } + + // 핸드폰 번호 검증 + if (phoneNumber == null || phoneNumber.isEmpty()) { + log.error("카카오 사용자 정보에 핸드폰 번호가 없습니다."); + return ResponseEntity.badRequest().body(Map.of("error", "카카오 사용자 정보에 핸드폰 번호가 없습니다.")); + } + + Member member = memberService.findByPhoneNumber(phoneNumber); + if (member == null) { + log.info("신규 사용자 생성 - 핸드폰 번호: {}", phoneNumber); + member = memberService.createMember(email, name, phoneNumber, birthDate); + } + + // JWT 토큰 생성 + String accessToken = jwtUtil.createAccessToken(member.getEmail()); + String refreshToken = jwtUtil.createRefreshToken(member.getEmail()); + + // 응답 데이터 생성 + Map response = new HashMap<>(); + response.put("accessToken", accessToken); + response.put("refreshToken", refreshToken); + + log.info("로그인 성공: 이메일={}, accessToken 생성 완료", email); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("카카오 로그인 처리 중 오류 발생: {}", e.getMessage(), e); + return ResponseEntity.internalServerError().body(Map.of("error", "로그인 처리 실패")); + } + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/dto/AuthResponse.java b/src/main/java/umc/kkijuk/server/auth/dto/AuthResponse.java new file mode 100644 index 00000000..4ad86971 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/dto/AuthResponse.java @@ -0,0 +1,16 @@ +package umc.kkijuk.server.auth.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AuthResponse { + private String accessToken; + private String refreshToken; + + public AuthResponse(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/dto/CustomOAuth2User.java b/src/main/java/umc/kkijuk/server/auth/dto/CustomOAuth2User.java new file mode 100644 index 00000000..06a68b57 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/dto/CustomOAuth2User.java @@ -0,0 +1,38 @@ +package umc.kkijuk.server.auth.dto; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public class CustomOAuth2User implements OAuth2User { + + private final Collection authorities; + private final Map attributes; + private final String nameAttributeKey; + + public CustomOAuth2User( + Collection authorities, + Map attributes, + String nameAttributeKey) { + this.authorities = authorities; + this.attributes = attributes; + this.nameAttributeKey = nameAttributeKey; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getName() { + return (String) attributes.get(nameAttributeKey); + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/dto/KakaoResponse.java b/src/main/java/umc/kkijuk/server/auth/dto/KakaoResponse.java new file mode 100644 index 00000000..0a3f1562 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/dto/KakaoResponse.java @@ -0,0 +1,38 @@ +package umc.kkijuk.server.auth.dto; + +import java.util.Map; + +public class KakaoResponse implements OAuth2Response { + + private final Map attributes; // 전체 응답 데이터 + private final Map kakaoAccountAttributes; // 카카오 계정 데이터 + private final Map profileAttributes; // 프로필 데이터 + + // 생성자 + public KakaoResponse(Map attributes) { + this.attributes = attributes; + this.kakaoAccountAttributes = (Map) attributes.get("kakao_account"); + this.profileAttributes = + kakaoAccountAttributes != null + ? (Map) kakaoAccountAttributes.get("profile") + : null; + } + + @Override + public String getProvider() { + return "kakao"; // 고정 값 + } + + @Override + public String getProviderId() { + return attributes.get("id").toString(); // 카카오 사용자 고유 ID + } + + @Override + public String getEmail() { + return kakaoAccountAttributes != null && kakaoAccountAttributes.containsKey("email") + ? kakaoAccountAttributes.get("email").toString() + : null; // 이메일이 없는 경우 null 반환 + } + +} diff --git a/src/main/java/umc/kkijuk/server/auth/dto/OAuth2Response.java b/src/main/java/umc/kkijuk/server/auth/dto/OAuth2Response.java new file mode 100644 index 00000000..4738404c --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/dto/OAuth2Response.java @@ -0,0 +1,10 @@ +package umc.kkijuk.server.auth.dto; + +public interface OAuth2Response { + String getProvider(); + + String getProviderId(); + + String getEmail(); + +} diff --git a/src/main/java/umc/kkijuk/server/auth/dto/OAuth2ResponseImpl.java b/src/main/java/umc/kkijuk/server/auth/dto/OAuth2ResponseImpl.java new file mode 100644 index 00000000..c57fd449 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/dto/OAuth2ResponseImpl.java @@ -0,0 +1,23 @@ +package umc.kkijuk.server.auth.dto; + +import lombok.Getter; +import umc.kkijuk.server.member.domain.Member; + +@Getter +public class OAuth2ResponseImpl implements OAuth2Response { + private final String provider = "kakao"; // Provider 고정 (예: Kakao) + private final String providerId; + private final String email; + + public OAuth2ResponseImpl(String providerId, String email) { + this.providerId = providerId; + this.email = email; + } + + public static OAuth2ResponseImpl fromUser(Member member) { + return new OAuth2ResponseImpl( + member.getId().toString(), // 예시로 user ID를 providerId로 설정 + member.getEmail() + ); + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/dto/RefreshTokenRequest.java b/src/main/java/umc/kkijuk/server/auth/dto/RefreshTokenRequest.java new file mode 100644 index 00000000..3addaccb --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/dto/RefreshTokenRequest.java @@ -0,0 +1,11 @@ +package umc.kkijuk.server.auth.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RefreshTokenRequest { + private String email; + private String refreshToken; +} diff --git a/src/main/java/umc/kkijuk/server/auth/handler/CustomSuccessHandler.java b/src/main/java/umc/kkijuk/server/auth/handler/CustomSuccessHandler.java new file mode 100644 index 00000000..f624d02a --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/handler/CustomSuccessHandler.java @@ -0,0 +1,38 @@ +package umc.kkijuk.server.auth.handler; + +import java.io.IOException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + // 기존 리다이렉트 동작 제거 + clearAuthenticationAttributes(request); + + // JSON 응답 + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + + // 인가코드 반환 (테스트용 코드, 프론트엔드에서 처리 가능) + String authorizationCode = request.getParameter("code"); + log.info("로그인 성공, 인가코드 반환: {}", authorizationCode); + + response.getWriter().write(String.format( + "{\"authorizationCode\": \"%s\"}", authorizationCode)); + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/jwt/JwtCustomFilter.java b/src/main/java/umc/kkijuk/server/auth/jwt/JwtCustomFilter.java new file mode 100644 index 00000000..6b38d737 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/jwt/JwtCustomFilter.java @@ -0,0 +1,34 @@ +package umc.kkijuk.server.auth.jwt; + +import java.io.IOException; + +import lombok.RequiredArgsConstructor; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +public class JwtCustomFilter extends OncePerRequestFilter { + + private final JwtFilter jwtFilter; + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String requestUri = request.getRequestURI(); + + // 특정 요청 URI에 대해 필터 제외 + if (requestUri.matches("^\\/login(?:\\/.*)?$") || requestUri.matches("^\\/oauth2(?:\\/.*)?$")) { + filterChain.doFilter(request, response); + return; + } + + // JWT 필터 실행 + jwtFilter.doFilterInternal(request, response, filterChain); + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/jwt/JwtFilter.java b/src/main/java/umc/kkijuk/server/auth/jwt/JwtFilter.java new file mode 100644 index 00000000..3d5d98df --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/jwt/JwtFilter.java @@ -0,0 +1,83 @@ +package umc.kkijuk.server.auth.jwt; + +import java.io.IOException; +import java.util.Collections; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import umc.kkijuk.server.member.domain.Member; +import umc.kkijuk.server.member.repository.MemberRepository; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final MemberRepository memberRepository; + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + String requestUri = request.getRequestURI(); + + // 카카오 로그인 경로 제외 + if (requestUri.startsWith("/api/auth/kakao/login")) { + chain.doFilter(request, response); + return; + } + + try { + final String authorizationHeader = request.getHeader("Authorization"); + String email = null; + String jwt = null; + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + jwt = authorizationHeader.substring(7); + email = jwtUtil.extractEmail(jwt); + } + + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + Member member = memberRepository.findByEmail(email).orElse(null); + + if (member != null && jwtUtil.validateToken(jwt, member.getEmail())) { + UserDetails userDetails = + new org.springframework.security.core.userdetails.User( + member.getEmail(), + "", + Collections.singletonList(new SimpleGrantedAuthority(member.getRole().name()))); + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + log.warn("Invalid JWT Token for member: {}", email); + } + } + } catch (Exception e) { + log.warn("JWT Authentication Error: {}", e.getMessage()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + return; + } + + chain.doFilter(request, response); + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/jwt/JwtUtil.java b/src/main/java/umc/kkijuk/server/auth/jwt/JwtUtil.java new file mode 100644 index 00000000..18b0170e --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/jwt/JwtUtil.java @@ -0,0 +1,94 @@ +package umc.kkijuk.server.auth.jwt; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; + +@Slf4j +@Component +public class JwtUtil { + + @Value("${spring.jwt.secret}") + private String secretKey; + + private Key getSigningKey() { + return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + public String createAccessToken(String email) { + Date expiration = Date.from(Instant.now().plus(1, ChronoUnit.HOURS)); // 1시간 유효 + return Jwts.builder() + .setId(email) + .setIssuedAt(new Date()) + .setExpiration(expiration) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public String createRefreshToken(String email) { + Date expiration = Date.from(Instant.now().plus(7, ChronoUnit.DAYS)); // 7일 유효 + return Jwts.builder() + .setId(email) + .setIssuedAt(new Date()) + .setExpiration(expiration) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public boolean validateToken(String token, String email) { + try { + Claims claims = + Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + + String extractedEmail = claims.getId(); + if (!extractedEmail.equals(email)) { + log.warn("JWT Token validation failed: Email mismatch"); + return false; + } + + if (claims.getExpiration().before(new Date())) { + log.warn("JWT Token validation failed: Token expired"); + return false; + } + + return true; + } catch (JwtException e) { + log.warn("JWT validation failed: {}", e.getMessage()); + return false; + } + } + + public String extractEmail(String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody() + .getId(); + } + + public Long extractFamilyId(String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody() + .get("familyId", Long.class); // 가족 ID 추출 + } + + +} diff --git a/src/main/java/umc/kkijuk/server/auth/service/CustomOAuth2UserService.java b/src/main/java/umc/kkijuk/server/auth/service/CustomOAuth2UserService.java new file mode 100644 index 00000000..2a7a9c81 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/service/CustomOAuth2UserService.java @@ -0,0 +1,133 @@ +package umc.kkijuk.server.auth.service; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import umc.kkijuk.server.auth.dto.CustomOAuth2User; +import umc.kkijuk.server.member.domain.Member; +import umc.kkijuk.server.member.domain.Role; +import umc.kkijuk.server.member.repository.MemberRepository; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final MemberRepository memberRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + // 전체 응답 데이터 로그 출력 + Map attributes = oAuth2User.getAttributes(); + log.info("OAuth2User Attributes: {}", attributes); + + // 사용자 정보 추출 + String email = extractEmail(attributes); + String name = extractName(attributes); + String phoneNumber = extractPhoneNumber(attributes); + LocalDate birthDate = extractBirthDate(attributes); + + log.info("Extracted user info: email={}, name={}, phoneNumber={}, birthDate={}", + email, name, phoneNumber, birthDate); + + if (email == null || email.isEmpty()) { + throw new OAuth2AuthenticationException("Email is required but not provided."); + } + + // 사용자 조회 또는 신규 생성 + Member member = memberRepository.findByEmail(email).orElseGet(() -> createNewMember(email, name, phoneNumber, birthDate)); + + // OAuth2User 객체 생성 + return createCustomOAuth2User(member, attributes); + } + + private OAuth2User createCustomOAuth2User(Member member, Map attributes) { + Map updatedAttributes = new HashMap<>(attributes); + updatedAttributes.put("email", member.getEmail()); // 필수 속성 추가 + updatedAttributes.put("name", member.getName()); + updatedAttributes.put("phoneNumber", member.getPhoneNumber()); + updatedAttributes.put("birthDate", member.getBirthDate().toString()); + + return new CustomOAuth2User( + Collections.singleton(new SimpleGrantedAuthority(member.getRole().name())), // Role에 따라 권한 설정 + updatedAttributes, + "email" // 기본 속성 키 설정 + ); + } + + private String extractEmail(Map attributes) { + if (attributes.containsKey("kakao_account")) { + Map kakaoAccount = (Map) attributes.get("kakao_account"); + return (String) kakaoAccount.get("email"); + } + return null; + } + + private String extractName(Map attributes) { + if (attributes.containsKey("kakao_account")) { + Map profile = (Map) ((Map) attributes.get("kakao_account")).get("profile"); + return profile != null ? (String) profile.get("nickname") : null; + } + return null; + } + + private String extractPhoneNumber(Map attributes) { + if (attributes.containsKey("kakao_account")) { + Map kakaoAccount = (Map) attributes.get("kakao_account"); + return kakaoAccount != null ? (String) kakaoAccount.get("phone_number") : null; + } + return null; + } + + private LocalDate extractBirthDate(Map attributes) { + if (attributes.containsKey("kakao_account")) { + Map kakaoAccount = (Map) attributes.get("kakao_account"); + if (kakaoAccount == null) return null; + + String birthday = (String) kakaoAccount.get("birthday"); // MMDD 형식 + String birthyear = (String) kakaoAccount.get("birthyear"); // YYYY 형식 (선택적) + + if (birthday == null || birthday.isEmpty()) { + return null; // 생일 정보가 없는 경우 + } + + int year = (birthyear != null && !birthyear.isEmpty()) + ? Integer.parseInt(birthyear) + : LocalDate.now().getYear(); + + int month = Integer.parseInt(birthday.substring(0, 2)); + int day = Integer.parseInt(birthday.substring(2, 4)); + + return LocalDate.of(year, month, day); + } + return null; + } + + private Member createNewMember(String email, String name, String phoneNumber, LocalDate birthDate) { + Member member = Member.builder() + .email(email) + .name(name != null ? name : "Default User") // 이름이 없는 경우 기본값 설정 + .phoneNumber(phoneNumber != null ? phoneNumber : "Unknown") // 핸드폰 번호 기본값 설정 + .birthDate(birthDate != null ? birthDate : LocalDate.now()) // 생일이 없는 경우 현재 날짜로 설정 + .role(Role.ROLE_USER) // 기본 권한 설정 + .build(); + + memberRepository.save(member); + log.info("New member created and saved in DB. Email: {}, Name: {}, Phone: {}, BirthDate: {}", + email, name, phoneNumber, birthDate); + return member; + } +} diff --git a/src/main/java/umc/kkijuk/server/auth/service/KakaoAuthService.java b/src/main/java/umc/kkijuk/server/auth/service/KakaoAuthService.java new file mode 100644 index 00000000..190b308c --- /dev/null +++ b/src/main/java/umc/kkijuk/server/auth/service/KakaoAuthService.java @@ -0,0 +1,79 @@ +package umc.kkijuk.server.auth.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDate; +import java.util.Map; + +@Service +@RequiredArgsConstructor +@Slf4j +public class KakaoAuthService { + + private final RestTemplate restTemplate; + + public Map getKakaoUserInfo(String accessToken) { + String userInfoUri = "https://kapi.kakao.com/v2/user/me"; + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + + HttpEntity request = new HttpEntity<>(headers); + + try { + log.info("카카오 사용자 정보 요청: URL={}, Headers={}", userInfoUri, headers); + ResponseEntity response = restTemplate.exchange(userInfoUri, HttpMethod.GET, request, Map.class); + log.info("카카오 사용자 정보 응답: {}", response.getBody()); + return response.getBody(); + } catch (Exception e) { + log.error("카카오 사용자 정보 요청 실패: {}", e.getMessage()); + throw new RuntimeException("카카오 사용자 정보 요청 실패", e); + } + } + + public String extractEmail(Map kakaoUserInfo) { + Map kakaoAccount = (Map) kakaoUserInfo.get("kakao_account"); + return kakaoAccount != null ? (String) kakaoAccount.get("email") : null; + } + + public String extractName(Map kakaoUserInfo) { + Map kakaoAccount = (Map) ((Map) kakaoUserInfo.get("kakao_account")).get("profile"); + return kakaoAccount != null ? (String) kakaoAccount.get("name") : null; + } + + public String extractPhoneNumber(Map kakaoUserInfo) { + Map kakaoAccount = (Map) kakaoUserInfo.get("kakao_account"); + return kakaoAccount != null ? (String) kakaoAccount.get("phone_number") : null; + } + + public LocalDate extractBirthDate(Map kakaoUserInfo) { + Map kakaoAccount = (Map) kakaoUserInfo.get("kakao_account"); + if (kakaoAccount == null) return null; + + String birthday = (String) kakaoAccount.get("birthday"); // MMDD 형식 + String birthyear = (String) kakaoAccount.get("birthyear"); // YYYY 형식 (선택적) + + if (birthday == null || birthday.isEmpty()) { + return null; // 생일 정보가 없는 경우 + } + + // 출생 연도가 없는 경우 현재 연도로 설정 + int year = (birthyear != null && !birthyear.isEmpty()) + ? Integer.parseInt(birthyear) // 출생 연도 사용 + : LocalDate.now().getYear(); // 기본적으로 현재 연도 사용 + + // MMDD → 월, 일 추출 + int month = Integer.parseInt(birthday.substring(0, 2)); + int day = Integer.parseInt(birthday.substring(2, 4)); + + return LocalDate.of(year, month, day); // LocalDate로 변환 + } + +} diff --git a/src/main/java/umc/kkijuk/server/common/template/ResponseTemplate.java b/src/main/java/umc/kkijuk/server/common/template/ResponseTemplate.java new file mode 100644 index 00000000..fad0ee3b --- /dev/null +++ b/src/main/java/umc/kkijuk/server/common/template/ResponseTemplate.java @@ -0,0 +1,22 @@ +package umc.kkijuk.server.common.template; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class ResponseTemplate { + int statusCode; + String message; + T data; + + public ResponseTemplate(HttpStatus httpStatus, String message, T data) { + this.statusCode = httpStatus.value(); + this.message = message; + this.data = data; + } + + public ResponseTemplate(HttpStatus httpStatus, String message) { + this.statusCode = httpStatus.value(); + this.message = message; + } +} diff --git a/src/main/java/umc/kkijuk/server/config/RestTemplateConfig.java b/src/main/java/umc/kkijuk/server/config/RestTemplateConfig.java new file mode 100644 index 00000000..8afcf137 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/config/RestTemplateConfig.java @@ -0,0 +1,13 @@ +package umc.kkijuk.server.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/umc/kkijuk/server/config/SecurityConfig.java b/src/main/java/umc/kkijuk/server/config/SecurityConfig.java new file mode 100644 index 00000000..0f1c936f --- /dev/null +++ b/src/main/java/umc/kkijuk/server/config/SecurityConfig.java @@ -0,0 +1,67 @@ +package umc.kkijuk.server.config; + + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import umc.kkijuk.server.auth.handler.CustomSuccessHandler; +import umc.kkijuk.server.auth.jwt.JwtCustomFilter; +import umc.kkijuk.server.auth.jwt.JwtFilter; +import umc.kkijuk.server.auth.service.CustomOAuth2UserService; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final CustomOAuth2UserService customOAuth2UserService; + private final CustomSuccessHandler customSuccessHandler; + private final JwtFilter jwtFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf(csrf -> csrf.disable()) + .sessionManagement( + session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests( + requests -> + requests + .requestMatchers( + "/", + "/swagger-ui/**", + "/v3/api-docs/**", + "/health-check", + "/api/users/**", + "/api/users/home", + "/oauth2/**", + "/login/**", + "/api/auth/**") + .permitAll() + .anyRequest() + .authenticated()) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .addFilterAfter(new JwtCustomFilter(jwtFilter), OAuth2LoginAuthenticationFilter.class) + .oauth2Login( + oauth2 -> + oauth2 + .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) + .successHandler(customSuccessHandler)); + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/swagger/Config.java b/src/main/java/umc/kkijuk/server/config/SwaggerConfig.java similarity index 91% rename from src/main/java/umc/kkijuk/server/swagger/Config.java rename to src/main/java/umc/kkijuk/server/config/SwaggerConfig.java index 25bec3d6..a7649a31 100644 --- a/src/main/java/umc/kkijuk/server/swagger/Config.java +++ b/src/main/java/umc/kkijuk/server/config/SwaggerConfig.java @@ -1,4 +1,4 @@ -package umc.kkijuk.server.swagger; +package umc.kkijuk.server.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; @@ -8,7 +8,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -public class Config { +public class SwaggerConfig { @Value("${api.server.url}") diff --git a/src/main/java/umc/kkijuk/server/member/controller/EmailAuthController.java b/src/main/java/umc/kkijuk/server/member/controller/EmailAuthController.java index 1b9e00c2..dea52ab1 100644 --- a/src/main/java/umc/kkijuk/server/member/controller/EmailAuthController.java +++ b/src/main/java/umc/kkijuk/server/member/controller/EmailAuthController.java @@ -48,14 +48,14 @@ public ResponseEntity confirmMailNumber(@RequestBody @Valid MailCertifi return ResponseEntity.ok(mailService.verifyMail(mailCertificationDto)); } - @Operation( - summary = "회원 비밀번호 재설정", - description = "회원의 비밀번호를 새로운 값으로 재설정합니다.") - @PostMapping("/password/reset") - public ResponseEntity resetMemberPassword(@RequestBody @Valid MemberPasswordResetDto memberPasswordResetDto){ - - Member member = memberService.resetMemberPassword(memberPasswordResetDto); - return ResponseEntity.ok(Boolean.TRUE); - } +// @Operation( +// summary = "회원 비밀번호 재설정", +// description = "회원의 비밀번호를 새로운 값으로 재설정합니다.") +// @PostMapping("/password/reset") +// public ResponseEntity resetMemberPassword(@RequestBody @Valid MemberPasswordResetDto memberPasswordResetDto){ +// +// Member member = memberService.resetMemberPassword(memberPasswordResetDto); +// return ResponseEntity.ok(Boolean.TRUE); +// } } diff --git a/src/main/java/umc/kkijuk/server/member/controller/MemberController.java b/src/main/java/umc/kkijuk/server/member/controller/MemberController.java index d64e8e5f..aa720347 100644 --- a/src/main/java/umc/kkijuk/server/member/controller/MemberController.java +++ b/src/main/java/umc/kkijuk/server/member/controller/MemberController.java @@ -98,15 +98,15 @@ public ResponseEntity postField(@RequestBody MemberFieldDto memberField return ResponseEntity.ok(Boolean.TRUE); } - @Operation( - summary = "비밀번호 변경", - description = "비밀번호를 변경합니다.") - @PostMapping("myPage/password") - public ResponseEntity changeMemberPassword(@RequestBody @Valid MemberPasswordChangeDto memberPasswordChangeDto){ - Long loginUser = LoginUser.get().getId(); - memberService.changeMemberPassword(loginUser, memberPasswordChangeDto); - return ResponseEntity.ok(Boolean.TRUE); - } +// @Operation( +// summary = "비밀번호 변경", +// description = "비밀번호를 변경합니다.") +// @PostMapping("myPage/password") +// public ResponseEntity changeMemberPassword(@RequestBody @Valid MemberPasswordChangeDto memberPasswordChangeDto){ +// Long loginUser = LoginUser.get().getId(); +// memberService.changeMemberPassword(loginUser, memberPasswordChangeDto); +// return ResponseEntity.ok(Boolean.TRUE); +// } @Operation( summary = "내정보 조회 인증 화면 이메일 가져오기", @@ -121,15 +121,15 @@ public ResponseEntity getEmail() { } - @Operation( - summary = "내정보 조회용 비밀번호 인증", - description = "내 정보를 조회하기 위해 비밀번호를 인증합니다.") - @PostMapping("/myPage") - public ResponseEntity myPagePasswordAuth(@RequestBody @Valid MyPagePasswordAuthDto myPagePasswordAuthDto){ - Long loginUser = LoginUser.get().getId(); - memberService.myPagePasswordAuth(loginUser, myPagePasswordAuthDto); - return ResponseEntity.ok(Boolean.TRUE); - } +// @Operation( +// summary = "내정보 조회용 비밀번호 인증", +// description = "내 정보를 조회하기 위해 비밀번호를 인증합니다.") +// @PostMapping("/myPage") +// public ResponseEntity myPagePasswordAuth(@RequestBody @Valid MyPagePasswordAuthDto myPagePasswordAuthDto){ +// Long loginUser = LoginUser.get().getId(); +// memberService.myPagePasswordAuth(loginUser, myPagePasswordAuthDto); +// return ResponseEntity.ok(Boolean.TRUE); +// } @Operation( summary = "회원 탈퇴", diff --git a/src/main/java/umc/kkijuk/server/member/domain/Member.java b/src/main/java/umc/kkijuk/server/member/domain/Member.java index 2fd10a3e..1b7f627d 100644 --- a/src/main/java/umc/kkijuk/server/member/domain/Member.java +++ b/src/main/java/umc/kkijuk/server/member/domain/Member.java @@ -34,9 +34,6 @@ public class Member extends BaseEntity { @NotNull private LocalDate birthDate; - @NotNull - private String password; - @Convert(converter = StringListToStringConverter.class) private List field; @@ -53,13 +50,18 @@ public class Member extends BaseEntity { @Convert(converter = StringListToStringConverter.class) private List recruitTags; + @Enumerated(EnumType.STRING) + private MemberJob memberJob; - public Member(String email, String name, String phoneNumber, LocalDate birthDate, String password, MarketingAgree marketingAgree, State userState) { + @NotNull + @Enumerated(EnumType.STRING) + private Role role; + + public Member(String email, String name, String phoneNumber, LocalDate birthDate, MarketingAgree marketingAgree, State userState) { this.email = email; this.name = name; this.phoneNumber = phoneNumber; this.birthDate = birthDate; - this.password = password; this.marketingAgree = marketingAgree; this.userState = userState; } @@ -74,10 +76,6 @@ public void changeMemberInfo(String phoneNumber, LocalDate birthDate, MarketingA this.marketingAgree = marketingAgree; } - public void changeMemberPassword(String password){ - this.password = password; - } - public void inactivate() { this.userState = State.INACTIVATE; this.deleteDate = LocalDate.now().plusWeeks(1); @@ -95,4 +93,5 @@ public void addRecruitTag(String tag){ public void deleteRecruitTag(String tag) { this.recruitTags.remove(tag); } + } diff --git a/src/main/java/umc/kkijuk/server/member/domain/MemberJob.java b/src/main/java/umc/kkijuk/server/member/domain/MemberJob.java new file mode 100644 index 00000000..3efb9d1a --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/domain/MemberJob.java @@ -0,0 +1,12 @@ +package umc.kkijuk.server.member.domain; + +public enum MemberJob { + MIDDLE_OR_HIGH_SCHOOL, // 중/고등학생 + JOB_SEEKER, // 취준생 + UNIVERSITY_STUDENT, // 대학 재/휴학생 + UNIVERSITY_GRADUATE, // 대학 졸업(유예)생 + EMPLOYEE, // 직장인 + FREELANCER, // 프리랜서 + ENTREPRENEUR, // 창업/사업 중 + OTHER // 기타 +} diff --git a/src/main/java/umc/kkijuk/server/member/domain/Role.java b/src/main/java/umc/kkijuk/server/member/domain/Role.java new file mode 100644 index 00000000..10db9803 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/domain/Role.java @@ -0,0 +1,6 @@ +package umc.kkijuk.server.member.domain; + +public enum Role { + ROLE_USER, // 일반 사용자 + ROLE_ADMIN // 관리자 +} diff --git a/src/main/java/umc/kkijuk/server/member/dto/MemberJoinDto.java b/src/main/java/umc/kkijuk/server/member/dto/MemberJoinDto.java index 59d415a2..2805c5ec 100644 --- a/src/main/java/umc/kkijuk/server/member/dto/MemberJoinDto.java +++ b/src/main/java/umc/kkijuk/server/member/dto/MemberJoinDto.java @@ -1,77 +1,77 @@ -package umc.kkijuk.server.member.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import umc.kkijuk.server.member.domain.MarketingAgree; -import umc.kkijuk.server.member.domain.Member; -import umc.kkijuk.server.member.domain.State; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - -@Data -@NoArgsConstructor -public class MemberJoinDto { - - @NotNull @Email - @Schema(description = "이메일", example = "test@gmail.com", type = "string") - private String email; - @NotNull - private String name; - @NotNull - private String phoneNumber; - @NotNull - private LocalDate birthDate; - @NotNull - private String password; - @NotNull - private String passwordConfirm; - @NotNull - private MarketingAgree marketingAgree; - @NotNull - private State userState; - +//package umc.kkijuk.server.member.dto; +// +//import io.swagger.v3.oas.annotations.media.Schema; +//import jakarta.validation.constraints.Email; +//import jakarta.validation.constraints.NotNull; +//import lombok.Builder; +//import lombok.Data; +//import lombok.NoArgsConstructor; +//import umc.kkijuk.server.member.domain.MarketingAgree; +//import umc.kkijuk.server.member.domain.Member; +//import umc.kkijuk.server.member.domain.State; +// +//import java.time.LocalDate; +//import java.util.ArrayList; +//import java.util.List; +// +//@Data +//@NoArgsConstructor +//public class MemberJoinDto { +// +// @NotNull @Email +// @Schema(description = "이메일", example = "test@gmail.com", type = "string") +// private String email; +// @NotNull +// private String name; +// @NotNull +// private String phoneNumber; +// @NotNull +// private LocalDate birthDate; +// @NotNull +// private String password; +// @NotNull +// private String passwordConfirm; +// @NotNull +// private MarketingAgree marketingAgree; +// @NotNull +// private State userState; +// +//// @Builder +//// public MemberJoinDto(String email, String name, String phoneNumber, LocalDate birthDate, +//// String password, Boolean marketingAgree, State userState) { +//// this.email = email; +//// this.name = name; +//// this.phoneNumber = phoneNumber; +//// this.birthDate = birthDate; +//// this.password = password; +//// this.marketingAgree = marketingAgree; +//// this.userState = userState; +//// } +// // @Builder // public MemberJoinDto(String email, String name, String phoneNumber, LocalDate birthDate, -// String password, Boolean marketingAgree, State userState) { +// String password, String passwordConfirm, MarketingAgree marketingAgree, State userState) { // this.email = email; // this.name = name; // this.phoneNumber = phoneNumber; // this.birthDate = birthDate; // this.password = password; +// this.passwordConfirm = passwordConfirm; // this.marketingAgree = marketingAgree; // this.userState = userState; // } - - @Builder - public MemberJoinDto(String email, String name, String phoneNumber, LocalDate birthDate, - String password, String passwordConfirm, MarketingAgree marketingAgree, State userState) { - this.email = email; - this.name = name; - this.phoneNumber = phoneNumber; - this.birthDate = birthDate; - this.password = password; - this.passwordConfirm = passwordConfirm; - this.marketingAgree = marketingAgree; - this.userState = userState; - } - - public Member toEntity() { - return Member.builder() - .email(email) - .name(name) - .phoneNumber(phoneNumber) - .birthDate(birthDate) - .password(password) - .marketingAgree(marketingAgree) - .userState(userState) - .recruitTags(new ArrayList<>(List.of("인턴", "정규직", "대외활동", "동아리"))) - .build(); - } - -} +// +// public Member toEntity() { +// return Member.builder() +// .email(email) +// .name(name) +// .phoneNumber(phoneNumber) +// .birthDate(birthDate) +// .password(password) +// .marketingAgree(marketingAgree) +// .userState(userState) +// .recruitTags(new ArrayList<>(List.of("인턴", "정규직", "대외활동", "동아리"))) +// .build(); +// } +// +//} diff --git a/src/main/java/umc/kkijuk/server/member/repository/MemberJpaRepository.java b/src/main/java/umc/kkijuk/server/member/repository/MemberJpaRepository.java index 8aadf3fa..4cebde75 100644 --- a/src/main/java/umc/kkijuk/server/member/repository/MemberJpaRepository.java +++ b/src/main/java/umc/kkijuk/server/member/repository/MemberJpaRepository.java @@ -8,4 +8,5 @@ public interface MemberJpaRepository extends JpaRepository{ Optional findById(Long id); Optional findByEmail(String email); + Optional findByPhoneNumber(String phoneNumber); } diff --git a/src/main/java/umc/kkijuk/server/member/repository/MemberRepository.java b/src/main/java/umc/kkijuk/server/member/repository/MemberRepository.java index 227f3b19..66b4bd73 100644 --- a/src/main/java/umc/kkijuk/server/member/repository/MemberRepository.java +++ b/src/main/java/umc/kkijuk/server/member/repository/MemberRepository.java @@ -8,4 +8,5 @@ public interface MemberRepository { Optional findById(Long id); Optional findByEmail(String email); Member save(Member member); + Optional findByPhoneNumber(String phoneNumber); } \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/member/repository/MemberRepositoryImpl.java b/src/main/java/umc/kkijuk/server/member/repository/MemberRepositoryImpl.java index 73c3686d..2562bad1 100644 --- a/src/main/java/umc/kkijuk/server/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/umc/kkijuk/server/member/repository/MemberRepositoryImpl.java @@ -15,7 +15,6 @@ public class MemberRepositoryImpl implements MemberRepository{ public Optional findById(Long id) { return memberJpaRepository.findById(id); } - @Override public Optional findByEmail(String email) { return memberJpaRepository.findByEmail(email); @@ -25,4 +24,8 @@ public Optional findByEmail(String email) { public Member save(Member member) { return memberJpaRepository.save(member); } + @Override + public Optional findByPhoneNumber(String phoneNumber) { + return memberJpaRepository.findByPhoneNumber(phoneNumber); + } } \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/member/service/MemberService.java b/src/main/java/umc/kkijuk/server/member/service/MemberService.java index e1c6e407..ce56b76c 100644 --- a/src/main/java/umc/kkijuk/server/member/service/MemberService.java +++ b/src/main/java/umc/kkijuk/server/member/service/MemberService.java @@ -7,21 +7,24 @@ import umc.kkijuk.server.member.domain.Member; import umc.kkijuk.server.member.dto.*; +import java.time.LocalDate; import java.util.List; public interface MemberService { Member getById(Long memberId); - Member join(MemberJoinDto memberJoinDto); +// Member join(MemberJoinDto memberJoinDto); MemberInfoResponse getMemberInfo(Long memberId); List getMemberField(Long memberId); Member updateMemberField(Long memberId, MemberFieldDto memberFieldDto); Member updateMemberInfo(Long memberId, MemberInfoChangeDto memberInfoChangeDto); - Member changeMemberPassword(Long memberId, MemberPasswordChangeDto memberPasswordChangeDto); - Member myPagePasswordAuth(Long memberId, MyPagePasswordAuthDto myPagePasswordAuthDto); +// Member changeMemberPassword(Long memberId, MemberPasswordChangeDto memberPasswordChangeDto); +// Member myPagePasswordAuth(Long memberId, MyPagePasswordAuthDto myPagePasswordAuthDto); MemberEmailResponse getMemberEmail(Long memberId); MemberStateResponse changeMemberState(Long memberId); - Member resetMemberPassword(MemberPasswordResetDto memberPasswordResetDto); +// Member resetMemberPassword(MemberPasswordResetDto memberPasswordResetDto); Boolean confirmDupEmail(MemberEmailDto memberEmailDto); List addRecruitTag(Member member, String tag); List deleteRecruitTag(Member Member, String tag); + Member findByPhoneNumber(String phoneNumber); + Member createMember(String email, String name, String phoneNumber, LocalDate birthDate); } diff --git a/src/main/java/umc/kkijuk/server/member/service/MemberServiceImpl.java b/src/main/java/umc/kkijuk/server/member/service/MemberServiceImpl.java index ecb5dbb0..8bd83ee7 100644 --- a/src/main/java/umc/kkijuk/server/member/service/MemberServiceImpl.java +++ b/src/main/java/umc/kkijuk/server/member/service/MemberServiceImpl.java @@ -16,6 +16,7 @@ import umc.kkijuk.server.member.dto.*; import umc.kkijuk.server.member.repository.MemberRepository; +import java.time.LocalDate; import java.util.List; import java.util.Optional; @@ -34,26 +35,26 @@ public Member getById(Long memberId) { .orElseThrow(() -> new ResourceNotFoundException("Member", memberId)); } - @Override - @Transactional - public Member join(MemberJoinDto memberJoinDto) { - String passwordConfirm = memberJoinDto.getPasswordConfirm(); - if (!passwordConfirm.equals(memberJoinDto.getPassword())) { - throw new ConfirmPasswordMismatchException(); - } - - Member joinMember = memberJoinDto.toEntity(); - - String encodedPassword = passwordEncoder.encode(memberJoinDto.getPassword()); - joinMember.changeMemberPassword(encodedPassword); - - Optional member = memberRepository.findByEmail(memberJoinDto.getEmail()); - if (member.isPresent()){ - throw new EmailAlreadyExistsException(); - } - - return memberRepository.save(joinMember); - } +// @Override +// @Transactional +// public Member join(MemberJoinDto memberJoinDto) { +// String passwordConfirm = memberJoinDto.getPasswordConfirm(); +// if (!passwordConfirm.equals(memberJoinDto.getPassword())) { +// throw new ConfirmPasswordMismatchException(); +// } +// +// Member joinMember = memberJoinDto.toEntity(); +// +// String encodedPassword = passwordEncoder.encode(memberJoinDto.getPassword()); +// joinMember.changeMemberPassword(encodedPassword); +// +// Optional member = memberRepository.findByEmail(memberJoinDto.getEmail()); +// if (member.isPresent()){ +// throw new EmailAlreadyExistsException(); +// } +// +// return memberRepository.save(joinMember); +// } @Override public MemberInfoResponse getMemberInfo(Long memberId) { @@ -106,33 +107,33 @@ public Member updateMemberInfo(Long memberId, MemberInfoChangeDto memberInfoChan return memberRepository.save(member); } - @Override - @Transactional - public Member changeMemberPassword(Long memberId, MemberPasswordChangeDto memberPasswordChangeDto){ - Member member = this.getById(memberId); - if(!memberPasswordChangeDto.getNewPassword().equals(memberPasswordChangeDto.getNewPasswordConfirm())){ - throw new ConfirmPasswordMismatchException(); - } - if(!passwordEncoder.matches(memberPasswordChangeDto.getCurrentPassword(), member.getPassword())){ - throw new CurrentPasswordMismatchException(); - } - - String encodedPassword = passwordEncoder.encode(memberPasswordChangeDto.getNewPassword()); - member.changeMemberPassword(encodedPassword); - - return memberRepository.save(member); - } - - @Override - public Member myPagePasswordAuth(Long memberId, MyPagePasswordAuthDto myPagePasswordAuthDto) { - Member member = this.getById(memberId); - - if(!passwordEncoder.matches(myPagePasswordAuthDto.getCurrentPassword(), member.getPassword())){ - throw new CurrentPasswordMismatchException(); - } - - return member; - } +// @Override +// @Transactional +// public Member changeMemberPassword(Long memberId, MemberPasswordChangeDto memberPasswordChangeDto){ +// Member member = this.getById(memberId); +// if(!memberPasswordChangeDto.getNewPassword().equals(memberPasswordChangeDto.getNewPasswordConfirm())){ +// throw new ConfirmPasswordMismatchException(); +// } +// if(!passwordEncoder.matches(memberPasswordChangeDto.getCurrentPassword(), member.getPassword())){ +// throw new CurrentPasswordMismatchException(); +// } +// +// String encodedPassword = passwordEncoder.encode(memberPasswordChangeDto.getNewPassword()); +// member.changeMemberPassword(encodedPassword); +// +// return memberRepository.save(member); +// } + +// @Override +// public Member myPagePasswordAuth(Long memberId, MyPagePasswordAuthDto myPagePasswordAuthDto) { +// Member member = this.getById(memberId); +// +// if(!passwordEncoder.matches(myPagePasswordAuthDto.getCurrentPassword(), member.getPassword())){ +// throw new CurrentPasswordMismatchException(); +// } +// +// return member; +// } @Override @Transactional @@ -152,20 +153,20 @@ else if(member.getUserState().equals(State.ACTIVATE)){ .build(); } - @Override - @Transactional - public Member resetMemberPassword(MemberPasswordResetDto memberPasswordResetDto){ - Optional member = memberRepository.findByEmail(memberPasswordResetDto.getEmail()); - - if(!memberPasswordResetDto.getNewPassword().equals(memberPasswordResetDto.getNewPasswordConfirm())){ - throw new ConfirmPasswordMismatchException(); - } - - String encodedPassword = passwordEncoder.encode(memberPasswordResetDto.getNewPassword()); - member.get().changeMemberPassword(encodedPassword); - - return memberRepository.save(member.get()); - } +// @Override +// @Transactional +// public Member resetMemberPassword(MemberPasswordResetDto memberPasswordResetDto){ +// Optional member = memberRepository.findByEmail(memberPasswordResetDto.getEmail()); +// +// if(!memberPasswordResetDto.getNewPassword().equals(memberPasswordResetDto.getNewPasswordConfirm())){ +// throw new ConfirmPasswordMismatchException(); +// } +// +// String encodedPassword = passwordEncoder.encode(memberPasswordResetDto.getNewPassword()); +// member.get().changeMemberPassword(encodedPassword); +// +// return memberRepository.save(member.get()); +// } @Override public Boolean confirmDupEmail(MemberEmailDto memberEmailDto) { @@ -196,4 +197,31 @@ public List deleteRecruitTag(Member member, String tag) { member.deleteRecruitTag(tag); return member.getRecruitTags(); } + + /** + * 아래부터 소셜로그인 이후 추가된 기능 + */ + + @Override + @Transactional + public Member findByPhoneNumber(String phoneNumber) { + return memberRepository.findByPhoneNumber(phoneNumber).orElse(null); + } + + @Override + @Transactional + public Member createMember(String email, String name, String phoneNumber, LocalDate birthDate) { + + Member member = Member.builder() + .email(email) + .name(name) + .phoneNumber(phoneNumber) + .birthDate(birthDate) + .build(); + + memberRepository.save(member); + log.info("신규 사용자 생성: 이메일={}, 이름={}, 핸드폰 번호={}, 생년월일={}", email, name, phoneNumber, birthDate); + return member; + } + } diff --git a/src/main/java/umc/kkijuk/server/security/SecurityConfig.java b/src/main/java/umc/kkijuk/server/security/SecurityConfig.java deleted file mode 100644 index 0f03461c..00000000 --- a/src/main/java/umc/kkijuk/server/security/SecurityConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package umc.kkijuk.server.security; - - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; - -@Configuration -@EnableWebSecurity -@RequiredArgsConstructor -public class SecurityConfig { -} diff --git a/src/main/java/umc/kkijuk/server/security/jwt/TokenProvider.java b/src/main/java/umc/kkijuk/server/security/jwt/TokenProvider.java new file mode 100644 index 00000000..5b7b659c --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/TokenProvider.java @@ -0,0 +1,115 @@ +//package umc.kkijuk.server.security.jwt; +// +//import io.jsonwebtoken.*; +//import io.jsonwebtoken.io.Decoders; +//import io.jsonwebtoken.security.Keys; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +//import org.springframework.security.core.Authentication; +//import org.springframework.stereotype.Component; +//import umc.kkijuk.server.security.jwt.dto.TokenDto; +// +//import java.security.Key; +//import java.util.Collections; +//import java.util.Date; +// +//@Slf4j +//@Component +//public class TokenProvider { +// private static final String BEARER_TYPE = "Bearer"; +// +// @Value("${jwt.expiration.access}") //access token 만료 시간 +// private Long accessTokenExpiration; +// +// //refresh token 만료 시간 +// @Value("${jwt.expiration.refresh}") +// private Long refreshTokenExpiration; +// +// private final Key key; //jwt의 토큰 서명을 생성하고 검증하는데 사용 +// +// public TokenProvider(@Value("${jwt.secret}") String secretKey) { //secret key 생성 +// byte[] keyBytes = Decoders.BASE64.decode(secretKey); +// this.key = Keys.hmacShaKeyFor(keyBytes); +// } +// +// public TokenDto generateToken(Long userId) { +// String accessToken = generateAccessToken(userId); +// String refreshToken = generateRefreshToken(); +// +// return TokenDto.builder() +// .grantType(BEARER_TYPE) +// .accessToken(accessToken) +// .refreshToken(refreshToken) +// .build(); +// } +// +// //Access Token 만료 시 사용 +// public TokenDto reissue(Long userId) { +// String newAccessToken = generateAccessToken(userId); +// String newRefreshToken = generateRefreshToken(); +// +// return TokenDto.builder() +// .grantType(BEARER_TYPE) +// .accessToken(newAccessToken) +// .refreshToken(newRefreshToken) +// .build(); +// } +// +// public String generateAccessToken(Long userId) { +// Date now = new Date(); +// Date accessTokenExpireTime = new Date(now.getTime() + accessTokenExpiration); +// +// //Payload에 사용자를 찾을 수 있게 정보와 권한이 저장되어야 한다. +// return Jwts.builder() +// .setSubject(String.valueOf(userId)) +// .setExpiration(accessTokenExpireTime) +// .signWith(key, SignatureAlgorithm.HS512) +// .compact(); //컴팩트화 -> JWT를 문자열로 반환하는 역할 +// } +// +// //refresh token은 access token과 다르게 재발급을 위한 것이므로 중요 정보(claim) 없이 만료 시간만 담아도 된다. +// public String generateRefreshToken() { +// Date now = new Date(); +// Date refreshTokenExpireTime = new Date(now.getTime() + refreshTokenExpiration); +// +// return Jwts.builder() +// .setExpiration(refreshTokenExpireTime) +// .signWith(key, SignatureAlgorithm.HS512) +// .compact(); //컴팩트화 -> JWT를 문자열로 반환하는 역할 +// } +// +// public Authentication getAuthentication(String token) { +// Claims claims = Jwts.parserBuilder() +// .setSigningKey(key) +// .build() +// .parseClaimsJws(token) +// .getBody(); +// +// Long userId = Long.parseLong(claims.getSubject()); +// +// return new UsernamePasswordAuthenticationToken(userId, "", Collections.emptyList()); +// } +// +// public boolean validateToken(String token) { +// try { +// //setSigningKey() -> JWT의 서명 확인 시 필요한 key 설정 +// //parseClaimsJws() -> JWT 토큰을 분석, 확인 +// Jwts.parserBuilder() +// .setSigningKey(key) +// .build() +// .parseClaimsJws(token); +// +// return true; +// } catch (UnsupportedJwtException | MalformedJwtException exception) { +// log.info("유효하지 않은 JWT 토큰"); +// } catch (ExpiredJwtException exception) { +// log.info("만료된 JWT 토큰입니다."); +// } catch (IllegalArgumentException exception) { +// log.info("JWT 토큰 값이 들어있지 않습니다."); +// } catch (SignatureException exception) { +// log.info("JWT 토큰 서명이 유효하지 않습니다."); +// } +// return false; +// } +//} diff --git a/src/main/java/umc/kkijuk/server/security/jwt/TokenUtil.java b/src/main/java/umc/kkijuk/server/security/jwt/TokenUtil.java new file mode 100644 index 00000000..60415cce --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/TokenUtil.java @@ -0,0 +1,25 @@ +//package umc.kkijuk.server.security.jwt; +// +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import umc.kkijuk.server.security.util.CookieUtil; +// +//public class TokenUtil { +// private static final String REFRESH_TOKEN_COOKIE_NAME = "refresh_token"; +// private static final int COOKIE_EXPIRE_SECONDS = 60 * 60 * 24 * 365 * 10; // 10년 +// +// // Refresh Token을 쿠키에 저장 +// public static void saveRefreshToken(HttpServletResponse response, String refreshToken) { +// CookieUtil.addCookie(response, REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_EXPIRE_SECONDS); +// } +// +// // Refresh Token을 쿠키에서 가져오기 +// public static String getRefreshToken(HttpServletRequest request) { +// return CookieUtil.getCookieValue(request, REFRESH_TOKEN_COOKIE_NAME); +// } +// +// public static void updateRefreshTokenCookie(HttpServletRequest request, HttpServletResponse response, String newRefreshToken) { +// CookieUtil.deleteCookie(request, response, "refresh_token"); +// CookieUtil.addCookie(response, "refresh_token", newRefreshToken, COOKIE_EXPIRE_SECONDS); +// } +//} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/security/jwt/dto/TokenDto.java b/src/main/java/umc/kkijuk/server/security/jwt/dto/TokenDto.java new file mode 100644 index 00000000..54f81c09 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/dto/TokenDto.java @@ -0,0 +1,20 @@ +//package umc.kkijuk.server.security.jwt.dto; +// +//import lombok.Builder; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +// +//@Getter +//@NoArgsConstructor +//public class TokenDto { +// private String grantType; +// private String accessToken; +// private String refreshToken; +// +// @Builder +// public TokenDto(String grantType, String accessToken, String refreshToken) { +// this.grantType = grantType; +// this.accessToken = accessToken; +// this.refreshToken = refreshToken; +// } +//} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/security/jwt/filter/JwtAuthorizationFilter.java b/src/main/java/umc/kkijuk/server/security/jwt/filter/JwtAuthorizationFilter.java new file mode 100644 index 00000000..623195ec --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/filter/JwtAuthorizationFilter.java @@ -0,0 +1,49 @@ +//package umc.kkijuk.server.security.jwt.filter; +// +///* +//HTTP 요청을 중간에서 가로채서 jwt 처리, 해당 토큰으로 사용자 인증 +//jwt 토큰 추출 -> 유효성 검사 -> if 유효: 토큰으로 사용자 인증 후 SecurityContextHolder에 인증 정보 설정 +// */ +// +//import jakarta.servlet.FilterChain; +//import jakarta.servlet.ServletException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import lombok.RequiredArgsConstructor; +//import org.springframework.security.core.Authentication; +//import org.springframework.security.core.context.SecurityContextHolder; +//import org.springframework.stereotype.Component; +//import org.springframework.util.StringUtils; +//import org.springframework.web.filter.OncePerRequestFilter; +//import umc.kkijuk.server.security.jwt.TokenProvider; +// +//import java.io.IOException; +// +//@Component +//@RequiredArgsConstructor +//public class JwtAuthorizationFilter extends OncePerRequestFilter { //커스텀 필터 클래스 +// private static final String AUTHORIZATION_HEADER = "Authorization"; +// private static final String BEARER_PREFIX = "Bearer "; +// private final TokenProvider tokenProvider; +// +// @Override +// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { +// String jwt = resolveToken(request); +// +// if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { +// Authentication authentication = tokenProvider.getAuthentication(jwt); //토큰 사용하여 사용자 인증 +// SecurityContextHolder.getContext().setAuthentication(authentication); //SecurityContextHolder에 인증 정보 설정 +// } +// +// filterChain.doFilter(request, response); //이 필터의 작업이 끝난 후 다음 필터로 http 요청 전달 +// } +// +// private String resolveToken(HttpServletRequest request) { //여기서 HttpServeletRequest는 HTTP 요청 정보를 캡슐화한 객체 +// String token = request.getHeader(AUTHORIZATION_HEADER); +// +// if (StringUtils.hasText(token) && token.startsWith(BEARER_PREFIX)) { //토큰에서 추출한 헤더 값이 null이 아닌지 && "Bearer "로 시작하는지 -> "Bearer " 다음에 토큰이 오는 것이 관례 +// return token.substring(BEARER_PREFIX.length()); //"Bearer " 제외 후 실제 토큰 문자열을 반환 +// } +// return null; +// } +//} diff --git a/src/main/java/umc/kkijuk/server/security/jwt/filter/JwtExceptionFilter.java b/src/main/java/umc/kkijuk/server/security/jwt/filter/JwtExceptionFilter.java new file mode 100644 index 00000000..f399766e --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/filter/JwtExceptionFilter.java @@ -0,0 +1,47 @@ +//package umc.kkijuk.server.security.jwt.filter; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import io.jsonwebtoken.JwtException; +//import jakarta.servlet.FilterChain; +//import jakarta.servlet.ServletException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.MediaType; +//import org.springframework.stereotype.Component; +//import org.springframework.web.filter.OncePerRequestFilter; +//import umc.kkijuk.server.security.jwt.response.JwtExceptionResponse; +// +//import java.io.IOException; +//import java.time.LocalDateTime; +// +//@Component +//public class JwtExceptionFilter extends OncePerRequestFilter { +// @Override +// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { +// try { +// filterChain.doFilter(request, response); //현재 필터에서의 작업이 끝난 후 다음 필터로 HTTP 요청 전달.(JwtAuthenticationFilter로 이동) +// } catch (JwtException e) { +// setErrorResponse(HttpStatus.UNAUTHORIZED, response, e); +// } +// } +// +// private void setErrorResponse(HttpStatus status, HttpServletResponse response, Throwable throwable) throws IOException { +// response.setStatus(status.value()); +// response.setContentType(MediaType.APPLICATION_JSON_VALUE); +// response.setCharacterEncoding("UTF-8"); +// +// JwtExceptionResponse jwtExceptionResponse = new JwtExceptionResponse( +// LocalDateTime.now().toString(), +// HttpStatus.UNAUTHORIZED.value(), +// "Unauthorized", +// throwable.getMessage() +// ); +// +// ObjectMapper objectMapper = new ObjectMapper(); +// //JwtExceptionResponse 객체를 JSON 문자열로 변환 +// String jsonResponse = objectMapper.writeValueAsString(jwtExceptionResponse); +// //문자열을 HTTP 응답으로 전송 +// response.getWriter().write(jsonResponse); +// } +//} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAccessDeniedHandler.java b/src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAccessDeniedHandler.java new file mode 100644 index 00000000..4b4be0e1 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAccessDeniedHandler.java @@ -0,0 +1,19 @@ +//package umc.kkijuk.server.security.jwt.filter.handler; +// +//import jakarta.servlet.ServletException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.security.access.AccessDeniedException; +//import org.springframework.security.web.access.AccessDeniedHandler; +//import org.springframework.stereotype.Component; +// +//import java.io.IOException; +// +//@Component +//public class JwtAccessDeniedHandler implements AccessDeniedHandler { +// @Override +// public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { +// //필요한 권한 없이 접근 시 403 +// response.sendError(HttpServletResponse.SC_FORBIDDEN); +// } +//} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAuthenticationEntryPoint.java b/src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..9c96a4fb --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/filter/handler/JwtAuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +//package umc.kkijuk.server.security.jwt.filter.handler; +// +//import jakarta.servlet.ServletException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.security.core.AuthenticationException; +//import org.springframework.security.web.AuthenticationEntryPoint; +//import org.springframework.stereotype.Component; +// +//import java.io.IOException; +// +//@Component +//public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { +// @Override +// public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { +// //유효한 자격증명을 제공하지 않고 접근 시 401 +// response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); +// } +//} diff --git a/src/main/java/umc/kkijuk/server/security/jwt/response/JwtExceptionResponse.java b/src/main/java/umc/kkijuk/server/security/jwt/response/JwtExceptionResponse.java new file mode 100644 index 00000000..aa9c56c4 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/jwt/response/JwtExceptionResponse.java @@ -0,0 +1,11 @@ +//package umc.kkijuk.server.security.jwt.response; +// +//import lombok.AllArgsConstructor; +// +//@AllArgsConstructor +//public class JwtExceptionResponse { +// private String timestamp; +// private int status; +// private String error; +// private String message; +//} diff --git a/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoApiClient.java b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoApiClient.java new file mode 100644 index 00000000..3609b42f --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoApiClient.java @@ -0,0 +1,67 @@ +//package umc.kkijuk.server.security.oauth.kakao; +// +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.http.HttpEntity; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.MediaType; +//import org.springframework.stereotype.Component; +//import org.springframework.util.LinkedMultiValueMap; +//import org.springframework.util.MultiValueMap; +//import org.springframework.web.client.RestTemplate; +// +//import java.util.Objects; +// +//@Component +//@RequiredArgsConstructor +//@Slf4j +//public class KakaoApiClient { +// @Value("${kakao.rest_api.oauth.url.auth}") +// private String authUrl; +// @Value("${kakao.rest_api.oauth.url.api}") +// private String apiUrl; +// @Value("${kakao.rest_api.key}") +// private String clientId; +// @Value("${kakao.rest_api.oauth.client-secret}") +// private String clientSecret; +// +// private final RestTemplate restTemplate; +// +// public String requestAccessToken(KakaoLoginParam params) { +// String url = authUrl + "/oauth/token"; +// HttpEntity> request = generateHttpRequest(params); +// +// KakaoToken kakaoToken = restTemplate.postForObject(url, request, KakaoToken.class); +// Objects.requireNonNull(kakaoToken, "Kakao token is null"); +// +// return kakaoToken.accessToken(); +// } +// +// public KakaoUserInfo requestOAuthInfo(String accessToken) { +// String url = apiUrl + "/v2/user/me"; +// +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); +// headers.set("Authorization", "Bearer " + accessToken); +// +// MultiValueMap body = new LinkedMultiValueMap<>(); +// body.add("property_keys", "[\"kakao_account.email\"]"); +// +// HttpEntity request = new HttpEntity<>(body, headers); +// +// return restTemplate.postForObject(url, request, KakaoUserInfo.class); +// } +// +// private HttpEntity> generateHttpRequest(KakaoLoginParam params) { +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); +// +// MultiValueMap body = params.makeBody(); +// body.add("grant_type", "authorization_code"); +// body.add("client_id", clientId); +// body.add("client_secret", clientSecret); +// +// return new HttpEntity<>(body, headers); +// } +//} diff --git a/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoLoginParam.java b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoLoginParam.java new file mode 100644 index 00000000..d892c624 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoLoginParam.java @@ -0,0 +1,22 @@ +//package umc.kkijuk.server.security.oauth.kakao; +// +//import lombok.AllArgsConstructor; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +//import org.springframework.util.LinkedMultiValueMap; +//import org.springframework.util.MultiValueMap; +// +//@Getter +//@NoArgsConstructor +//@AllArgsConstructor +//public class KakaoLoginParam { +// private String authorizationCode; +// +// public MultiValueMap makeBody() { +// MultiValueMap body = new LinkedMultiValueMap<>(); +// body.add("code", authorizationCode); +// +// return body; +// } +//} +// diff --git a/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoToken.java b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoToken.java new file mode 100644 index 00000000..532d4e30 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoToken.java @@ -0,0 +1,13 @@ +//package umc.kkijuk.server.security.oauth.kakao; +// +//import com.fasterxml.jackson.annotation.JsonProperty; +// +//public record KakaoToken( +// @JsonProperty("token_type") String tokenType, +// @JsonProperty("access_token") String accessToken, +// @JsonProperty("expires_in") String expiresIn, +// @JsonProperty("refresh_token") String refreshToken, +// @JsonProperty("refresh_token_expires_in") String refreshTokenExpiresIn, +// @JsonProperty("scope") String scope +//) { +//} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoUserInfo.java b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoUserInfo.java new file mode 100644 index 00000000..8eb0b803 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/oauth/kakao/KakaoUserInfo.java @@ -0,0 +1,22 @@ +//package umc.kkijuk.server.security.oauth.kakao; +// +//import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +//import com.fasterxml.jackson.annotation.JsonProperty; +//import lombok.Getter; +// +//@Getter +//@JsonIgnoreProperties(ignoreUnknown = true) +//public class KakaoUserInfo { +// @JsonProperty("kakao_account") +// private KakaoAccount kakaoAccount; +// +// @Getter +// @JsonIgnoreProperties(ignoreUnknown = true) +// static class KakaoAccount { +// private String email; +// } +// +// public String getEmail() { +// return this.kakaoAccount.email; +// } +//} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/security/util/CookieUtil.java b/src/main/java/umc/kkijuk/server/security/util/CookieUtil.java new file mode 100644 index 00000000..07824d7e --- /dev/null +++ b/src/main/java/umc/kkijuk/server/security/util/CookieUtil.java @@ -0,0 +1,45 @@ +//package umc.kkijuk.server.security.util; +// +//import jakarta.servlet.http.Cookie; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +// +//import java.util.Arrays; +// +//public class CookieUtil { +// private static final int MAX_LIST_SIZE = 5; +// +// public static String getCookieValue(HttpServletRequest request, String cookieName) { +// Cookie cookie = getCookie(request, cookieName); +// return cookie != null ? cookie.getValue() : null; +// } +// +// public static void addCookie(HttpServletResponse response, String cookieName, String cookieValue, int expireSeconds) { +// Cookie cookie = new Cookie(cookieName, cookieValue); +// cookie.setPath("/"); +// cookie.setMaxAge(expireSeconds); +// cookie.setSecure(true); +// cookie.setHttpOnly(true); +// response.addCookie(cookie); +// } +// +// public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) { +// Cookie cookie = getCookie(request, cookieName); +// if (cookie != null) { +// cookie.setValue(""); +// cookie.setMaxAge(0); +// cookie.setPath("/"); +// response.addCookie(cookie); +// } +// } +// +// private static Cookie getCookie(HttpServletRequest request, String cookieName) { +// if (request.getCookies() != null) { +// return Arrays.stream(request.getCookies()) +// .filter(cookie -> cookie.getName().equals(cookieName)) +// .findFirst() +// .orElse(null); +// } +// return null; +// } +//} diff --git a/src/test/java/umc/kkijuk/server/member/mock/FakeMemberRepository.java b/src/test/java/umc/kkijuk/server/member/mock/FakeMemberRepository.java index 438c5c8f..704901ac 100644 --- a/src/test/java/umc/kkijuk/server/member/mock/FakeMemberRepository.java +++ b/src/test/java/umc/kkijuk/server/member/mock/FakeMemberRepository.java @@ -38,4 +38,10 @@ public Optional findByEmail(String email) { .filter(item -> item.getEmail().equals(email)) .findAny(); } + @Override + public Optional findByPhoneNumber(String phoneNumber) { + return data.stream() + .filter(item -> item.getPhoneNumber().equals(phoneNumber)) + .findAny(); + } } \ No newline at end of file diff --git a/src/test/java/umc/kkijuk/server/member/service/MailServiceTest.java b/src/test/java/umc/kkijuk/server/member/service/MailServiceTest.java index 7bd1a94e..00481c53 100644 --- a/src/test/java/umc/kkijuk/server/member/service/MailServiceTest.java +++ b/src/test/java/umc/kkijuk/server/member/service/MailServiceTest.java @@ -47,7 +47,7 @@ void init() { .name("홍길동") .phoneNumber("01012345678") .birthDate(LocalDate.of(1999, 3, 31)) - .password("testpassword") +// .password("testpassword") .marketingAgree(MarketingAgree.BOTH) .userState(State.ACTIVATE) .field(List.of("game", "computer")) diff --git a/src/test/java/umc/kkijuk/server/unitTest/member/service/MemberServiceTest.java b/src/test/java/umc/kkijuk/server/unitTest/member/service/MemberServiceTest.java index 72036ab4..a6e8f70b 100644 --- a/src/test/java/umc/kkijuk/server/unitTest/member/service/MemberServiceTest.java +++ b/src/test/java/umc/kkijuk/server/unitTest/member/service/MemberServiceTest.java @@ -1,373 +1,373 @@ -package umc.kkijuk.server.unitTest.member.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.security.crypto.password.PasswordEncoder; -import umc.kkijuk.server.common.domian.exception.*; -import umc.kkijuk.server.member.controller.response.MemberEmailResponse; -import umc.kkijuk.server.member.controller.response.MemberInfoResponse; -import umc.kkijuk.server.member.controller.response.MemberStateResponse; -import umc.kkijuk.server.member.domain.MarketingAgree; -import umc.kkijuk.server.member.domain.Member; -import umc.kkijuk.server.member.domain.State; -import umc.kkijuk.server.member.dto.*; -import umc.kkijuk.server.member.repository.MemberRepository; -import umc.kkijuk.server.member.service.MemberService; -import umc.kkijuk.server.member.service.MemberServiceImpl; -import umc.kkijuk.server.unitTest.mock.FakeMemberRepository; -import umc.kkijuk.server.unitTest.mock.FakePasswordEncoder; - -import java.time.LocalDate; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - - -public class MemberServiceTest { - private MemberService memberService; - private Member member; - - /** - * testValues - */ - private final String testMemberEmail = "test@naver.com"; - private final String testMemberName = "홍길동"; - private final String testMemberPhoneNumber = "01012345678"; - private final LocalDate testMemberBirthDate = LocalDate.of(1999, 3, 31); - private final String testMemberPassword = "test-password"; - private final MarketingAgree testMemberMarketingAgree = MarketingAgree.BOTH; - private final State testMemberState = State.ACTIVATE; - private final List testMemberField = List.of("game", "computer"); - - @BeforeEach - void init() { - member = Member.builder() - .email(testMemberEmail) - .name(testMemberName) - .phoneNumber(testMemberPhoneNumber) - .birthDate(testMemberBirthDate) - .password(testMemberPassword) - .marketingAgree(testMemberMarketingAgree) - .userState(testMemberState) - .field(testMemberField) - .build(); - - MemberRepository memberRepository = new FakeMemberRepository(); - PasswordEncoder passwordEncoder = new FakePasswordEncoder(); - - this.memberService = MemberServiceImpl.builder() - .memberRepository(memberRepository) - .passwordEncoder(passwordEncoder) - .build(); - - memberRepository.save(member); - } - - @Test - @DisplayName("[getById] 멤버 ID로 멤버 조회 - 정상 조회") - void testGetById() { - //given - //when - Member result = memberService.getById(1L); - - //then - assertAll( - () -> assertThat(result.getId()).isEqualTo(1L), - () -> assertThat(result.getEmail()).isEqualTo(testMemberEmail), - () -> assertThat(result.getName()).isEqualTo(testMemberName), - () -> assertThat(result.getPhoneNumber()).isEqualTo(testMemberPhoneNumber), - () -> assertThat(result.getPassword()).isEqualTo(testMemberPassword), - () -> assertThat(result.getBirthDate()).isEqualTo(testMemberBirthDate), - () -> assertThat(result.getMarketingAgree()).isEqualTo(testMemberMarketingAgree), - () -> assertThat(result.getUserState()).isEqualTo(testMemberState) - ); - } - - @Test - @DisplayName("[getById] 존재하지 않는 멤버 조회 시 예외 발생 - ResourceNotFoundException") - void testGetByIdDisable() { - //given - //when - //then - assertThatThrownBy( - () -> memberService.getById(100L)) - .isInstanceOf(ResourceNotFoundException.class); - } - - @Test - @DisplayName("[join] 새로운 멤버 가입 - 정상 가입") - void testJoin() { - //given - MemberJoinDto joinMember = MemberJoinDto.builder() - .email("new@naver.com") - .name("newMember") - .phoneNumber("01011112222") - .birthDate(LocalDate.of(2002, 1,1)) - .password("abcd!@") - .passwordConfirm("abcd!@") - .marketingAgree(MarketingAgree.SMS) - .userState(State.ACTIVATE) - .build(); - //when - Member newMember = memberService.join(joinMember); - //then - assertAll( - () -> assertThat(newMember.getId()).isEqualTo(2L), - () -> assertThat(newMember.getEmail()).isEqualTo("new@naver.com"), - () -> assertThat(newMember.getName()).isEqualTo("newMember"), - () -> assertThat(newMember.getPhoneNumber()).isEqualTo("01011112222"), - () -> assertThat(newMember.getPassword()).isEqualTo("abcd!@"), - () -> assertThat(newMember.getBirthDate()).isEqualTo(LocalDate.of(2002, 1,1)), - () -> assertThat(newMember.getMarketingAgree()).isEqualTo(MarketingAgree.SMS), - () -> assertThat(newMember.getUserState()).isEqualTo(State.ACTIVATE) - ); - - } - - - - @Test - @DisplayName("[join] 비밀번호 불일치로 가입 시 예외 발생 - ConfirmPasswordMismatchException") - void testJoinDisable() { - //given - MemberJoinDto joinMember = MemberJoinDto.builder() - .email(testMemberEmail) - .name("newMember") - .phoneNumber("01011112222") - .birthDate(LocalDate.of(2002, 1,1)) - .password("abcd!@") - .passwordConfirm("aaaa") - .marketingAgree(MarketingAgree.SMS) - .userState(State.ACTIVATE) - .build(); - //when - - //then - assertThatThrownBy( - () -> memberService.join(joinMember)) - .isInstanceOf(ConfirmPasswordMismatchException.class); - - } - - @Test - @DisplayName("[getMemberInfo] 멤버 정보 조회 - 정상 조회") - void testGetMemberInfo() { - // 여기에 테스트 로직 추가 - //given - //when - MemberInfoResponse result = memberService.getMemberInfo(1L); - //then - assertAll( - () -> assertThat(result.getEmail()).isEqualTo(testMemberEmail), - () -> assertThat(result.getName()).isEqualTo(testMemberName), - () -> assertThat(result.getPhoneNumber()).isEqualTo(testMemberPhoneNumber), - () -> assertThat(result.getBirthDate()).isEqualTo(testMemberBirthDate) - ); - - } - - @Test - @DisplayName("[getMemberField] 멤버 필드 정보 조회 - 정상 조회") - void testGetMemberField() { - //given - //when - List result = memberService.getMemberField(1L); - //then - assertThat(result).isEqualTo(testMemberField); - } - - @Test - @DisplayName("[getMemberEmail] 멤버 이메일 조회 - 정상 조회") - void testGetMemberEmail() { - //given - //when - MemberEmailResponse result = memberService.getMemberEmail(1L); - //then - assertThat(result.getEmail()).isEqualTo(testMemberEmail); - } - - @Test - @DisplayName("[updateMemberField] 멤버 필드 정보 업데이트 - 정상 업데이트") - void testUpdateMemberField() { - //given - List newField = List.of("test1", "test2", "test3"); - MemberFieldDto memberFieldDto = MemberFieldDto.builder().field(newField).build(); - //when - Member result = memberService.updateMemberField(1L, memberFieldDto); - - //then - assertThat(result.getField()).isEqualTo(newField); - } - - +//package umc.kkijuk.server.unitTest.member.service; +// +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Test; +//import org.springframework.security.crypto.password.PasswordEncoder; +//import umc.kkijuk.server.common.domian.exception.*; +//import umc.kkijuk.server.member.controller.response.MemberEmailResponse; +//import umc.kkijuk.server.member.controller.response.MemberInfoResponse; +//import umc.kkijuk.server.member.controller.response.MemberStateResponse; +//import umc.kkijuk.server.member.domain.MarketingAgree; +//import umc.kkijuk.server.member.domain.Member; +//import umc.kkijuk.server.member.domain.State; +//import umc.kkijuk.server.member.dto.*; +//import umc.kkijuk.server.member.repository.MemberRepository; +//import umc.kkijuk.server.member.service.MemberService; +//import umc.kkijuk.server.member.service.MemberServiceImpl; +//import umc.kkijuk.server.unitTest.mock.FakeMemberRepository; +//import umc.kkijuk.server.unitTest.mock.FakePasswordEncoder; +// +//import java.time.LocalDate; +//import java.util.List; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.assertj.core.api.Assertions.assertThatThrownBy; +//import static org.junit.jupiter.api.Assertions.assertAll; +// +// +//public class MemberServiceTest { +// private MemberService memberService; +// private Member member; +// +// /** +// * testValues +// */ +// private final String testMemberEmail = "test@naver.com"; +// private final String testMemberName = "홍길동"; +// private final String testMemberPhoneNumber = "01012345678"; +// private final LocalDate testMemberBirthDate = LocalDate.of(1999, 3, 31); +// private final String testMemberPassword = "test-password"; +// private final MarketingAgree testMemberMarketingAgree = MarketingAgree.BOTH; +// private final State testMemberState = State.ACTIVATE; +// private final List testMemberField = List.of("game", "computer"); +// +// @BeforeEach +// void init() { +// member = Member.builder() +// .email(testMemberEmail) +// .name(testMemberName) +// .phoneNumber(testMemberPhoneNumber) +// .birthDate(testMemberBirthDate) +// .password(testMemberPassword) +// .marketingAgree(testMemberMarketingAgree) +// .userState(testMemberState) +// .field(testMemberField) +// .build(); +// +// MemberRepository memberRepository = new FakeMemberRepository(); +// PasswordEncoder passwordEncoder = new FakePasswordEncoder(); +// +// this.memberService = MemberServiceImpl.builder() +// .memberRepository(memberRepository) +// .passwordEncoder(passwordEncoder) +// .build(); +// +// memberRepository.save(member); +// } +// +// @Test +// @DisplayName("[getById] 멤버 ID로 멤버 조회 - 정상 조회") +// void testGetById() { +// //given +// //when +// Member result = memberService.getById(1L); +// +// //then +// assertAll( +// () -> assertThat(result.getId()).isEqualTo(1L), +// () -> assertThat(result.getEmail()).isEqualTo(testMemberEmail), +// () -> assertThat(result.getName()).isEqualTo(testMemberName), +// () -> assertThat(result.getPhoneNumber()).isEqualTo(testMemberPhoneNumber), +// () -> assertThat(result.getPassword()).isEqualTo(testMemberPassword), +// () -> assertThat(result.getBirthDate()).isEqualTo(testMemberBirthDate), +// () -> assertThat(result.getMarketingAgree()).isEqualTo(testMemberMarketingAgree), +// () -> assertThat(result.getUserState()).isEqualTo(testMemberState) +// ); +// } +// +// @Test +// @DisplayName("[getById] 존재하지 않는 멤버 조회 시 예외 발생 - ResourceNotFoundException") +// void testGetByIdDisable() { +// //given +// //when +// //then +// assertThatThrownBy( +// () -> memberService.getById(100L)) +// .isInstanceOf(ResourceNotFoundException.class); +// } +// +// @Test +// @DisplayName("[join] 새로운 멤버 가입 - 정상 가입") +// void testJoin() { +// //given +// MemberJoinDto joinMember = MemberJoinDto.builder() +// .email("new@naver.com") +// .name("newMember") +// .phoneNumber("01011112222") +// .birthDate(LocalDate.of(2002, 1,1)) +// .password("abcd!@") +// .passwordConfirm("abcd!@") +// .marketingAgree(MarketingAgree.SMS) +// .userState(State.ACTIVATE) +// .build(); +// //when +// Member newMember = memberService.join(joinMember); +// //then +// assertAll( +// () -> assertThat(newMember.getId()).isEqualTo(2L), +// () -> assertThat(newMember.getEmail()).isEqualTo("new@naver.com"), +// () -> assertThat(newMember.getName()).isEqualTo("newMember"), +// () -> assertThat(newMember.getPhoneNumber()).isEqualTo("01011112222"), +// () -> assertThat(newMember.getPassword()).isEqualTo("abcd!@"), +// () -> assertThat(newMember.getBirthDate()).isEqualTo(LocalDate.of(2002, 1,1)), +// () -> assertThat(newMember.getMarketingAgree()).isEqualTo(MarketingAgree.SMS), +// () -> assertThat(newMember.getUserState()).isEqualTo(State.ACTIVATE) +// ); +// +// } +// +// +// // @Test -// @DisplayName("[updateMemberField] 기존의 필드 정보로 업데이트 시 예외 발생 - FieldUpdateException") -// void testUpdateMemberFieldDisable() { +// @DisplayName("[join] 비밀번호 불일치로 가입 시 예외 발생 - ConfirmPasswordMismatchException") +// void testJoinDisable() { // //given -// List newField = List.of("game", "computer"); +// MemberJoinDto joinMember = MemberJoinDto.builder() +// .email(testMemberEmail) +// .name("newMember") +// .phoneNumber("01011112222") +// .birthDate(LocalDate.of(2002, 1,1)) +// .password("abcd!@") +// .passwordConfirm("aaaa") +// .marketingAgree(MarketingAgree.SMS) +// .userState(State.ACTIVATE) +// .build(); +// //when +// +// //then +// assertThatThrownBy( +// () -> memberService.join(joinMember)) +// .isInstanceOf(ConfirmPasswordMismatchException.class); +// +// } +// +// @Test +// @DisplayName("[getMemberInfo] 멤버 정보 조회 - 정상 조회") +// void testGetMemberInfo() { +// // 여기에 테스트 로직 추가 +// //given +// //when +// MemberInfoResponse result = memberService.getMemberInfo(1L); +// //then +// assertAll( +// () -> assertThat(result.getEmail()).isEqualTo(testMemberEmail), +// () -> assertThat(result.getName()).isEqualTo(testMemberName), +// () -> assertThat(result.getPhoneNumber()).isEqualTo(testMemberPhoneNumber), +// () -> assertThat(result.getBirthDate()).isEqualTo(testMemberBirthDate) +// ); +// +// } +// +// @Test +// @DisplayName("[getMemberField] 멤버 필드 정보 조회 - 정상 조회") +// void testGetMemberField() { +// //given +// //when +// List result = memberService.getMemberField(1L); +// //then +// assertThat(result).isEqualTo(testMemberField); +// } +// +// @Test +// @DisplayName("[getMemberEmail] 멤버 이메일 조회 - 정상 조회") +// void testGetMemberEmail() { +// //given +// //when +// MemberEmailResponse result = memberService.getMemberEmail(1L); +// //then +// assertThat(result.getEmail()).isEqualTo(testMemberEmail); +// } +// +// @Test +// @DisplayName("[updateMemberField] 멤버 필드 정보 업데이트 - 정상 업데이트") +// void testUpdateMemberField() { +// //given +// List newField = List.of("test1", "test2", "test3"); // MemberFieldDto memberFieldDto = MemberFieldDto.builder().field(newField).build(); // //when +// Member result = memberService.updateMemberField(1L, memberFieldDto); +// +// //then +// assertThat(result.getField()).isEqualTo(newField); +// } +// +// +//// @Test +//// @DisplayName("[updateMemberField] 기존의 필드 정보로 업데이트 시 예외 발생 - FieldUpdateException") +//// void testUpdateMemberFieldDisable() { +//// //given +//// List newField = List.of("game", "computer"); +//// MemberFieldDto memberFieldDto = MemberFieldDto.builder().field(newField).build(); +//// //when +//// //then +//// assertThatThrownBy( +//// () -> memberService.updateMemberField(1L, memberFieldDto)) +//// .isInstanceOf(FieldUpdateException.class); +//// } +// +// @Test +// @DisplayName("[updateMemberInfo] 멤버 정보 업데이트 - 정상 업데이트") +// void testUpdateMemberInfo() { +// //given +// MemberInfoChangeDto memberInfoChangeDto = MemberInfoChangeDto.builder() +// .phoneNumber("01099998888") +// .birthDate(LocalDate.of(2022, 01, 01)) +// .marketingAgree(MarketingAgree.NONE) +// .build(); +// //when +// Member result = memberService.updateMemberInfo(1L, memberInfoChangeDto); +// //then +// assertAll( +// () -> assertThat(result.getPhoneNumber()).isEqualTo("01099998888"), +// () -> assertThat(result.getBirthDate()).isEqualTo(LocalDate.of(2022, 01, 01)), +// () -> assertThat(result.getMarketingAgree()).isEqualTo(MarketingAgree.NONE) +// ); +// } +// +// +// @Test +// @DisplayName("[changeMemberPassword] 비밀번호 변경 - 정상 변경") +// void testChangeMemberPassword() { +// //given +// String currentPW = testMemberPassword; +// String newPW = "1111"; +// String newPWConfirm = "1111"; +// MemberPasswordChangeDto memberPasswordChangeDto = MemberPasswordChangeDto.builder() +// .currentPassword(currentPW) +// .newPassword(newPW) +// .newPasswordConfirm(newPWConfirm) +// .build(); +// //when +// Member result = memberService.changeMemberPassword(1L, memberPasswordChangeDto); +// +// //then +// assertThat(result.getPassword()).isEqualTo(newPW); +// } +// +// @Test +// @DisplayName("[changeMemberPassword] 현재 비밀번호 불일치 시 예외 발생 - CurrentPasswordMismatchException") +// void testChangeMemberPasswordDisable1() { +// //given +// String currentPW = "CurrentPasswordMismatchTest"; +// String newPW = "1111"; +// String newPWConfirm = "1111"; +// MemberPasswordChangeDto memberPasswordChangeDto = MemberPasswordChangeDto.builder() +// .currentPassword(currentPW) +// .newPassword(newPW) +// .newPasswordConfirm(newPWConfirm) +// .build(); +// +// //when +// //then +// assertThatThrownBy( +// () -> memberService.changeMemberPassword(1L, memberPasswordChangeDto)) +// .isInstanceOf(CurrentPasswordMismatchException.class); +// } +// +// @Test +// @DisplayName("[changeMemberPassword] 새 비밀번호와 확인 비밀번호 불일치 시 예외 발생 - ConfirmPasswordMismatchException") +// void testChangeMemberPasswordDisable2() { +// //given +// String currentPW = testMemberPassword; +// String newPW = "1111"; +// String newPWConfirm = "2222"; +// MemberPasswordChangeDto memberPasswordChangeDto = MemberPasswordChangeDto.builder() +// .currentPassword(currentPW) +// .newPassword(newPW) +// .newPasswordConfirm(newPWConfirm) +// .build(); +// //when +// //then +// assertThatThrownBy( +// () -> memberService.changeMemberPassword(1L, memberPasswordChangeDto)) +// .isInstanceOf(ConfirmPasswordMismatchException.class); +// } +// +// @Test +// @DisplayName("[myPagePasswordAuth] 마이페이지 비밀번호 인증 - 정상 인증") +// void testMyPagePasswordAuth() { +// //given +// String currentPW = testMemberPassword; +// MyPagePasswordAuthDto myPagePasswordAuthDto = MyPagePasswordAuthDto.builder() +// .currentPassword(currentPW) +// .build(); +// //when +// Member result = memberService.myPagePasswordAuth(1L, myPagePasswordAuthDto); +// //then +// assertThat(currentPW).isEqualTo(result.getPassword()); +// } +// +// @Test +// @DisplayName("[myPagePasswordAuth] 비밀번호 인증 실패 시 예외 발생 - CurrentPasswordMismatchException") +// void testMyPagePasswordAuthDisable() { +// //given +// String currentPW = "CurrentPasswordMismatchTest"; +// MyPagePasswordAuthDto myPagePasswordAuthDto = MyPagePasswordAuthDto.builder() +// .currentPassword(currentPW) +// .build(); +// //when // //then // assertThatThrownBy( -// () -> memberService.updateMemberField(1L, memberFieldDto)) -// .isInstanceOf(FieldUpdateException.class); +// () -> memberService.myPagePasswordAuth(1L, myPagePasswordAuthDto)) +// .isInstanceOf(CurrentPasswordMismatchException.class); +// } +// +// @Test +// @DisplayName("[changeMemberState] 멤버 상태 업데이트(활성화 <-> 비활성화) - 정상 업데이트") +// void testChangeMemberState1(){ +// //given +// //when +// MemberStateResponse memberStateResponse = memberService.changeMemberState(1L); +// +// //then +// assertThat(memberStateResponse.getMemberState()).isEqualTo(State.INACTIVATE); +// +// MemberStateResponse memberStateResponse2 = memberService.changeMemberState(1L); +// +// assertThat(memberStateResponse2.getMemberState()).isEqualTo(State.ACTIVATE); +// } +// +// @Test +// @DisplayName("[confirmDupEmail] 이메일 중복 확인, 중복된 이메일 없음 - 정상 인증") +// public void testConfirmDupEmailTrue() throws Exception { +// //given +// String newEmail = "newTestEmail@naver.com"; +// MemberEmailDto memberEmailDto = MemberEmailDto.builder() +// .email(newEmail) +// .build(); +// +// //when +// Boolean result = memberService.confirmDupEmail(memberEmailDto); +// +// //then +// assertThat(result).isEqualTo(true); +// } +// +// @Test +// @DisplayName("[confirmDupEmail] 중복 이메일 있을 시 false 리턴") +// public void testConfirmDupEmailFalse() throws Exception { +// //given +// String newEmail = testMemberEmail; +// MemberEmailDto memberEmailDto = MemberEmailDto.builder() +// .email(newEmail) +// .build(); +// +// //when +// Boolean result = memberService.confirmDupEmail(memberEmailDto); +// +// //then +// assertThat(result).isEqualTo(false); // } - - @Test - @DisplayName("[updateMemberInfo] 멤버 정보 업데이트 - 정상 업데이트") - void testUpdateMemberInfo() { - //given - MemberInfoChangeDto memberInfoChangeDto = MemberInfoChangeDto.builder() - .phoneNumber("01099998888") - .birthDate(LocalDate.of(2022, 01, 01)) - .marketingAgree(MarketingAgree.NONE) - .build(); - //when - Member result = memberService.updateMemberInfo(1L, memberInfoChangeDto); - //then - assertAll( - () -> assertThat(result.getPhoneNumber()).isEqualTo("01099998888"), - () -> assertThat(result.getBirthDate()).isEqualTo(LocalDate.of(2022, 01, 01)), - () -> assertThat(result.getMarketingAgree()).isEqualTo(MarketingAgree.NONE) - ); - } - - - @Test - @DisplayName("[changeMemberPassword] 비밀번호 변경 - 정상 변경") - void testChangeMemberPassword() { - //given - String currentPW = testMemberPassword; - String newPW = "1111"; - String newPWConfirm = "1111"; - MemberPasswordChangeDto memberPasswordChangeDto = MemberPasswordChangeDto.builder() - .currentPassword(currentPW) - .newPassword(newPW) - .newPasswordConfirm(newPWConfirm) - .build(); - //when - Member result = memberService.changeMemberPassword(1L, memberPasswordChangeDto); - - //then - assertThat(result.getPassword()).isEqualTo(newPW); - } - - @Test - @DisplayName("[changeMemberPassword] 현재 비밀번호 불일치 시 예외 발생 - CurrentPasswordMismatchException") - void testChangeMemberPasswordDisable1() { - //given - String currentPW = "CurrentPasswordMismatchTest"; - String newPW = "1111"; - String newPWConfirm = "1111"; - MemberPasswordChangeDto memberPasswordChangeDto = MemberPasswordChangeDto.builder() - .currentPassword(currentPW) - .newPassword(newPW) - .newPasswordConfirm(newPWConfirm) - .build(); - - //when - //then - assertThatThrownBy( - () -> memberService.changeMemberPassword(1L, memberPasswordChangeDto)) - .isInstanceOf(CurrentPasswordMismatchException.class); - } - - @Test - @DisplayName("[changeMemberPassword] 새 비밀번호와 확인 비밀번호 불일치 시 예외 발생 - ConfirmPasswordMismatchException") - void testChangeMemberPasswordDisable2() { - //given - String currentPW = testMemberPassword; - String newPW = "1111"; - String newPWConfirm = "2222"; - MemberPasswordChangeDto memberPasswordChangeDto = MemberPasswordChangeDto.builder() - .currentPassword(currentPW) - .newPassword(newPW) - .newPasswordConfirm(newPWConfirm) - .build(); - //when - //then - assertThatThrownBy( - () -> memberService.changeMemberPassword(1L, memberPasswordChangeDto)) - .isInstanceOf(ConfirmPasswordMismatchException.class); - } - - @Test - @DisplayName("[myPagePasswordAuth] 마이페이지 비밀번호 인증 - 정상 인증") - void testMyPagePasswordAuth() { - //given - String currentPW = testMemberPassword; - MyPagePasswordAuthDto myPagePasswordAuthDto = MyPagePasswordAuthDto.builder() - .currentPassword(currentPW) - .build(); - //when - Member result = memberService.myPagePasswordAuth(1L, myPagePasswordAuthDto); - //then - assertThat(currentPW).isEqualTo(result.getPassword()); - } - - @Test - @DisplayName("[myPagePasswordAuth] 비밀번호 인증 실패 시 예외 발생 - CurrentPasswordMismatchException") - void testMyPagePasswordAuthDisable() { - //given - String currentPW = "CurrentPasswordMismatchTest"; - MyPagePasswordAuthDto myPagePasswordAuthDto = MyPagePasswordAuthDto.builder() - .currentPassword(currentPW) - .build(); - //when - //then - assertThatThrownBy( - () -> memberService.myPagePasswordAuth(1L, myPagePasswordAuthDto)) - .isInstanceOf(CurrentPasswordMismatchException.class); - } - - @Test - @DisplayName("[changeMemberState] 멤버 상태 업데이트(활성화 <-> 비활성화) - 정상 업데이트") - void testChangeMemberState1(){ - //given - //when - MemberStateResponse memberStateResponse = memberService.changeMemberState(1L); - - //then - assertThat(memberStateResponse.getMemberState()).isEqualTo(State.INACTIVATE); - - MemberStateResponse memberStateResponse2 = memberService.changeMemberState(1L); - - assertThat(memberStateResponse2.getMemberState()).isEqualTo(State.ACTIVATE); - } - - @Test - @DisplayName("[confirmDupEmail] 이메일 중복 확인, 중복된 이메일 없음 - 정상 인증") - public void testConfirmDupEmailTrue() throws Exception { - //given - String newEmail = "newTestEmail@naver.com"; - MemberEmailDto memberEmailDto = MemberEmailDto.builder() - .email(newEmail) - .build(); - - //when - Boolean result = memberService.confirmDupEmail(memberEmailDto); - - //then - assertThat(result).isEqualTo(true); - } - - @Test - @DisplayName("[confirmDupEmail] 중복 이메일 있을 시 false 리턴") - public void testConfirmDupEmailFalse() throws Exception { - //given - String newEmail = testMemberEmail; - MemberEmailDto memberEmailDto = MemberEmailDto.builder() - .email(newEmail) - .build(); - - //when - Boolean result = memberService.confirmDupEmail(memberEmailDto); - - //then - assertThat(result).isEqualTo(false); - } - -} +// +//} diff --git a/src/test/java/umc/kkijuk/server/unitTest/mock/FakeMemberRepository.java b/src/test/java/umc/kkijuk/server/unitTest/mock/FakeMemberRepository.java index 50764bcb..9b43e37c 100644 --- a/src/test/java/umc/kkijuk/server/unitTest/mock/FakeMemberRepository.java +++ b/src/test/java/umc/kkijuk/server/unitTest/mock/FakeMemberRepository.java @@ -1,53 +1,52 @@ -package umc.kkijuk.server.unitTest.mock; - -import umc.kkijuk.server.member.domain.Member; -import umc.kkijuk.server.member.repository.MemberRepository; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicLong; - -public class FakeMemberRepository implements MemberRepository { - private final AtomicLong authGeneratedID = new AtomicLong(0); - private final List data = new ArrayList(); - - @Override - public Member save(Member member) { - if (member.getId() == null || member.getId() == 0){ - Member newMember = Member.builder() - .id(authGeneratedID.incrementAndGet()) - .email(member.getEmail()) - .name(member.getName()) - .password(member.getPassword()) - .birthDate(member.getBirthDate()) - .phoneNumber(member.getPhoneNumber()) - .field(member.getField()) - .marketingAgree(member.getMarketingAgree()) - .userState(member.getUserState()) - .deleteDate(member.getDeleteDate()) - .build(); - data.add(newMember); - return newMember; - } else { - data.removeIf(item -> Objects.equals(item.getId(), member.getId())); - data.add(member); - return member; - } - } - - @Override - public Optional findById(Long id) { - return data.stream() - .filter(item -> item.getId().equals(id)) - .findAny(); - } - - @Override - public Optional findByEmail(String email) { - return data.stream() - .filter(item -> item.getEmail().equals(email)) - .findAny(); - } -} +//package umc.kkijuk.server.unitTest.mock; +// +//import umc.kkijuk.server.member.domain.Member; +//import umc.kkijuk.server.member.repository.MemberRepository; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Objects; +//import java.util.Optional; +//import java.util.concurrent.atomic.AtomicLong; +// +//public class FakeMemberRepository implements MemberRepository { +// private final AtomicLong authGeneratedID = new AtomicLong(0); +// private final List data = new ArrayList(); +// +// @Override +// public Member save(Member member) { +// if (member.getId() == null || member.getId() == 0){ +// Member newMember = Member.builder() +// .id(authGeneratedID.incrementAndGet()) +// .email(member.getEmail()) +// .name(member.getName()) +// .birthDate(member.getBirthDate()) +// .phoneNumber(member.getPhoneNumber()) +// .field(member.getField()) +// .marketingAgree(member.getMarketingAgree()) +// .userState(member.getUserState()) +// .deleteDate(member.getDeleteDate()) +// .build(); +// data.add(newMember); +// return newMember; +// } else { +// data.removeIf(item -> Objects.equals(item.getId(), member.getId())); +// data.add(member); +// return member; +// } +// } +// +// @Override +// public Optional findById(Long id) { +// return data.stream() +// .filter(item -> item.getId().equals(id)) +// .findAny(); +// } +// +// @Override +// public Optional findByEmail(String email) { +// return data.stream() +// .filter(item -> item.getEmail().equals(email)) +// .findAny(); +// } +//} diff --git a/src/test/java/umc/kkijuk/server/unitTest/recruit/service/RecruitServiceTest.java b/src/test/java/umc/kkijuk/server/unitTest/recruit/service/RecruitServiceTest.java index b4a351e8..39362073 100644 --- a/src/test/java/umc/kkijuk/server/unitTest/recruit/service/RecruitServiceTest.java +++ b/src/test/java/umc/kkijuk/server/unitTest/recruit/service/RecruitServiceTest.java @@ -47,7 +47,7 @@ void init() { .name("test-name") .phoneNumber("test-test-test") .birthDate(LocalDate.of(2024, 7, 25)) - .password("test-password") +// .password("test-password") .userState(State.ACTIVATE) .build(); diff --git a/src/test/java/umc/kkijuk/server/unitTest/review/service/ReviewServiceTest.java b/src/test/java/umc/kkijuk/server/unitTest/review/service/ReviewServiceTest.java index ea97959d..acd0fd8f 100644 --- a/src/test/java/umc/kkijuk/server/unitTest/review/service/ReviewServiceTest.java +++ b/src/test/java/umc/kkijuk/server/unitTest/review/service/ReviewServiceTest.java @@ -38,7 +38,7 @@ void Init() { .name("test-name") .phoneNumber("test-test-test") .birthDate(LocalDate.of(2024, 7, 25)) - .password("test-password") +// .password("test-password") .userState(State.ACTIVATE) .build(); diff --git a/src/test/java/umc/kkijuk/server/unitTest/tag/service/TagServiceTest.java b/src/test/java/umc/kkijuk/server/unitTest/tag/service/TagServiceTest.java index 21ef3bd7..ffc81825 100644 --- a/src/test/java/umc/kkijuk/server/unitTest/tag/service/TagServiceTest.java +++ b/src/test/java/umc/kkijuk/server/unitTest/tag/service/TagServiceTest.java @@ -46,7 +46,7 @@ void init() { .email("test@naver.com") .phoneNumber("010-1234-5678") .birthDate(LocalDate.of(2024, 7, 31)) - .password("test") +// .password("test") .userState(State.ACTIVATE) .build(); @@ -55,7 +55,7 @@ void init() { .email("test2@naver.com") .phoneNumber("010-2345-5678") .birthDate(LocalDate.of(2024, 7, 31)) - .password("test") +// .password("test") .userState(State.ACTIVATE) .build();