Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ dependencies {

// oauth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework:spring-context-support'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ public enum ErrorStatus implements BaseErrorCode {

MUSIC_NOT_FOUND(HttpStatus.BAD_REQUEST, "MUSIC400", "음원을 찾을 수 없습니다."),

LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "LIKE400", "해당 좋아요를 찾을 수 없습니다.");
LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "LIKE400", "해당 좋아요를 찾을 수 없습니다."),

EMAIL_SEND_ERROR(HttpStatus.BAD_REQUEST, "EMAIL400", "메일 발송에 실패하였습니다."),
EMAIL_CODE_ERROR(HttpStatus.BAD_REQUEST, "EMAIL401", "유효한 코드가 아닙니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
52 changes: 52 additions & 0 deletions src/main/java/umc/codeplay/config/security/CustomUserDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package umc.codeplay.config.security;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
private final String email;
private final String password;
private final Collection<? extends GrantedAuthority> authorities;

@Override
public String getUsername() {
return email; // username대신 email 사용
}

@Override
public String getPassword() {
return password;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package umc.codeplay.config.security;

import java.util.Collections;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
Expand All @@ -19,15 +22,21 @@ public class CustomUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Member member =
memberRepository
.findByEmail(username)
.findByEmail(email)
.orElseThrow(() -> new GeneralHandler(ErrorStatus.MEMBER_NOT_FOUND));

return org.springframework.security.core.userdetails.User.withUsername(member.getEmail())
.password(member.getPassword())
.roles(member.getRole().name())
.build();
// return
// org.springframework.security.core.userdetails.User.withUsername(member.getEmail())
// .password(member.getPassword())
// .roles(member.getRole().name())
// .build();

return new CustomUserDetails(
member.getEmail(),
member.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
24 changes: 24 additions & 0 deletions src/main/java/umc/codeplay/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Collection;
import java.util.stream.Collectors;
import jakarta.mail.MessagingException;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

Expand All @@ -24,6 +25,7 @@
import umc.codeplay.dto.MemberRequestDTO;
import umc.codeplay.dto.MemberResponseDTO;
import umc.codeplay.jwt.JwtUtil;
import umc.codeplay.service.EmailService;
import umc.codeplay.service.MemberService;

@RestController
Expand All @@ -35,6 +37,7 @@ public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;
private final MemberService memberService;
private final EmailService emailService;

@PostMapping("/login")
public ApiResponse<MemberResponseDTO.LoginResultDTO> login(
Expand Down Expand Up @@ -104,4 +107,25 @@ public ApiResponse<MemberResponseDTO.LoginResultDTO> refresh(
throw new GeneralHandler(ErrorStatus.INVALID_REFRESH_TOKEN);
}
}

// 비밀번호 찾기 및 변경. 이메일 인증
@PostMapping("/password/reset/request")
public ApiResponse<String> resetPasswordRequest(
@RequestBody MemberRequestDTO.ResetPasswordDTO request) throws MessagingException {
emailService.sendCode(request.getEmail());
return ApiResponse.onSuccess("메일로 인증번호가 전송되었습니다.");
}

// 비밀번호 찾기 및 변경. 인증 코드 확인
@PostMapping("/password/reset/verify")
public ApiResponse<String> resetPasswordVerify(
@RequestBody MemberRequestDTO.CheckVerificationCodeDTO request) {
boolean isValid = emailService.verifyCode(request.getEmail(), request.getCode());
if (isValid) {
return ApiResponse.onSuccess("인증에 성공하였습니다.");
// 이후에 비밀번호 변경 페이지 연결해 주어야 함.
} else {
throw new GeneralHandler(ErrorStatus.EMAIL_CODE_ERROR);
}
}
}
37 changes: 37 additions & 0 deletions src/main/java/umc/codeplay/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package umc.codeplay.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import lombok.RequiredArgsConstructor;

import umc.codeplay.apiPayLoad.ApiResponse;
import umc.codeplay.config.security.CustomUserDetails;
import umc.codeplay.converter.MemberConverter;
import umc.codeplay.domain.Member;
import umc.codeplay.dto.MemberRequestDTO;
import umc.codeplay.dto.MemberResponseDTO;
import umc.codeplay.service.MemberService;

@RestController
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {

private final MemberService memberService;
private final MemberConverter memberConverter;

@PutMapping("/update")
public ApiResponse<MemberResponseDTO.UpdateResultDTO> updateMember(
@AuthenticationPrincipal
CustomUserDetails
userDetails, // 현재 로그인된 사용자 정보, email로 조회하기 위해 customUserDetails 사용
@RequestBody MemberRequestDTO.UpdateMemberDTO requestDto) {

Member updatedMember = memberService.updateMember(userDetails.getUsername(), requestDto);
MemberResponseDTO.UpdateResultDTO responseDto =
memberConverter.toUpdateResultDTO(updatedMember);

return ApiResponse.onSuccess(responseDto);
}
}
26 changes: 26 additions & 0 deletions src/main/java/umc/codeplay/controller/MusicController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package umc.codeplay.controller;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

import umc.codeplay.apiPayLoad.ApiResponse;
import umc.codeplay.service.MusicService;

@RestController
@RequiredArgsConstructor
@RequestMapping("/music")
public class MusicController {

private final MusicService musicService;

@DeleteMapping("/{musicId}")
public ApiResponse<Long> delete(@PathVariable Long musicId) {

musicService.deleteMusic(musicId);
return ApiResponse.onSuccess(musicId);
}
}
10 changes: 10 additions & 0 deletions src/main/java/umc/codeplay/converter/MemberConverter.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package umc.codeplay.converter;

import org.springframework.stereotype.Component;

import umc.codeplay.domain.Member;
import umc.codeplay.domain.enums.Role;
import umc.codeplay.domain.enums.SocialStatus;
import umc.codeplay.dto.MemberRequestDTO;
import umc.codeplay.dto.MemberResponseDTO;

@Component
public class MemberConverter {

public static Member toMember(MemberRequestDTO.JoinDto request) {
Expand All @@ -32,4 +35,11 @@ public static MemberResponseDTO.LoginResultDTO toLoginResultDTO(
.refreshToken(refreshToken)
.build();
}

public static MemberResponseDTO.UpdateResultDTO toUpdateResultDTO(Member member) {
return MemberResponseDTO.UpdateResultDTO.builder()
.email(member.getEmail())
.profileUrl(member.getProfileUrl())
.build();
}
}
14 changes: 12 additions & 2 deletions src/main/java/umc/codeplay/domain/enums/Role.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
package umc.codeplay.domain.enums;

public enum Role {
ADMIN,
USER
USER("ROLE_USER"),
ADMIN("ROLE_ADMIN");

private final String role;

Role(String role) {
this.role = role;
}

public String getRole() {
return role;
}
}
18 changes: 18 additions & 0 deletions src/main/java/umc/codeplay/dto/EmailCodeDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package umc.codeplay.dto;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class EmailCodeDTO {

@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class VerificationCode {
String code;
LocalDateTime expires;
}
}
23 changes: 23 additions & 0 deletions src/main/java/umc/codeplay/dto/MemberRequestDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import jakarta.validation.constraints.NotBlank;

import lombok.Getter;
import lombok.Setter;

public class MemberRequestDTO {

Expand Down Expand Up @@ -31,4 +32,26 @@ public static class LoginDto {
@NotBlank(message = "비밀번호는 필수 입력값입니다.")
String password;
}

@Getter
public static class ResetPasswordDTO {
@Email(message = "이메일 형식이 아닙니다.")
String email;
}

@Getter
public static class CheckVerificationCodeDTO {
String email;
String code;
}

@Getter
@Setter
public static class UpdateMemberDTO {

@NotBlank(message = "비밀번호는 필수 입력값입니다.")
String password;

String profileUrl; // 프로필 사진 URL
}
}
9 changes: 9 additions & 0 deletions src/main/java/umc/codeplay/dto/MemberResponseDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,13 @@ public static class LoginResultDTO {
String token;
String refreshToken;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class UpdateResultDTO {
String email;
String profileUrl;
}
}
13 changes: 10 additions & 3 deletions src/main/java/umc/codeplay/jwt/JwtAuthenticationFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import umc.codeplay.config.security.CustomUserDetails;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtUtil jwtUtil;
Expand All @@ -34,18 +36,23 @@ protected void doFilterInternal(
// 2. 토큰 유효성 검사
if (jwtUtil.validateToken(token)
&& (jwtUtil.getTypeFromToken(token).equals("access"))) {
// 3. 토큰에서 사용자명 추출
// 3. 토큰에서 사용자 정보 추출
String username = jwtUtil.getUsernameFromToken(token);

System.out.println(username);
// String email = jwtUtil.getUsernameFromToken(token);
List<String> roles = jwtUtil.getRolesFromToken(token);

List<GrantedAuthority> authorities =
roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

// CustomUserDetails 객체 생성 후 저장
CustomUserDetails userDetails = new CustomUserDetails(username, "", authorities);

UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, authorities);
new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
System.out.println(authentication);

SecurityContextHolder.getContext().setAuthentication(authentication);
}
Expand Down
Loading
Loading