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
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ dependencies {
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'com.h2database:h2'

implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'org.springframework.boot:spring-boot-configuration-processor'

// QueryDSL : OpenFeign
implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0"
implementation "io.github.openfeign.querydsl:querydsl-core:7.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
package com.example.umc9th.domain.member.controller;


import com.example.umc9th.domain.member.dto.req.MemberReqDTO;
import com.example.umc9th.domain.member.dto.res.MemberResDTO;
import com.example.umc9th.domain.member.exception.code.MemberSuccessCode;
import com.example.umc9th.domain.member.service.command.MemberCommandService;
import com.example.umc9th.domain.member.service.query.MemberQueryService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestBody;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class MemberController {

private final MemberCommandService memberCommandService;
private final MemberQueryService memberQueryService;

// 회원가입
@PostMapping("/sign-up")
Expand All @@ -24,4 +33,28 @@ public ApiResponse<MemberResDTO.JoinDTO> signUp(
){
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberCommandService.signup(dto));
}

// 로그인
@PostMapping("/login")
public ApiResponse<MemberResDTO.LoginDTO> login(
@RequestBody @Valid MemberReqDTO.LoginDTO dto
){
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberQueryService.login(dto));
}

@PostMapping("/logout")
public ApiResponse<String> logout(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false);
if (session != null) session.invalidate();

SecurityContextHolder.clearContext();

ResponseCookie cookie = ResponseCookie.from("JSESSIONID", "")
.path("/")
.maxAge(0)
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

return ApiResponse.onSuccess(MemberSuccessCode.FOUND, "로그아웃 완료");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.umc9th.domain.member.dto.req.MemberReqDTO;
import com.example.umc9th.domain.member.dto.res.MemberResDTO;
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.global.auth.enums.Role;

