Skip to content
This repository has been archived by the owner on Aug 13, 2022. It is now read-only.

회원가입 및 로그인 기능 추가 #3

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
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
13 changes: 5 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,23 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compile group: 'commons-codec', name: 'commons-codec', version: '1.10'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java' //mysql connect 드라이버 설치
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
compile 'org.springframework.boot:spring-boot-starter-log4j2'
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.10.3'

testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.boot:spring-boot-starter-log4j2'
testImplementation 'mysql:mysql-connector-java'
testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.hamcrest:hamcrest-library:2.2'

// JUnit5의 경우 api, engine을 함께 사용해야 한다.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'

testRuntimeOnly 'com.h2database:h2'
}

test {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package me.naming.onlineshoppingmall.config;

import java.time.LocalDateTime;
import me.naming.onlineshoppingmall.domain.ResponseData;
import me.naming.onlineshoppingmall.exception.MessageException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

// @Controller or @RestController에서 발생하는 예외를 관리 할 수 있게 만들어주는 어노테이션
// @ExceptionHandler를 통해 각각의 예외처리 메세지를 원하는 형태로 작성 할 수 있다.
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(value = { MessageException.class })
protected ResponseEntity handleConflict(MessageException messageException, WebRequest request) {
String path = ((ServletWebRequest)request).getRequest().getRequestURI();
int code;

if(messageException.getStatusCode() != 0) {
code = messageException.getStatusCode();
} else {
code = HttpStatus.INTERNAL_SERVER_ERROR.value();
}

ResponseData responseData = ResponseData.builder()
.localDateTime(LocalDateTime.now())
.statusCode(code)
Copy link
Member

Choose a reason for hiding this comment

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

이렇게하면 status code가 body에 들어가지 않을까요?

.path(path)
.message(messageException.getMessage())
.build();

return new ResponseEntity<>(responseData, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

//상수만을 관리하기 위한 클래스
public class CommonConstant {

private CommonConstant() {};

public static final String LOGIN_INFO = "loginInfo";
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
package me.naming.onlineshoppingmall.controller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import me.naming.onlineshoppingmall.domain.Member;
import me.naming.onlineshoppingmall.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class MemberController {

private static final Logger logger = LogManager.getLogger(MemberController.class);
private final MemberService memberService;

@GetMapping
public void hello() {
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}

// 회원가입
@PostMapping(value = "/members/signup")
@ResponseStatus(HttpStatus.CREATED)
public void memberJoin(@RequestBody @Valid Member member) {
memberService.signUp(member);
}

// 회원정보 갖고오기
@GetMapping(value = "/members/{memNo}")
@ResponseStatus(HttpStatus.OK)
public Member getMemberInfo(@PathVariable(name = "memNo") Long memNo) {
return memberService.getMemberInfo(memNo);
}

@GetMapping(value = "/login")
@ResponseStatus(HttpStatus.OK)
public void login(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
memberService.doLogin(userLoginRequest.getEmail(), userLoginRequest.getPassword(), request);
}

@Getter
private static class UserLoginRequest {
@NonNull String email;
@NonNull String password;
}
}
67 changes: 47 additions & 20 deletions src/main/java/me/naming/onlineshoppingmall/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.naming.onlineshoppingmall.domain;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
Expand All @@ -9,59 +10,85 @@
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import lombok.Setter;
import lombok.ToString;

@Entity
@Table(name = "MEMBERS")
@Getter
@NoArgsConstructor
@ToString
public class Member {

@Builder
public Member(String email, String password, String name, String gender, Date birthDate) {
this.email = email;
this.password = password;
this.name = name;
this.gender = gender;
this.birthDate = birthDate;
}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEM_NO")
private Long memNo;

@Column
@Column(name = "EMAIL")
@NotNull
@Email(message = "이메일 형식에 맞지 않습니다.")
private String email;

@Column
// 영문(소문자, 대문자), 숫자, 특수문자 조합 9~12자리 조합
@Column(name = "PASSWORD")
@NotNull
@Setter
@Pattern(regexp = "^(?=.*\\d)(?=.*[~`!@#$%\\^&*()-])(?=.*[a-z])(?=.*[A-Z]).{9,12}$",
message = "비밀번호는 영문 대,소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 9자 ~ 12자의 비밀번호여야 합니다.")
private String password;

@Column
@Column(name = "NAME", length = 30)
@NotBlank(message = "이름은 필수 입력 값입니다.")
@NotNull
private String name;

@Column(name = "GENDER", length = 1)
@NotBlank(message = "성별은 필수 입력 값입니다.")
@NotNull
private String gender;

@Temporal(TemporalType.TIMESTAMP)
@Column
@Column(name = "STATUS")
@Setter
private MemberStatus memberStatus;

@Column(name = "HP", length = 11)
@NotNull
private Long hp;

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "BIRTH_DTS") @NotNull
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy/MM/dd", timezone = "Asia/Seoul)")
private Date birthDate;

@Temporal(TemporalType.TIMESTAMP)
@Column
@CreationTimestamp
@Column(name = "REG_DTS", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date regDts;

@Temporal(TemporalType.TIMESTAMP)
@Column
@CreationTimestamp
@Column(name = "MOD_DTS", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date modDts;
}

public enum MemberStatus{
// DEFAULT(가입), DELETE(탈퇴), DORMANT(휴면)
DEFAULT, DELETE, DORMANT
}

@Builder
public Member(String email, String password, String name, String gender, MemberStatus memberStatus, Date birthDate, Long hp) {
this.email = email;
this.password = password;
this.name = name;
this.gender = gender;
this.memberStatus = memberStatus;
this.birthDate = birthDate;
this.hp = hp;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package me.naming.onlineshoppingmall.domain;

import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.springframework.http.HttpStatus;

@Builder
@ToString
@Getter
public class ResponseData {
private LocalDateTime localDateTime;
private int statusCode;
private String path;
private String message;

public ResponseData(LocalDateTime localDateTime, int statusCode, String path, String message) {
this.localDateTime = localDateTime;
this.statusCode = statusCode;
this.path = path;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.naming.onlineshoppingmall.exception;

import org.springframework.http.HttpStatus;

public class MessageException extends RuntimeException{

private int statusCode;

public MessageException(String message) {
super(message);
this.statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
}

public MessageException(String message, int statusCode) {
super(message);
this.statusCode = statusCode;
}

public int getStatusCode() {
return statusCode;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@

public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByMemNo(Long memNo);
}
Member findByEmail(String email);
Member findMemnoAndPasswordByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package me.naming.onlineshoppingmall.service;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class AESServiceImpl implements CryptoService{

@Value("${encrypt.aesKey}")
private String aesKey;

private static final Logger logger = LogManager.getLogger(AESServiceImpl.class);

@Override
public String encrypt(String plainText) {

byte[] encrypted = new byte[0];

try {
SecretKeySpec keySpec = getKeySpec();

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(aesKey.substring(0, 16).getBytes()));
encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8.name()));

} catch (GeneralSecurityException e) {
logger.error("** GeneralSecurityException : {}", e);
} catch (UnsupportedEncodingException e) {
logger.error("** UnsupportedEncodingException : {}", e);
}
return new String(Base64.encodeBase64(encrypted));
}

@Override
public String decrypt(String encryptText) {
String aesDecode = null;

try {
SecretKeySpec keySpec = getKeySpec();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(aesKey.substring(0, 16).getBytes(StandardCharsets.UTF_8.name())));

byte[] byteStr = Base64.decodeBase64(encryptText.getBytes());
aesDecode = new String(cipher.doFinal(byteStr), StandardCharsets.UTF_8.name());

} catch (Exception e) {
logger.error("** Decode UnsupportedEncodingException : {}", e);
Copy link
Member

Choose a reason for hiding this comment

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

그냥 아래의 내용들과 메세지만 약간씩 다른것같은데 한번에 처리할 수 있지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Exception으로 모든 예외를 한꺼번에 처리해줄 수 있을 수 있지만, 예외 상황을 세부적으로 파악하기 위해 코드 내용을 작성해주는게 좋진 않을지 궁금합니다.

피드백 해주신 내용은 현재 GeneralSecurityExceptionUnsupportedEncodingException로 예외처리 했습니다.

}

if(StringUtils.isEmpty(aesDecode)) throw new RuntimeException("복호화 에러");
return aesDecode;
}

private SecretKeySpec getKeySpec() throws UnsupportedEncodingException {
byte[] keyBytes = new byte[16];
byte[] defaultByte = aesKey.substring(0, 16).getBytes(StandardCharsets.UTF_8.name());

int len = defaultByte.length;
if (len > keyBytes.length) {
len = keyBytes.length;
}

System.arraycopy(defaultByte, 0, keyBytes, 0, len);
return new SecretKeySpec(keyBytes, "AES");
}
}
Loading