diff --git a/build.gradle b/build.gradle index f4894d3..9943773 100644 --- a/build.gradle +++ b/build.gradle @@ -25,11 +25,14 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - // implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.11.5') runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/src/main/java/com/example/FixLog/config/JwtAuthenticationFilter.java b/src/main/java/com/example/FixLog/config/JwtAuthenticationFilter.java new file mode 100644 index 0000000..2c8e56e --- /dev/null +++ b/src/main/java/com/example/FixLog/config/JwtAuthenticationFilter.java @@ -0,0 +1,56 @@ +package com.example.FixLog.config; + +import com.example.FixLog.domain.member.Member; +import com.example.FixLog.exception.CustomException; +import com.example.FixLog.repository.MemberRepository; +import com.example.FixLog.util.JwtUtil; +import java.io.IOException; +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.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; +import com.example.FixLog.exception.ErrorCode; + +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final MemberRepository memberRepository; + + public JwtAuthenticationFilter(JwtUtil jwtUtil, MemberRepository memberRepository) { + this.jwtUtil = jwtUtil; + this.memberRepository = memberRepository; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String token = resolveToken(request); + + if (token != null && jwtUtil.isTokenValid(token)) { + String email = jwtUtil.getEmailFromToken(token); + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); + + Authentication auth = new UsernamePasswordAuthenticationToken(member, null, member.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(auth); + } + + filterChain.doFilter(request, response); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } +} diff --git a/src/main/java/com/example/FixLog/config/SecurityConfig.java b/src/main/java/com/example/FixLog/config/SecurityConfig.java index 225c160..65118fa 100644 --- a/src/main/java/com/example/FixLog/config/SecurityConfig.java +++ b/src/main/java/com/example/FixLog/config/SecurityConfig.java @@ -1,31 +1,58 @@ -//package com.example.FixLog.config; -// -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.http.HttpMethod; -//import org.springframework.security.config.annotation.web.builders.HttpSecurity; -//import org.springframework.security.web.SecurityFilterChain; -//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -//import org.springframework.security.crypto.password.PasswordEncoder; -// -//@Configuration -//public class SecurityConfig { -// -// @Bean -// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { -// http -// .csrf(csrf -> csrf.disable()) -// .authorizeHttpRequests(auth -> auth -// .requestMatchers(HttpMethod.POST, "/api/members/signup").permitAll() -// .requestMatchers(HttpMethod.GET, "/api/members/check-email").permitAll() -// .requestMatchers(HttpMethod.GET, "/api/members/check-nickname").permitAll() -// .anyRequest().authenticated() -// ); -// return http.build(); -// } -// -// @Bean -// public PasswordEncoder passwordEncoder() { -// return new BCryptPasswordEncoder(); -// } -//} +package com.example.FixLog.config; + +import com.example.FixLog.repository.MemberRepository; +import com.example.FixLog.util.JwtUtil; +import jakarta.servlet.Filter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtUtil jwtUtil; + private final MemberRepository memberRepository; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.POST, "/api/members/signup").permitAll() + .requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll() + .requestMatchers(HttpMethod.GET, "/api/members/check-email").permitAll() + .requestMatchers(HttpMethod.GET, "/api/members/check-nickname").permitAll() + .requestMatchers(HttpMethod.GET, "/h2-console/**").permitAll() + .anyRequest().authenticated() + ) + .headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔용 + .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public Filter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtUtil, memberRepository); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + // 인증 매니저 (선택: 로그인 시 AuthenticationManager 사용 가능) + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } +} diff --git a/src/main/java/com/example/FixLog/controller/AuthController.java b/src/main/java/com/example/FixLog/controller/AuthController.java new file mode 100644 index 0000000..1a3f722 --- /dev/null +++ b/src/main/java/com/example/FixLog/controller/AuthController.java @@ -0,0 +1,23 @@ +package com.example.FixLog.controller; + +import com.example.FixLog.dto.Response; +import com.example.FixLog.dto.member.LoginRequestDto; +import com.example.FixLog.dto.member.LoginResponseDto; +import com.example.FixLog.service.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthService authService; + + @PostMapping("/login") + public ResponseEntity> login(@RequestBody LoginRequestDto requestDto) { + LoginResponseDto result = authService.login(requestDto); + return ResponseEntity.ok(Response.success("로그인 성공", result)); + } +} diff --git a/src/main/java/com/example/FixLog/controller/MemberController.java b/src/main/java/com/example/FixLog/controller/MemberController.java index 944c817..e7b55a0 100644 --- a/src/main/java/com/example/FixLog/controller/MemberController.java +++ b/src/main/java/com/example/FixLog/controller/MemberController.java @@ -1,38 +1,53 @@ -//package com.example.FixLog.controller; -// -// -//import com.example.FixLog.dto.Response; -//import com.example.FixLog.dto.member.SignupRequestDto; -//import com.example.FixLog.dto.member.DuplicateCheckResponseDto; -//import com.example.FixLog.service.MemberService; -//import lombok.RequiredArgsConstructor; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.*; -// -//@RestController -//@RequestMapping("/api/members") -//@RequiredArgsConstructor -//public class MemberController { -// -// private final MemberService memberService; -// -// @PostMapping("/signup") -// public ResponseEntity> signup(@RequestBody SignupRequestDto request) { -// memberService.signup(request); -// return ResponseEntity.ok(Response.success("회원가입이 완료되었습니다.", null)); -// } -// -// @GetMapping("/check-email") -// public ResponseEntity> checkEmail(@RequestParam String email) { -// boolean exists = memberService.isEmailDuplicated(email); -// String msg = exists ? "이미 사용 중인 이메일입니다." : "사용 가능한 이메일입니다."; -// return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists))); -// } -// -// @GetMapping("/check-nickname") -// public ResponseEntity> checkNickname(@RequestParam String nickname) { -// boolean exists = memberService.isNicknameDuplicated(nickname); -// String msg = exists ? "이미 사용 중인 닉네임입니다." : "사용 가능한 닉네임입니다."; -// return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists))); -// } -//} +package com.example.FixLog.controller; + + +import com.example.FixLog.domain.member.Member; +import com.example.FixLog.dto.Response; +import com.example.FixLog.dto.member.MemberInfoResponseDto; +import com.example.FixLog.dto.member.SignupRequestDto; +import com.example.FixLog.dto.member.DuplicateCheckResponseDto; +import com.example.FixLog.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/members") +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + + @PostMapping("/signup") + public ResponseEntity> signup(@RequestBody SignupRequestDto request) { + memberService.signup(request); + return ResponseEntity.ok(Response.success("회원가입 성공", null)); + } + + @GetMapping("/check-email") + public ResponseEntity> checkEmail(@RequestParam String email) { + boolean exists = memberService.isEmailDuplicated(email); + String msg = exists ? "이미 사용 중인 이메일입니다." : "사용 가능한 이메일입니다."; + return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists))); + } + + @GetMapping("/check-nickname") + public ResponseEntity> checkNickname(@RequestParam String nickname) { + boolean exists = memberService.isNicknameDuplicated(nickname); + String msg = exists ? "이미 사용 중인 닉네임입니다." : "사용 가능한 닉네임입니다."; + return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists))); + } + + @GetMapping("/me") + public ResponseEntity> getMyInfo(@AuthenticationPrincipal Member member) { + MemberInfoResponseDto responseDto = new MemberInfoResponseDto( + member.getEmail(), + member.getNickname(), + member.getProfileImageUrl(), + member.getBio(), + member.getSocialType() + ); + return ResponseEntity.ok(Response.success("회원 정보 조회 성공", responseDto)); + } +} diff --git a/src/main/java/com/example/FixLog/domain/member/Member.java b/src/main/java/com/example/FixLog/domain/member/Member.java index acc29ae..3c89f3f 100644 --- a/src/main/java/com/example/FixLog/domain/member/Member.java +++ b/src/main/java/com/example/FixLog/domain/member/Member.java @@ -9,16 +9,20 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Collection; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @EntityListeners(AuditingEntityListener.class) -public class Member { +public class Member implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -76,6 +80,41 @@ public static Member of(String email, String password, String nickname, SocialTy member.nickname = nickname; member.socialType = socialType; member.isDeleted = false; + member.profileImageUrl = "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; // 기본 프로필 이미지(임시) return member; } + + public void setProfileImageUrl(String profileImageUrl) { + this.profileImageUrl = profileImageUrl; + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_USER")); // 기본 권한 + } + + @Override + public String getUsername() { + return this.email; // 로그인 시 사용할 사용자 식별자 + } + + @Override + public boolean isAccountNonExpired() { + return true; // 계정 만료 여부 (true = 사용 가능) + } + + @Override + public boolean isAccountNonLocked() { + return true; // 계정 잠금 여부 (true = 잠금 아님) + } + + @Override + public boolean isCredentialsNonExpired() { + return true; // 비밀번호 만료 여부 + } + + @Override + public boolean isEnabled() { + return !this.isDeleted; // 탈퇴 여부 기반 활성 상태 + } } \ No newline at end of file diff --git a/src/main/java/com/example/FixLog/dto/member/LoginRequestDto.java b/src/main/java/com/example/FixLog/dto/member/LoginRequestDto.java new file mode 100644 index 0000000..f1fd07f --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/LoginRequestDto.java @@ -0,0 +1,9 @@ +package com.example.FixLog.dto.member; + +import lombok.Getter; + +@Getter +public class LoginRequestDto { + private String email; + private String password; +} diff --git a/src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java b/src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java new file mode 100644 index 0000000..fb7f80c --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java @@ -0,0 +1,15 @@ +package com.example.FixLog.dto.member; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Getter +@AllArgsConstructor +public class LoginResponseDto { + private Long userId; + private String accessToken; + private String nickname; + private String profileImageUrl; +} diff --git a/src/main/java/com/example/FixLog/dto/member/MemberInfoResponseDto.java b/src/main/java/com/example/FixLog/dto/member/MemberInfoResponseDto.java new file mode 100644 index 0000000..45ced06 --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/MemberInfoResponseDto.java @@ -0,0 +1,15 @@ +package com.example.FixLog.dto.member; + +import com.example.FixLog.domain.member.SocialType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MemberInfoResponseDto { + private String email; + private String nickname; + private String profileImageUrl; + private String bio; + private SocialType socialType; +} diff --git a/src/main/java/com/example/FixLog/exception/ErrorCode.java b/src/main/java/com/example/FixLog/exception/ErrorCode.java index e4d3d9c..ec737d5 100644 --- a/src/main/java/com/example/FixLog/exception/ErrorCode.java +++ b/src/main/java/com/example/FixLog/exception/ErrorCode.java @@ -21,7 +21,9 @@ public enum ErrorCode { FOLDER_NOT_FOUND(HttpStatus.NOT_FOUND, "폴더를 찾을 수 없습니다."), ACCESS_DENIED(HttpStatus.FORBIDDEN, "권한이 없습니다."), TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "없는 태그 번호입니다."), - SORT_NOT_EXIST(HttpStatus.BAD_REQUEST, "사용할 수 없는 정렬입니다."); + SORT_NOT_EXIST(HttpStatus.BAD_REQUEST, "사용할 수 없는 정렬입니다."), + INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."), + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다."); private final HttpStatus status; private final String message; diff --git a/src/main/java/com/example/FixLog/mock/BookmarkFolderTestDataInitializer.java b/src/main/java/com/example/FixLog/mock/BookmarkFolderTestDataInitializer.java index f094621..81504ba 100644 --- a/src/main/java/com/example/FixLog/mock/BookmarkFolderTestDataInitializer.java +++ b/src/main/java/com/example/FixLog/mock/BookmarkFolderTestDataInitializer.java @@ -1,16 +1,17 @@ package com.example.FixLog.mock; import com.example.FixLog.domain.bookmark.BookmarkFolder; -import com.example.FixLog.domain.member.Member; import com.example.FixLog.repository.MemberRepository; import com.example.FixLog.repository.bookmark.BookmarkFolderRepository; import lombok.RequiredArgsConstructor; import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.List; @Component +@Order(2) @RequiredArgsConstructor public class BookmarkFolderTestDataInitializer implements CommandLineRunner { @@ -20,15 +21,14 @@ public class BookmarkFolderTestDataInitializer implements CommandLineRunner { @Override public void run(String... args) { if (bookmarkFolderRepository.count() == 0) { - Member member = memberRepository.findByEmail("test1@example.com") - .orElseThrow(() -> new IllegalStateException("test1@example.com 사용자가 존재하지 않습니다.")); - - BookmarkFolder defaultFolder = new BookmarkFolder(member, "default folder"); - BookmarkFolder etcFolder = new BookmarkFolder(member,"그외 폴더"); - - bookmarkFolderRepository.saveAll(List.of(defaultFolder, etcFolder)); - - System.out.println("테스트용 북마크 폴더 2개 생성 완료"); + memberRepository.findByEmail("test1@example.com").ifPresentOrElse(member -> { + BookmarkFolder defaultFolder = new BookmarkFolder(member, "default folder"); + BookmarkFolder etcFolder = new BookmarkFolder(member, "그외 폴더"); + bookmarkFolderRepository.saveAll(List.of(defaultFolder, etcFolder)); + System.out.println("테스트용 북마크 폴더 2개 생성 완료"); + }, () -> { + System.out.println("test1@example.com 사용자가 존재하지 않아 폴더 생성 생략됨"); + }); } } } diff --git a/src/main/java/com/example/FixLog/mock/MemberTestDataInitializer.java b/src/main/java/com/example/FixLog/mock/MemberTestDataInitializer.java index 0c36918..034ed92 100644 --- a/src/main/java/com/example/FixLog/mock/MemberTestDataInitializer.java +++ b/src/main/java/com/example/FixLog/mock/MemberTestDataInitializer.java @@ -5,6 +5,8 @@ import com.example.FixLog.repository.bookmark.BookmarkFolderRepository; import lombok.RequiredArgsConstructor; import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import com.example.FixLog.domain.member.Member; import com.example.FixLog.domain.member.SocialType; @@ -12,17 +14,20 @@ import java.util.List; @Component +@Order(1) @RequiredArgsConstructor public class MemberTestDataInitializer implements CommandLineRunner { private final MemberRepository memberRepository; private final BookmarkFolderRepository bookmarkFolderRepository; + // 비밀번호 암호화 인코더 + private final PasswordEncoder passwordEncoder; @Override public void run(String... args) { if (memberRepository.count() == 0) { - Member member1 = Member.of("test1@example.com", "1234", "가나다", SocialType.EMAIL); - Member member2 = Member.of("test2@example.com", "1234", "라마바", SocialType.EMAIL); + Member member1 = Member.of("test1@example.com", passwordEncoder.encode("1234"), "가나다", SocialType.EMAIL); + Member member2 = Member.of("test2@example.com", passwordEncoder.encode("1234"), "라마바", SocialType.EMAIL); memberRepository.saveAll(List.of(member1, member2)); BookmarkFolder folder1 = new BookmarkFolder(member1); diff --git a/src/main/java/com/example/FixLog/service/AuthService.java b/src/main/java/com/example/FixLog/service/AuthService.java new file mode 100644 index 0000000..8736ecf --- /dev/null +++ b/src/main/java/com/example/FixLog/service/AuthService.java @@ -0,0 +1,45 @@ +package com.example.FixLog.service; + +import com.example.FixLog.domain.member.Member; +import com.example.FixLog.dto.member.LoginRequestDto; +import com.example.FixLog.dto.member.LoginResponseDto; +import com.example.FixLog.exception.CustomException; +import com.example.FixLog.exception.ErrorCode; +import com.example.FixLog.repository.MemberRepository; +import com.example.FixLog.util.JwtUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; + + public LoginResponseDto login(LoginRequestDto requestDto) { + Member member = memberRepository.findByEmail(requestDto.getEmail()) + .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); + + if (!passwordEncoder.matches(requestDto.getPassword(), member.getPassword())) { + throw new CustomException(ErrorCode.INVALID_PASSWORD); + } + + String token = jwtUtil.createToken(member.getUserId(), member.getEmail()); + + // 로그인 응답 시에도 null-safe하게 처리 + String profileUrl = member.getProfileImageUrl() != null + ? member.getProfileImageUrl() + : "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; + + return new LoginResponseDto( + member.getUserId(), + token, + member.getNickname(), + member.getProfileImageUrl() != null + ? member.getProfileImageUrl() + : "https://your-cdn.com/images/default-profile.png"); + } +} diff --git a/src/main/java/com/example/FixLog/service/MemberService.java b/src/main/java/com/example/FixLog/service/MemberService.java index 043234c..417a31e 100644 --- a/src/main/java/com/example/FixLog/service/MemberService.java +++ b/src/main/java/com/example/FixLog/service/MemberService.java @@ -1,56 +1,61 @@ -//package com.example.FixLog.service; -// -//import com.example.FixLog.domain.bookmark.BookmarkFolder; -//import com.example.FixLog.domain.member.Member; -//import com.example.FixLog.domain.member.SocialType; -//import com.example.FixLog.dto.member.SignupRequestDto; -//import com.example.FixLog.exception.CustomException; -//import com.example.FixLog.exception.ErrorCode; -//import com.example.FixLog.repository.MemberRepository; -//import com.example.FixLog.repository.bookmark.BookmarkFolderRepository; -//import lombok.RequiredArgsConstructor; -//import org.springframework.security.crypto.password.PasswordEncoder; -//import org.springframework.stereotype.Service; -// -//@Service -//@RequiredArgsConstructor -//public class MemberService { -// -// private final MemberRepository memberRepository; -// private final PasswordEncoder passwordEncoder; -// private final BookmarkFolderRepository bookmarkFolderRepository; -// -// public void signup(SignupRequestDto request) { -// // 이메일 중복 검사 -// if (isEmailDuplicated(request.getEmail())) { -// throw new CustomException(ErrorCode.EMAIL_DUPLICATED); -// } -// -// // 닉네임 중복 검사 -// if (isNicknameDuplicated(request.getNickname())) { -// throw new CustomException(ErrorCode.NICKNAME_DUPLICATED); -// } -// -// // 문제 없으면 저장 -// Member member = Member.of( -// request.getEmail(), -// passwordEncoder.encode(request.getPassword()), -// request.getNickname(), -// SocialType.EMAIL -// ); -// -// // 기본 폴더 생성 -// BookmarkFolder newFolder = new BookmarkFolder(member); -// bookmarkFolderRepository.save(newFolder); -// -// memberRepository.save(member); -// } -// -// public boolean isEmailDuplicated(String email) { -// return memberRepository.findByEmail(email).isPresent(); -// } -// -// public boolean isNicknameDuplicated(String nickname) { -// return memberRepository.findByNickname(nickname).isPresent(); -// } -//} +package com.example.FixLog.service; + +import com.example.FixLog.domain.bookmark.BookmarkFolder; +import com.example.FixLog.domain.member.Member; +import com.example.FixLog.domain.member.SocialType; +import com.example.FixLog.dto.member.SignupRequestDto; +import com.example.FixLog.exception.CustomException; +import com.example.FixLog.exception.ErrorCode; +import com.example.FixLog.repository.MemberRepository; +import com.example.FixLog.repository.bookmark.BookmarkFolderRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final BookmarkFolderRepository bookmarkFolderRepository; + + public void signup(SignupRequestDto request) { + // 이메일 중복 검사 + if (isEmailDuplicated(request.getEmail())) { + throw new CustomException(ErrorCode.EMAIL_DUPLICATED); + } + + // 닉네임 중복 검사 + if (isNicknameDuplicated(request.getNickname())) { + throw new CustomException(ErrorCode.NICKNAME_DUPLICATED); + } + + // 문제 없으면 저장 + Member member = Member.of( + request.getEmail(), + passwordEncoder.encode(request.getPassword()), + request.getNickname(), + SocialType.EMAIL + ); + // 기본 프로필 이미지 URL 생성 + member.setProfileImageUrl("https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"); + // 먼저 회원 정보 저장 + memberRepository.save(member); + + // 기본 폴더 생성 + BookmarkFolder newFolder = new BookmarkFolder(member); + bookmarkFolderRepository.save(newFolder); + + + } + + public boolean isEmailDuplicated(String email) { + return memberRepository.findByEmail(email).isPresent(); + } + + public boolean isNicknameDuplicated(String nickname) { + return memberRepository.findByNickname(nickname).isPresent(); + } +} + diff --git a/src/main/java/com/example/FixLog/util/JwtUtil.java b/src/main/java/com/example/FixLog/util/JwtUtil.java new file mode 100644 index 0000000..6ef7972 --- /dev/null +++ b/src/main/java/com/example/FixLog/util/JwtUtil.java @@ -0,0 +1,62 @@ +package com.example.FixLog.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String secretKeyString; + + private Key secretKey; + private final long expiration = 1000 * 60 * 60 * 24; // 24시간 + + @PostConstruct + public void init() { + this.secretKey = Keys.hmacShaKeyFor(secretKeyString.getBytes()); + } + + public String createToken(Long userId, String email) { + return Jwts.builder() + .setSubject(email) + .claim("userId", userId) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + public Claims getClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + } + + public boolean isTokenValid(String token) { + try { + getClaims(token); + return true; + } catch (Exception e) { + return false; + } + } + + public String getEmailFromToken(String token) { + return getClaims(token).getSubject(); + } + + public Long getUserIdFromToken(String token) { + return getClaims(token).get("userId", Long.class); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3b3f9fe..8052a7f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,5 +15,5 @@ spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect # JWT -jwt.secret-key=fixlogfixlogfixlogfixlogfixlog1234 +jwt.secret=fixlogfixlogfixlogfixlogfixlog1234 jwt.expiration-time=86400000 \ No newline at end of file