Skip to content
Open
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
.apt_generated
.classpath
Expand Down
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ dependencies {

// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

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

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

tasks.named('test') {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/umc/domain/member/controller/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,20 @@ public ApiResponse<PageResponse<MissionChallengeListDto>> getAvailableMissions(

return ApiResponse.onSuccess(GeneralSuccessCode.OK, missionList);
}

// 회원가입
@PostMapping("/sign-up")
public ApiResponse<MemberResDTO.JoinDTO> signUp2(
@RequestBody @Valid MemberReqDTO.JoinDTO dto
) {
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberCommandService.signUp(dto));
}

// 로그인
@PostMapping("/login")
public ApiResponse<MemberResDTO.LoginDTO> login2(
@RequestBody @Valid MemberReqDTO.LoginDTO dto
) {
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberQueryService.login(dto));
}
Comment on lines +82 to +96
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'로그인/회원가입'과 같이 권한 관련 코드들은 Member가 아닌, 따로 Auth 패키지를 파서 관리하기도 한답니다!
참고해주세요!

}
14 changes: 12 additions & 2 deletions src/main/java/umc/domain/member/converter/MemberConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import umc.domain.member.dto.member.MemberReqDTO;
import umc.domain.member.dto.member.MemberResDTO;
import umc.domain.member.entity.Member;
import umc.global.auth.enums.Role;

