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

Commit

Permalink
Add 회원가입 기능
Browse files Browse the repository at this point in the history
- 회원가입 기능을 통해 Careers를 로그인 한 사용자들만 이용할 수 있도록 한다.
- 이름, 이메일, 비밀번호 모두를 입력 받는다.(Null 체크)
- 이메일은 이메일 형식에 맞게 작성해야 한다.
- 비밀번호는 문자, 숫자, 특수문자로 구성되어야 한다.
- 비밀번호는 암호화하여 DB에 저장한다.(sha-256)
- 중복된 이메일인지 체크하여 중복가입을 제한한다.

#3
  • Loading branch information
phantom08266 committed Mar 7, 2021
1 parent c29bca5 commit 7d57c84
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 3 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ dependencies {
implementation group: 'mysql', name: 'mysql-connector-java'
implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.1.4'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-log4j2'
// 요청파라미터 검증하기 위해 추가
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '2.4.3'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
6 changes: 6 additions & 0 deletions sql/ddl.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
create table Curator(
email varchar(64) not null primary key,
username varchar(64) not null,
password varchar(64) not null,
salt varchar(64) not null
);
40 changes: 40 additions & 0 deletions src/main/java/com/dev/careers/controller/CuratorController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.dev.careers.controller;

import com.dev.careers.model.Curator;
import com.dev.careers.service.CuratorService;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.List;
import java.util.Optional;

@RestController
public class CuratorController {

private final CuratorService curatorService;

public CuratorController(CuratorService curatorService) {
this.curatorService = curatorService;
}

@PutMapping("/curators/join")
public String putMember(@Valid @ModelAttribute Curator curator, BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()){
Optional<ObjectError> objectError = bindingResult.getAllErrors().stream().findFirst();
if (objectError.isPresent()){
return objectError.get().getDefaultMessage();
}
}
return curatorService.join(curator);
}

@GetMapping("/curators/all")
public List<Curator> getMembers(){
return curatorService.getMembers();
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/dev/careers/mapper/CuratorMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.dev.careers.mapper;

import com.dev.careers.model.Curator;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@Mapper
public interface CuratorMapper {

Integer insertCurator(
@Param("email") String email,
@Param("name") String name,
@Param("password") String password,
@Param("salt") String salt);

List<Curator> getCurators();
}
25 changes: 25 additions & 0 deletions src/main/java/com/dev/careers/model/Curator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.dev.careers.model;

import lombok.Data;
import lombok.NonNull;
import org.springframework.lang.Nullable;

import javax.validation.constraints.Email;
import javax.validation.constraints.Pattern;


@Data
public class Curator {
@NonNull
@Email(message = "Email Format Violation")
private String email;
@NonNull
private String name;
@NonNull
//최소 8자리에 숫자, 문자, 특수문자 각각 1개 이상 포함
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$",
message = "Password Format Violation")
private String password;
@Nullable
private String salt;
}
77 changes: 77 additions & 0 deletions src/main/java/com/dev/careers/service/CuratorService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.dev.careers.service;

import com.dev.careers.mapper.CuratorMapper;
import com.dev.careers.model.Curator;
import org.springframework.stereotype.Service;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;

@Service
public class CuratorService {
private final CuratorMapper curatorMapper;
private final int SALT_SIZE = 16;

public CuratorService(CuratorMapper curatorMapper) {
this.curatorMapper = curatorMapper;
}

public String join(Curator curator) throws NoSuchAlgorithmException {
//중복검증
Optional<Curator> memberOptional = getMembers()
.stream()
.filter(m -> m.getEmail().equals(curator.getEmail()))
.findAny();

if (memberOptional.isPresent()) {
return "Duplicated Email";
} else {
String salt = makeSalt();

curatorMapper.insertCurator(
curator.getEmail(),
curator.getName(),
hashing(curator.getPassword().getBytes(), salt),
salt);

return "Success";
}
}

private String makeSalt() {
SecureRandom srd = new SecureRandom();
byte[] data = new byte[SALT_SIZE];
srd.nextBytes(data);

return byteArrayToString(data);
}

//Salt와 키 스트레칭 방식으로 구현
private String hashing(byte[] password, String salt) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");

StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 10; i++) {
buffer.append(byteArrayToString(password));
buffer.append(salt);
md.update(buffer.toString().getBytes());
password = md.digest();
}
return new String(password);
}

public String byteArrayToString(byte[] bytes) {
StringBuilder builder = new StringBuilder();
for (byte data : bytes) {
builder.append(String.format("%02X ", data));
}
return builder.toString();
}

public List<Curator> getMembers() {
return curatorMapper.getCurators();
}
}
10 changes: 7 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
spring.datasource.url=jdbc:mysql://localhost:3306/<디비이름>?useUnicode=true@characterEncoding=utf8&serverTimezone=Asia/Seoul
spring.datasource.username=<유저이름>
spring.datasource.password=<유저비밀번호>
spring.datasource.url=jdbc:mysql://localhost:3306/Careers?useUnicode=true@characterEncoding=utf8&serverTimezone=Asia/Seoul
#<유저이름>
spring.datasource.username=junehee
# <유저비밀번호>
spring.datasource.password=wnsgml12
mybatis.type-aliases-package=com.dev.careers.model
mybatis.mapper-locations=mybatis/*.xml
11 changes: 11 additions & 0 deletions src/main/resources/mybatis/CuratorMapper.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dev.careers.mapper.CuratorMapper">
<select id="insertCurator" resultType="int">
INSERT INTO Curator VALUES(#{email}, #{name}, #{password}, #{salt});
</select>

<select id="getCurators" resultType="Curator">
SELECT * from Curator;
</select>
</mapper>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.dev.careers.controller;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@Transactional
class CuratorControllerTest {

@Autowired
CuratorController curatorController;
MockMvc mockMvc;

@BeforeEach
public void beforeEach(){
mockMvc = MockMvcBuilders.standaloneSetup(curatorController).build();
}

@Test
@DisplayName("정상적인 회원가입")
public void joinCurator() throws Exception {
mockMvc.perform(put("/curators/join")
.param("email","test@google.com")
.param("name", "홍길동")
.param("password", "test123!@"))
.andDo(print())
.andExpect(status().isOk());
}

@Test
@DisplayName("잘못된 이메일 형식 요청")
public void violationEmail() throws Exception {
mockMvc.perform(put("/curators/join")
.param("email", "test123.com")
.param("name", "홍길동")
.param("password", "test123!@"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("Email Format Violation"));
}

@Test
@DisplayName("잘못된 비밀번호 형식 요청")
public void violationPassword() throws Exception {
mockMvc.perform(put("/curators/join")
.param("email", "test@google.com")
.param("name", "홍길동")
.param("password", "123"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("Password Format Violation"));
}
}
32 changes: 32 additions & 0 deletions src/test/java/com/dev/careers/service/CuratorServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.dev.careers.service;

import com.dev.careers.model.Curator;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@SpringBootTest
class CuratorServiceTest {

@Autowired
CuratorService curatorService;

@Test
@DisplayName("중복된 이메일 회원가입 요청")
public void DupicatedEmail() throws Exception {
Curator curator = new Curator(
"test@google.com",
"홍길동",
"test123!@"
);
Assertions.assertThat(curatorService.join(curator)).isEqualTo("Success");
org.junit.jupiter.api.Assertions.assertThrows(
DuplicateKeyException.class,
()->curatorService.join(curator));
}
}

0 comments on commit 7d57c84

Please sign in to comment.