public class MemberConverter {

Expand All @@ -18,14 +19,30 @@ public static MemberResDTO.JoinDTO toJoinDTO(

// DTO -> Entity
public static Member toMember(
MemberReqDTO.JoinDTO dto
MemberReqDTO.JoinDTO dto,
String password,
Role role
){
return Member.builder()
.name(dto.name())
.email(dto.email()) // 추가된 코드
.password(password) // 추가된 코드
.role(role) // 추가된 코드
.birth(dto.birth())
.address(dto.address())
.phoneNum(dto.phoneNum())
.detailAddress(dto.specAddress())
.gender(dto.gender())
.build();
}


// Entity -> DTO (로그인 응답) 추가
public static MemberResDTO.LoginDTO toLoginDTO(Member member, String accessToken) {
return MemberResDTO.LoginDTO.builder()
.memberId(member.getId())
.accessToken(accessToken)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@

import com.example.umc9th.domain.member.enums.Gender;
import com.example.umc9th.global.annotation.ExistFoods;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDate;
import java.util.List;

public class MemberReqDTO {

// 회원가입
public record JoinDTO(
@NotBlank
String name,
@Email
String email, // 추가된 속성
@NotBlank
String password, // 추가된 속성
@NotNull
Gender gender,
@NotNull
Expand All @@ -20,7 +27,16 @@ public record JoinDTO(
String address,
@NotNull
String specAddress,
@NotBlank String phoneNum,
@ExistFoods
List<Long> preferCategory
){}

// 로그인
public record LoginDTO(
@NotBlank
String email,
@NotBlank
String password
){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@
import java.time.LocalDateTime;

public class MemberResDTO {
// 회원가입
@Builder
public record JoinDTO(
Long memberId,
LocalDateTime createAt
){}

// 로그인
@Builder
public record LoginDTO(
Long memberId,
String accessToken
){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.example.umc9th.domain.member.enums.Gender;
import com.example.umc9th.domain.member.enums.PhoneVerificationStatus;
import com.example.umc9th.domain.member.enums.SocialType;
import com.example.umc9th.global.auth.enums.Role;
import com.example.umc9th.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
Expand Down Expand Up @@ -54,9 +55,15 @@ public class Member extends BaseEntity {
@Enumerated(EnumType.STRING)
private SocialType socialType;

@Column(name = "email", length = 50)
@Column(name = "email", unique = true, length = 50)
private String email;

@Column(nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;

@Column(name = "phone_verification_status", nullable = false)
@Enumerated(EnumType.STRING)
@Builder.Default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ public enum MemberErrorCode implements BaseErrorCode {
NOT_FOUND(HttpStatus.NOT_FOUND,
"MEMBER404_1",
"해당 사용자를 찾지 못했습니다."),
;

INVALID(HttpStatus.BAD_REQUEST,
"MEMBER400_1",
"유효하지 않은 사용자 요청입니다.");

private final HttpStatus status;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface MemberRepository extends JpaRepository<Member,Long> {
// 활성 상태인 회원 단건 조회
Optional<Member> findByIdAndIsActiveTrue(Long id);

Optional<Member> findByEmail(String email);

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import com.example.umc9th.domain.member.repository.FoodRepository;
import com.example.umc9th.domain.member.repository.MemberFoodRepository;
import com.example.umc9th.domain.member.repository.MemberRepository;
import com.example.umc9th.global.auth.enums.Role;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
Expand All @@ -27,19 +29,26 @@ public class MemberCommandServiceImpl implements MemberCommandService{
private final MemberFoodRepository memberFoodRepository;
private final FoodRepository foodRepository;

// Password Encoder
private final PasswordEncoder passwordEncoder;

// 회원가입
@Override
@Transactional
public MemberResDTO.JoinDTO signup(
MemberReqDTO.JoinDTO dto
){

// 솔트된 비밀번호 생성
String salt = passwordEncoder.encode(dto.password());

// 사용자 생성
Member member = MemberConverter.toMember(dto);
Member member = MemberConverter.toMember(dto, salt, Role.ROLE_USER);
// DB 적용
memberRepository.save(member);

// 선호 음식 존재 여부 확인
if (dto.preferCategory().size() > 1){
if (dto.preferCategory() != null && !dto.preferCategory().isEmpty()) {
List<MemberFood> memberFoodList = new ArrayList<>();

// 선호 음식 ID별 조회
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.umc9th.domain.member.service.query;

import com.example.umc9th.domain.member.dto.req.MemberReqDTO;
import com.example.umc9th.domain.member.dto.res.MemberResDTO;
import jakarta.validation.Valid;

public interface MemberQueryService {
MemberResDTO.LoginDTO login(MemberReqDTO.@Valid LoginDTO dto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.example.umc9th.domain.member.service.query;

import com.example.umc9th.domain.member.converter.MemberConverter;
import com.example.umc9th.domain.member.dto.req.MemberReqDTO;
import com.example.umc9th.domain.member.dto.res.MemberResDTO;
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.domain.member.exception.MemberException;
import com.example.umc9th.domain.member.exception.code.MemberErrorCode;
import com.example.umc9th.domain.member.repository.MemberRepository;
import com.example.umc9th.global.auth.CustomUserDetails;
import com.example.umc9th.global.auth.util.JwtUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MemberQueryServiceImpl implements MemberQueryService{

private final MemberRepository memberRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder encoder;

@Override
public MemberResDTO.LoginDTO login(
MemberReqDTO.@Valid LoginDTO dto
) {

// Member 조회
Member member = memberRepository.findByEmail(dto.email())
.orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND));

// 비밀번호 검증
if (!encoder.matches(dto.password(), member.getPassword())){
throw new MemberException(MemberErrorCode.INVALID);
}

// JWT 토큰 발급용 UserDetails
CustomUserDetails userDetails = new CustomUserDetails(member);

// 엑세스 토큰 발급
String accessToken = jwtUtil.createAccessToken(userDetails);

// DTO 조립
return MemberConverter.toLoginDTO(member, accessToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.umc9th.global.auth;

import com.example.umc9th.global.apiPayload.ApiResponse;
import com.example.umc9th.global.apiPayload.code.GeneralErrorCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.io.IOException;

public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper = new ObjectMapper();


@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

ApiResponse<Void> errorResponse = ApiResponse.onFailure(
GeneralErrorCode.UNAUTHORIZED,
null
);

objectMapper.writeValue(response.getOutputStream(), errorResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.umc9th.global.auth;

import com.example.umc9th.domain.member.entity.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {

private final Member member;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(() -> member.getRole().toString());
}

@Override
public String getPassword() {
return member.getPassword();
}

@Override
public String getUsername() {
return member.getEmail();
}
}
Loading