public class MemberConverter {

Expand All @@ -13,13 +14,22 @@ public static MemberResDTO.JoinDTO toJoinDTO(Member member) {
.build();
}

public static Member toMember(MemberReqDTO.JoinDTO dto) {
public static Member toMember(MemberReqDTO.JoinDTO dto, String password, Role role) {
return Member.builder()
.name(dto.name())
.password(dto.password())
.email(dto.email())
.password(password)
.role(role)
.birth(dto.birth())
.address(dto.address())
.gender(dto.gender())
.build();
}

public static MemberResDTO.LoginDTO toLoginDTO(Member member, String accessToken) {
return MemberResDTO.LoginDTO.builder()
.memberId(member.getId())
.accessToken(accessToken)
.build();
}
}
12 changes: 12 additions & 0 deletions src/main/java/umc/domain/member/dto/member/MemberReqDTO.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package umc.domain.member.dto.member;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
Expand All @@ -12,6 +13,8 @@ public class MemberReqDTO {
public record JoinDTO(
@NotBlank
String name,
@Email
String email,
@NotBlank
String password,
@NotNull
Expand All @@ -24,4 +27,13 @@ public record JoinDTO(
List<Long> preferCategory
) {
}

// 로그인
public record LoginDTO(
@NotBlank
String email,
@NotBlank
String password
) {
}
}
8 changes: 8 additions & 0 deletions src/main/java/umc/domain/member/dto/member/MemberResDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ public record JoinDTO(
LocalDateTime createdAt
) {
}

// 로그인
@Builder
public record LoginDTO(
Long memberId,
String accessToken
) {
}
}
6 changes: 5 additions & 1 deletion src/main/java/umc/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import umc.domain.member.entity.mapping.MemberMission;
import umc.domain.member.enums.Gender;
import umc.domain.member.enums.SnsType;
import umc.global.auth.enums.Role;
import umc.global.entity.BaseEntity;

@Entity
Expand All @@ -46,7 +47,10 @@ public class Member extends BaseEntity {
@Column(name = "password", nullable = false, length = 255)
private String password;

@Column(name = "email", length = 255)
@Enumerated(EnumType.STRING)
private Role role;

@Column(name = "email", nullable = false, length = 255)
private String email;

@Column(name = "phone_number", length = 11)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ public enum MemberErrorCode implements BaseErrorCode {
NOT_FOUND(HttpStatus.NOT_FOUND,
"MEMBER404_1",
"해당 사용자를 찾지 못했습니다."),
;
INVALID(HttpStatus.UNAUTHORIZED,
"MEMBER401_1",
"비밀번호가 일치하지 않습니다.");

private final HttpStatus status;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package umc.domain.member.repository;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -21,4 +22,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
where m.id = :id
""")
MyPageDto findMyPageById(@Param("id") long id);

Optional<Member> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import umc.domain.member.converter.MemberConverter;
import umc.domain.member.dto.member.MemberReqDTO;
Expand All @@ -13,6 +14,7 @@
import umc.domain.member.repository.FoodRepository;
import umc.domain.member.repository.MemberFoodRepository;
import umc.domain.member.repository.MemberRepository;
import umc.global.auth.enums.Role;

@Service
@RequiredArgsConstructor
Expand All @@ -21,12 +23,15 @@ public class MemberCommandServiceImpl implements MemberCommandService {
private final MemberRepository memberRepository;
private final FoodRepository foodRepository;
private final MemberFoodRepository memberFoodRepository;
private final PasswordEncoder passwordEncoder;

@Override
public MemberResDTO.JoinDTO signUp(
MemberReqDTO.JoinDTO dto
) {
Member member = MemberConverter.toMember(dto);
String salt = passwordEncoder.encode(dto.password());

Member member = MemberConverter.toMember(dto, salt, Role.ROLE_USER);
memberRepository.save(member);

if (dto.preferCategory().size() > 1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package umc.domain.member.service.query;

import jakarta.validation.Valid;
import org.springframework.data.domain.Pageable;
import umc.domain.member.dto.MissionChallengeListDto;
import umc.domain.member.dto.MissionListDto;
import umc.domain.member.dto.MyPageDto;
import umc.domain.member.dto.member.MemberReqDTO;
import umc.domain.member.dto.member.MemberResDTO;
import umc.domain.member.enums.Status;
import umc.global.dto.PageResponse;

Expand All @@ -18,4 +21,6 @@ PageResponse<MissionChallengeListDto> getAvailableMissions(Long memberId,
String regionName,
Long lastMissionId,
Pageable pageable);

MemberResDTO.LoginDTO login(MemberReqDTO.@Valid LoginDTO dto);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
package umc.domain.member.service.query;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import umc.domain.member.converter.MemberConverter;
import umc.domain.member.dto.MissionChallengeListDto;
import umc.domain.member.dto.MissionListDto;
import umc.domain.member.dto.MyPageDto;
import umc.domain.member.dto.member.MemberReqDTO;
import umc.domain.member.dto.member.MemberResDTO;
import umc.domain.member.entity.Member;
import umc.domain.member.enums.Status;
import umc.domain.member.exception.member.MemberException;
import umc.domain.member.exception.member.code.MemberErrorCode;
import umc.domain.member.repository.MemberRepository;
import umc.domain.member.repository.membermission.MemberMissionRepository;
import umc.global.auth.CustomUserDetails;
import umc.global.auth.JwtUtil;
import umc.global.dto.PageResponse;

@Service
Expand All @@ -18,6 +28,8 @@ public class MemberQueryServiceImpl implements MemberQueryService {

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

@Override
public MyPageDto getMyPage(Long memberId) {
Expand All @@ -43,5 +55,29 @@ public PageResponse<MissionChallengeListDto> getAvailableMissions(Long memberId,
.findAvailableMissionsByMemberIdAndRegion(regionName, memberId, lastMissionId, pageable);
return PageResponse.of(page);
}

@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
@@ -1,5 +1,6 @@
package umc.domain.misson.controller;

import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -12,6 +13,7 @@
import umc.domain.misson.exception.code.MissionSuccessCode;
import umc.domain.misson.service.command.MissionCommandService;
import umc.domain.misson.service.query.MissionQueryService;
import umc.global.annotation.PageParam;
import umc.global.apiPayload.ApiResponse;

@RestController
Expand All @@ -24,8 +26,8 @@ public class MissionController implements MissionControllerDocs {
@GetMapping("/missions")
@Override
public ApiResponse<MissionPreviewListDTO> getMissionsByStore(
@RequestParam String storeName,
@RequestParam Integer page
@RequestParam @NotBlank String storeName,
@RequestParam @PageParam Integer page
) {
MissionSuccessCode code = MissionSuccessCode.FOUND;
return ApiResponse.onSuccess(code, missionQueryService.findMissionsByStore(storeName, page));
Expand All @@ -34,8 +36,8 @@ public ApiResponse<MissionPreviewListDTO> getMissionsByStore(
@GetMapping("/{memberId}/missions/ongoing")
@Override
public ApiResponse<MissionPreviewListDTO> getOngoingMissions(
@PathVariable Long memberId,
@RequestParam Integer page
@PathVariable @NotBlank Long memberId,
@RequestParam @PageParam Integer page
) {
MemberMissionSuccessCode code = MemberMissionSuccessCode.FOUND;
return ApiResponse.onSuccess(code, missionQueryService.findOngoingMissions(memberId, page));
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/umc/global/auth/AuthenticationEntryPointImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package umc.global.auth;

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

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);
}
}
29 changes: 29 additions & 0 deletions src/main/java/umc/global/auth/CustomUserDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package umc.global.auth;

import java.util.Collection;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import umc.domain.member.entity.Member;

@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