Skip to content

Commit

Permalink
Bp 25 implement user registration (#6)
Browse files Browse the repository at this point in the history
* feat : 회원가입 api 권한 수정, 엔드포인트 prefix 메소드 추가

* feat : 에러코드, 커스텀 에러 추가, 전역 핸들러에 로직구현

* feat : 회원가입 DTO추가, 멤버 컨트롤러 추가

* feat : 멤버 서비스 추가, 회원가입 기능 추가

* test : 컨트롤러 테스트, 유닛테스트 작성

* feat : 유저 비밀번호 암호화 추가

* rename : 파일 위치 수정

* test : 암호화 로직 추가

* rename : DTO 컨트롤러 패키지로 이동

* refactor : 매직넘버 삭제, HttpStatus사용

* refactor : 기존 ErrorCode를 인터페이스화, 도메인마다 에러코드 정의

* refactor : 에러코드 구조 변경에 따른 코드 수정

* chore : spring-validation 추가
  • Loading branch information
ekgns33 authored Jul 20, 2024
1 parent abf9384 commit 437488f
Show file tree
Hide file tree
Showing 18 changed files with 443 additions and 70 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security:2.3.3.RELEASE'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@

import com.fasterxml.jackson.databind.ObjectMapper;

import gdsc.konkuk.platformcore.global.exceptions.ErrorCode;
import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode;
import gdsc.konkuk.platformcore.global.responses.ErrorResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -23,9 +22,9 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws
IOException, ServletException {
IOException {

ErrorResponse errorResponse = ErrorResponse.of(ErrorCode.INVALID_USER_INFO);
ErrorResponse errorResponse = ErrorResponse.of(MemberErrorCode.INVALID_USER_INFO);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import gdsc.konkuk.platformcore.application.auth.exceptions.InvalidUserInfoException;
import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode;
import gdsc.konkuk.platformcore.domain.member.entity.Member;
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository;
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode;
import gdsc.konkuk.platformcore.global.exceptions.BusinessException;
import lombok.RequiredArgsConstructor;

@Service
Expand All @@ -21,8 +21,8 @@ public class CustomUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String memberId) throws UsernameNotFoundException {

Member member = memberRepository.findByMemberId(memberId)
.orElseThrow(()-> InvalidUserInfoException.of(ErrorCode.USER_NOT_FOUND));
.orElseThrow(()-> BusinessException.of(MemberErrorCode.USER_NOT_FOUND));

return new CustomUserDetails(member);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gdsc.konkuk.platformcore.application.member;

import java.util.Optional;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode;
import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException;
import gdsc.konkuk.platformcore.controller.member.MemberRegisterRequest;
import gdsc.konkuk.platformcore.domain.member.entity.Member;
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository;
import gdsc.konkuk.platformcore.global.responses.SuccessResponse;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {

private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;

@Transactional
public SuccessResponse register(MemberRegisterRequest registerRequest) {

if(checkMemberAlreadyExist(registerRequest.getMemberId())) {
throw UserAlreadyExistException.of(MemberErrorCode.USER_ALREADY_EXISTS);
}

String encodedPassword = passwordEncoder.encode(registerRequest.getPassword());
registerRequest.setPassword(encodedPassword);
memberRepository.save(MemberRegisterRequest.toEntity(registerRequest));

return SuccessResponse.messageOnly();
}

private boolean checkMemberAlreadyExist(String memberId) {
Optional<Member> member = memberRepository.findByMemberId(memberId);
return member.isPresent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gdsc.konkuk.platformcore.application.member.exceptions;

import gdsc.konkuk.platformcore.global.exceptions.CustomErrorCode;
import lombok.Getter;

@Getter
public enum MemberErrorCode implements CustomErrorCode {

USER_NOT_FOUND("사용자가 존재하지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보를 찾을 수 없음"),
INVALID_USER_INFO("사용자 정보가 올바르지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보가 올바르지 않음"),
DEACTIVATED_USER("비활성화된 사용자입니다. 다시 확인해주세요", "[ERROR] : 탈퇴한 사용자"),
USER_ALREADY_EXISTS("이미 존재하는 사용자입니다.", "[ERROR] : 이미 존재하는 사용자");

private final String message;
private final String logMessage;

MemberErrorCode(String message, String logMessage) {
this.message = message;
this.logMessage = logMessage;
}


@Override
public String getLogMessage() {
return this.logMessage;
}

@Override
public String getName() {
return this.name();
}

@Override
public String getMessage() {
return this.message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gdsc.konkuk.platformcore.application.member.exceptions;

import gdsc.konkuk.platformcore.global.exceptions.BusinessException;
import gdsc.konkuk.platformcore.global.exceptions.CustomErrorCode;

public class UserAlreadyExistException extends BusinessException {

protected UserAlreadyExistException(CustomErrorCode errorCode, String logMessage) {
super(errorCode, logMessage);
}

public static UserAlreadyExistException of(CustomErrorCode errorCode) {
return new UserAlreadyExistException(errorCode, errorCode.getLogMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package gdsc.konkuk.platformcore.controller.member;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import gdsc.konkuk.platformcore.application.member.MemberService;
import gdsc.konkuk.platformcore.global.responses.SuccessResponse;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/v1/members")
@RequiredArgsConstructor
public class MemberController {

private final MemberService memberService;

@PostMapping()
public ResponseEntity<SuccessResponse> signup(@RequestBody MemberRegisterRequest registerRequest) {
memberService.register(registerRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(SuccessResponse.messageOnly());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gdsc.konkuk.platformcore.controller.member;

import gdsc.konkuk.platformcore.domain.member.entity.Member;
import jakarta.validation.constraints.NotEmpty;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class MemberRegisterRequest {
@NotEmpty
private String memberId;
@NotEmpty
private String password;
@NotEmpty
private String name;
@NotEmpty
private String email;
private int batch;

public static Member toEntity(MemberRegisterRequest request) {
return Member.builder()
.memberId(request.getMemberId())
.password(request.getPassword())
.name(request.getName())
.email(request.getEmail())
.batch(request.getBatch())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
Expand All @@ -22,16 +21,16 @@ public class SecurityConfig {

private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

private static final String API_PREFIX = "/api/v1";
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterBefore(new SecurityContextPersistenceFilter(), BasicAuthenticationFilter.class)

.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/docs/**").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/member").hasRole("MEMBER")
.requestMatchers(apiPath("/docs/**")).permitAll()
.requestMatchers(HttpMethod.POST, apiPath("/members")).permitAll()
.requestMatchers(apiPath("/admin/**")).hasRole("ADMIN")
.anyRequest().authenticated())

.formLogin(login -> login
Expand All @@ -56,4 +55,8 @@ public SecurityFilterChain swaggerFilterchain(HttpSecurity httpSecurity) throws
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

private String apiPath(String path) {
return API_PREFIX + path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,25 @@
import org.springframework.web.bind.annotation.RestControllerAdvice;

import gdsc.konkuk.platformcore.global.exceptions.BusinessException;
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode;
import gdsc.konkuk.platformcore.global.exceptions.GlobalErrorCode;
import gdsc.konkuk.platformcore.global.responses.ErrorResponse;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {


@ExceptionHandler(BusinessException.class)
protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessException e) {
log.error("BusinessException Caught! [{}]", e.getLogMessage());
log.error("BusinessException Caught! [{}]", e.getLogMessage(), e);
final ErrorResponse response = ErrorResponse.of(e.getMessage(), e.getLogMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("Exception Uncaught! [{}]", e.getCause().toString());
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR);
log.error("Exception Uncaught!", e);
final ErrorResponse response = ErrorResponse.of(GlobalErrorCode.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,12 @@ public class BusinessException extends RuntimeException{

private final String logMessage;

protected BusinessException(String message, String logMessage) {
super(message);
this.logMessage = logMessage;
}

protected BusinessException(ErrorCode errorCode, String logMessage) {
protected BusinessException(CustomErrorCode errorCode, String logMessage) {
super(errorCode.getMessage());
this.logMessage = logMessage;
}

public static BusinessException of(ErrorCode errorCode) {
public static BusinessException of(CustomErrorCode errorCode) {
return new BusinessException(errorCode, errorCode.getLogMessage());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gdsc.konkuk.platformcore.global.exceptions;

public interface CustomErrorCode {

String getLogMessage();

String getName();

String getMessage();
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gdsc.konkuk.platformcore.global.exceptions;

import lombok.Getter;

@Getter
public enum GlobalErrorCode implements CustomErrorCode{

// DTO Validation에서 발생한 에러 처리를 위한 코드
ARGUMENT_NOT_VALID("잘못된 입력입니다. 다시 확인해주세요", "[ERROR] : 400 컨트롤러 벨리데이션 실패 잘못된 인자"),

NOT_FOUND("찾을 수 없습니다. 다시 확인해주세요", "[ERROR] : 404 에러 발생"),
INTERNAL_SERVER_ERROR("서버 오류입니다. 잠시후 재시도 해주세요", "[ERROR] : 500 예상치못한 에러 발생");

private final String message;
private final String logMessage;


GlobalErrorCode(String message, String logMessage) {
this.message = message;
this.logMessage = logMessage;
}

@Override
public String getLogMessage() {
return this.logMessage;
}

@Override
public String getName() {
return this.name();
}

@Override
public String getMessage() {
return this.message;
}
}
Loading

0 comments on commit 437488f

Please sign in to comment.