
풀스택 개발자 공부를 하고 난 이후, 첫 미니(?) 프로젝트를 완성하였다. 기본적인 회원가입과 로그인을 구현하였고 카카오로 로그인하기까지 넣어 소셜 로그인이 가능하도록 하였다.
백엔드와 프론트엔드 모두 구현을 해야하기에 처음에 어떻게 프로젝트를 구성할지 고민이 많았다. 처음에는 하나의 저장소에 함께 구현을 해볼까 하다가, 요즘은 백엔드와 프론트엔드 서버를 아예 분리하여 많이 개발을 한다는 글을 보고 나누어 진행을 하였다.
@Table(name = "member")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
private String password;
@Column(unique = true)
private String nickname;
@Enumerated(EnumType.STRING)
private Role role;
@Enumerated(EnumType.STRING)
private SocialType socialType;
private String socialId;
public void authorizeUser() {
this.role = Role.USER;
}
public void authorizeAdmin() {
this.role = Role.ADMIN;
}
public void updateNickname(String nickname) {
this.nickname = nickname;
}
public void encodePassword(PasswordEncoder passwordEncoder) {
this.password = passwordEncoder.encode(password);
}
public boolean matchPassword(PasswordEncoder passwordEncoder, String checkPassword) {
return passwordEncoder.matches(checkPassword, getPassword());
}
}
Member 클래스는 데이터베이스의 member 테이블과 매핑되는 엔티티 클래스이다. 이 클래스는 회원 정보와 관련된 필드를 가지고 있으며, 각 필드는 다음과 같이 정의된다.
@Id 및 @GeneratedValue 어노테이션을 통해 자동으로 생성된다.@Column 어노테이션을 통해 유니크하고 널이 될 수 없도록 설정하였다.@Enumerated 어노테이션을 사용하여 문자열 형태로 저장된다.@Enumerated 어노테이션을 사용하여 문자열 형태로 저장된다.이 클래스는 @Getter, @NoArgsConstructor, @AllArgsConstructor, @Builder 어노테이션을 사용하여 Lombok을 통해 자동으로 필요한 메소드와 생성자를 생성한다. 이를 통해 코드의 간결함과 유지하고, 유지 보수성을 높인다.
@Service
@Transactional
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
@Override
public void join(MemberJoinDto memberJoinDto) throws BaseException {
Member member = memberJoinDto.toEntity();
member.authorizeUser();
member.encodePassword(passwordEncoder);
if (memberRepository.findByEmail(memberJoinDto.email()).isPresent()) {
throw new MemberException(MemberExceptionType.ALREADY_EXIST_EMAIL);
}
if (memberRepository.findByNickname(memberJoinDto.nickname()).isPresent()) {
throw new MemberException(MemberExceptionType.ALREADY_EXIST_NICKNAME);
}
memberRepository.save(member);
}
@Override
public void update(MemberUpdateDto memberUpdateDto, String email) throws BaseException {
Member member = memberRepository.findByEmail(email).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
if (memberRepository.findByNickname(memberUpdateDto.nickname()).isPresent()) {
throw new MemberException(MemberExceptionType.ALREADY_EXIST_NICKNAME);
}
member.updateNickname(memberUpdateDto.nickname());
}
@Override
public MemberDto getMyInfo() throws BaseException {
Member member = memberRepository.findByEmail(GetLoginMember.getLoginMemberEmail()).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
return new MemberDto(member);
}
@Override
public void oauthJoin(OauthJoinRequest oauthJoinRequest) throws BaseException {
Member member = memberRepository.findByEmail(oauthJoinRequest.email()).orElseThrow(() -> new MemberException(MemberExceptionType.ALREADY_EXIST_EMAIL));
if (memberRepository.findByNickname(oauthJoinRequest.nickname()).isPresent()) {
throw new MemberException(MemberExceptionType.ALREADY_EXIST_NICKNAME);
}
member.updateNickname(oauthJoinRequest.nickname());
}
@Override
public void authorizeUser(OauthJoinRequest oauthJoinRequest) throws BaseException {
Member member = memberRepository.findByEmail(oauthJoinRequest.email()).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
member.authorizeUser();
}
@Override
public void withdraw(String email) throws BaseException {
Member member = memberRepository.findByEmail(email).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
memberRepository.delete(member);
}
}
MemberServiceImpl 클래스는 MemberService 인터페이스를 구현하는 서비스 클래스이다. 이 클래스는 회원 관련 비즈니스 로직을 처리하며, @Service 어노테이션을 통해 Spring의 서비스 컴포넌트로 등록된다. 또한, @Transactional 어노테이션을 통해 트랜잭션 관리가 적용된다. 주요 메소드는 다음과 같다.
회원 가입 (join 메소드)
MemberJoinDto 객체를 통해 전달된 회원 정보를 기반으로 새로운 회원을 등록한다. 회원의 이메일과 닉네임이 이미 존재하는지 확인한 후, 회원 정보를 저장한다. 회원의 비밀번호는 암호화되어 저장된다.MemberException 예외를 발생시킨다.회원 정보 수정 (update 메소드)
MemberUpdateDto 객체를 통해 전달된 닉네임으로 회원 정보를 수정한다. 닉네임이 이미 존재하는지 확인한 후, 수정된 닉네임을 저장한다.MemberException 예외를 발생시킨다.회원 정보 조회 (getMyInfo 메소드)
MemberDto 객체로 반환한다.MemberException 예외를 발생시킨다.OAuth2 회원 정보 수정 (oauthJoin 메소드)
MemberException 예외를 발생시킨다.사용자 권한 부여 (authorizeUser 메소드)
MemberException 예외를 발생시킨다.회원 탈퇴 (withdraw 메소드)
MemberException 예외를 발생시킨다.이와 같이, MemberServiceImpl 클래스는 회원 가입, 정보 수정, 조회, 권한 부여, 탈퇴 등의 다양한 회원 관련 기능을 제공하며, 각 기능은 명시된 예외 처리 로직을 통해 안정성을 보장한다.
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final RedisService redisService;
@PostMapping("/join")
@ResponseStatus(HttpStatus.OK)
public void join(@Valid @RequestBody MemberJoinDto memberJoinDto) throws Exception {
memberService.join(memberJoinDto);
}
@PutMapping("/update")
@PreAuthorize("hasRole('USER'||'ADMIN')")
@ResponseStatus(HttpStatus.OK)
public void updateMemberInfo(@Valid @RequestBody MemberUpdateDto memberUpdateDto) throws Exception {
String email = GetLoginMember.getLoginMemberEmail();
memberService.update(memberUpdateDto, email);
}
@PutMapping("/oauth2/update")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<Void> oauthJoin(@Valid @RequestBody OauthJoinRequest oauthJoinRequest) throws Exception {
memberService.oauthJoin(oauthJoinRequest);
memberService.authorizeUser(oauthJoinRequest);
return ResponseEntity.ok().build();
}
@DeleteMapping
@PreAuthorize("hasRole('USER'||'ADMIN')")
@ResponseStatus(HttpStatus.OK)
public void withdraw() throws Exception {
String email = GetLoginMember.getLoginMemberEmail();
memberService.withdraw(email);
}
@GetMapping("/myInfo")
@PreAuthorize("hasRole('USER'||'ADMIN')")
public ResponseEntity<MemberDto> getMyInfo() throws Exception {
MemberDto dto = memberService.getMyInfo();
return ResponseEntity.ok(dto);
}
@GetMapping("/logout")
@PreAuthorize("hasRole('USER'||'ADMIN' || 'MANAGER')")
public void logout(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication());
}
@GetMapping("/access-denied")
public String accessDenied() {
return ("/error");
}
}
MemberController 클래스는 회원 관련 RESTful 웹 서비스의 엔드포인트를 제공하는 컨트롤러 클래스이다. 이 클래스는 다양한 회원 관련 요청을 처리하며, @RestController와 @RequestMapping 어노테이션을 통해 컨트롤러로 설정되고, 기본 경로는 /members로 설정된다. 주요 메소드는 다음과 같다.
회원 가입 (join 메소드)
/members/joinMemberJoinDto 객체MemberJoinDto 객체를 통해 회원 정보를 받아와 저장한다.회원 정보 수정 (updateMemberInfo 메소드)
/members/updateMemberUpdateDto 객체USER 또는 ADMIN 권한을 가진 사용자만 호출할 수 있다.OAuth2 회원 정보 수정 (oauthJoin 메소드)
/members/oauth2/updateOauthJoinRequest 객체ResponseEntity<Void>OauthJoinRequest 객체를 통해 회원 정보를 받아와 처리한다.회원 탈퇴 (withdraw 메소드)
/membersUSER 또는 ADMIN 권한을 가진 사용자만 호출할 수 있다.회원 정보 조회 (getMyInfo 메소드)
/members/myInfoResponseEntity<MemberDto>USER 또는 ADMIN 권한을 가진 사용자만 호출할 수 있다.로그아웃 (logout 메소드)
/members/logoutUSER, ADMIN 또는 MANAGER 권한을 가진 사용자만 호출할 수 있다.접근 거부 (accessDenied 메소드)
/members/access-deniedStringpublic enum MemberExceptionType implements BaseExceptionType {
ALREADY_EXIST_EMAIL(600, HttpStatus.CONFLICT, "이미 존재하는 이메일입니다."),
ALREADY_EXIST_NICKNAME(601, HttpStatus.CONFLICT, "이미 존재하는 닉네임입니다."),
WRONG_PASSWORD(602, HttpStatus.BAD_REQUEST, "아이디 또는 비밀번호를 잘못 입력했습니다."),
NOT_FOUND_MEMBER(603, HttpStatus.NOT_FOUND, "일치하는 회원이 존재하지 않습니다."),
WRONG_ROLE(604, HttpStatus.BAD_REQUEST, "잘못된 역할 권한입니다.");
private final int errorCode;
private final HttpStatus httpStatus;
private final String errorMessage;
MemberExceptionType(int errorCode, HttpStatus httpStatus, String errorMessage) {
this.errorCode = errorCode;
this.httpStatus = httpStatus;
this.errorMessage = errorMessage;
}
@Override
public int getErrorCode() {
return this.errorCode;
}
@Override
public HttpStatus getHttpStatus() {
return this.httpStatus;
}
@Override
public String getErrorMessage() {
return this.errorMessage;
}
}
MemberExceptionType 열거형(enum)은 회원 관련 예외 상황을 정의하고 관리하기 위해 사용되는 클래스이다. 이 클래스는 BaseExceptionType 인터페이스를 구현하여 공통적인 예외 속성을 제공하며, 다음과 같은 예외 타입을 정의한다.
ALREADY_EXIST_EMAIL
ALREADY_EXIST_NICKNAME
WRONG_PASSWORD
NOT_FOUND_MEMBER
WRONG_ROLE
각 예외 타입은 다음과 같은 속성을 가진다:
이 클래스는 생성자에서 예외 코드, HTTP 상태 코드, 에러 메시지를 초기화하며, getErrorCode, getHttpStatus, getErrorMessage 메소드를 통해 각 속성에 접근할 수 있다.
이와 같이 MemberExceptionType 클래스는 다양한 회원 관련 예외 상황을 일관된 방식으로 정의하고 관리할 수 있도록 해준다.
내용이 길어져서 다음 포스팅 내용부터는 Auth 관련 코드에 대한 설명을 할 예정이다. 그럼 두번째 글에서 이어서 작성하자.