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

Commit

Permalink
로그인 기능 추가 (#6)
Browse files Browse the repository at this point in the history
* Add 회원가입 기능

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

#3

* Fix 디비 primary key id로 수정

* Fix 큐레이터 테이블 unique index 설정

* Fix 회원가입 http method 변경

put -> post

* Fix 롬복사용하여 생성자 자동주입

* Fix 사용하지 않는 요청 메서드 제거

* Fix 이메일 중복 예외처리

* Fix http status code 처리

이메일, 비밀번호 위반 시 status code 반환하도록 수정

* Fix model코드 가독성 처리

* Fix 암호화코드와 유저서비스 기능 분리

단일책임원칙 적용

* Fix Select쿼리문 email컬럼 명시적으로 표현

* Fix mysql 사용자계정 변경

* Fix 롬복 Data 애노테이션 제거

명확한 의도를 설정하기위해 Data 애노테이션 대신 각각 설정

* Fix 롬복 애노테이션 정리

* Fix StringBuffer를 StringBuilder로 변경

여러 스레드가 해당 부분을 접근할 일이 현재로서는 없으므로 StringBuilder로 수정

* Fix 사용하지 않는 애노테이션 제거

* Update src/main/java/com/dev/careers/service/encryption/SHA256Encryption.java

Fix 코딩 컨벤션 위반 수정

Co-authored-by: f-lab <54677861+f-lab-dev@users.noreply.github.com>

* Fix 인터페이스, 클래스 명명규칙 수정

Encryption -> Encryptor 로 변경

* Fix 메소드 명명규칙 수정

* Add 로그인 기능 추가

- 이메일, 비밀번호를 입력받는다.(Null 체크)
- DB에 등록되어있는 회원인지 체크한다.
- 앱 종료 후 앱을 실행하면 자동 로그인이 되어 사용 가능하도록 한다.(세션방식)

#4

* Fix 요청에따른 결과 반환 void로 수정

* Fix void반환으로 인한 테스트케이스 수정

* Fix 성능보완을 위해 쿼리로 중복검증 체크

* Fix 성능향상을 위해 쿼리문 수정

exists를 사용하여 쿼리성능향상 및 이메일 존재 유무에 대해 명확하게 표현

* Fix Optional 사용하여 코드정리

* Fix HttpSession 으로 세션처리

* Fix 서비스로 세션로직 분리

Controller에서 처리한 세션로직을 Service에서 처리하도록 처리
단일책임원칙을 준수하도록하여 별도의 SessionController클래스생성

* Fix checkstyle 위반된 부분 수정

카멜케이스 적용

* Fix session처리 클래스 이름변경

* Fix 클래스 이름변경에 따른 누락된 부분 커밋

* Fix 로그인 인증부분 SRP적용

로그인/로그아웃에 대한 세션처리부분을 SessionAuthenticator에서 담당하도록 수정

* Fix 세션을 주입 처리

- SetAttribute를 호춣한 시점에 Servlet Container가 생성한 Session을 주입받도록 수정
- Session 인증 처리 메서드 이름 변경

* Fix session처리 메소드 이름변경

Co-authored-by: f-lab <54677861+f-lab-dev@users.noreply.github.com>
  • Loading branch information
phantom08266 and f-lab-dev committed May 2, 2021
1 parent 60ed0cc commit 1a96838
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 22 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.session', name: 'spring-session-core'
// 요청파라미터 검증하기 위해 추가
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '2.4.3'
//javax.annotation.meta.When 경고로 인해 구글에서 해당 버그 수정한 의존성 추가
Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/dev/careers/CareersApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ public class CareersApplication {
public static void main(String[] args) {
SpringApplication.run(CareersApplication.class, args);
}

}
36 changes: 32 additions & 4 deletions src/main/java/com/dev/careers/controller/CuratorController.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,62 @@
package com.dev.careers.controller;

import com.dev.careers.model.Curator;
import com.dev.careers.model.LoginParamter;
import com.dev.careers.service.CuratorService;
import com.dev.careers.service.error.ViolationException;
import com.dev.careers.service.session.SessionAuthenticator;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RequiredArgsConstructor
@RestController
@RequestMapping("curators")
public class CuratorController {

private final CuratorService curatorService;
private final SessionAuthenticator sessionAuthenticator;

@PostMapping("/curators/join")
public void putMember(@Valid @ModelAttribute Curator curator, BindingResult bindingResult)
@PostMapping("join")
public void joinMember(@Valid @ModelAttribute Curator curator, BindingResult bindingResult)
throws Exception {
verifyCuratorParameter(bindingResult);

curatorService.join(curator);
}

@PostMapping("login")
public void loginMember(@Valid @ModelAttribute LoginParamter loginParamter,
HttpSession httpSession,
BindingResult bindingResult)
throws NoSuchAlgorithmException {

verifyCuratorParameter(bindingResult);
curatorService.login(loginParamter);
sessionAuthenticator.login(loginParamter);
}

@DeleteMapping("logout")
public void logout() {
sessionAuthenticator.logout();
}

public void verifyCuratorParameter(BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Optional<ObjectError> objectError = bindingResult.getAllErrors().stream().findFirst();
if (objectError.isPresent()) {
throw new ViolationException();
}
}

curatorService.join(curator);
}
}
11 changes: 7 additions & 4 deletions src/main/java/com/dev/careers/mapper/CuratorMapper.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package com.dev.careers.mapper;

import java.util.HashMap;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface CuratorMapper {

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

HashMap<String, String> getMemberInfo(@Param("email") String email);

boolean checkEmailExists(@Param("email") String email);
}
10 changes: 6 additions & 4 deletions src/main/java/com/dev/careers/model/Curator.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,24 @@
@Getter
@RequiredArgsConstructor
public class Curator {

@Nullable
private int id;

@NonNull
@Email(message = "Email Format Violation")
private String email;

@NonNull
private String name;

//최소 8자리에 숫자, 문자, 특수문자 각각 1개 이상 포함
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$",
message = "Password Format Violation")
message = "Password Format Violation")
@NonNull
private String password;

@Nullable
private String salt;

}
22 changes: 22 additions & 0 deletions src/main/java/com/dev/careers/model/LoginParamter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.dev.careers.model;

import javax.validation.constraints.Email;
import javax.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;

@Getter
@AllArgsConstructor
public class LoginParamter {

@NonNull
@Email(message = "Email Format Violation")
private String email;

//최소 8자리에 숫자, 문자, 특수문자 각각 1개 이상 포함
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$",
message = "Password Format Violation")
@NonNull
private String password;
}
23 changes: 21 additions & 2 deletions src/main/java/com/dev/careers/service/CuratorService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,47 @@

import com.dev.careers.mapper.CuratorMapper;
import com.dev.careers.model.Curator;
import com.dev.careers.model.LoginParamter;
import com.dev.careers.service.encryption.PasswordEncryptor;
import com.dev.careers.service.error.DuplicatedEmailException;
import com.dev.careers.service.error.ViolationException;
import com.dev.careers.service.session.SessionAuthenticator;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class CuratorService {

private final CuratorMapper curatorMapper;
private final PasswordEncryptor passwordEncryptor;
private final SessionAuthenticator sessionAuthenticator;

public void join(Curator curator) throws NoSuchAlgorithmException {
//중복검증
if (curatorMapper.checkEmailExists(curator.getEmail()))
if (curatorMapper.checkEmailExists(curator.getEmail())) {
throw new DuplicatedEmailException();

}
String salt = passwordEncryptor.makeSalt();
curatorMapper.insertCurator(
curator.getEmail(),
curator.getName(),
passwordEncryptor.hashing(curator.getPassword().getBytes(), salt),
salt);
}

public void login(LoginParamter loginParamter) throws NoSuchAlgorithmException {
Optional<HashMap<String, String>> memberInfo = Optional
.ofNullable(curatorMapper.getMemberInfo(loginParamter.getEmail()));

String salt = memberInfo.map(v -> v.get("salt"))
.orElse("test");

String hashing = passwordEncryptor.hashing(loginParamter.getPassword().getBytes(), salt);
memberInfo.filter(v -> hashing.equals(v.get("password")))
.orElseThrow(ViolationException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.dev.careers.service.session;

import com.dev.careers.model.LoginParamter;
import java.util.Optional;
import javax.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SessionAuthenticator {

private final static String sessionName = "sessionInfo";
private final HttpSession httpSession;

public SessionAuthenticator(HttpSession httpSession) {
this.httpSession = httpSession;
}

public void login(LoginParamter loginParamter) {
httpSession.setAttribute(sessionName,loginParamter);
}

public void logout() {
httpSession.removeAttribute(sessionName);
}
}
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ spring.datasource.username=dev
spring.datasource.password=1234
mybatis.type-aliases-package=com.dev.careers.model
mybatis.mapper-locations=mybatis/*.xml
server.servlet.session.timeout=60
7 changes: 7 additions & 0 deletions src/main/resources/mybatis/CuratorMapper.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
VALUES (#{email}, #{name}, #{password}, #{salt});
</select>

<select id="getMemberInfo" resultType="hashmap">
select email, password, salt
from Curator
where email = #{email};
</select>

<select id="checkEmailExists" resultType="boolean">
select exists(select 1 from curator where email = #{email});
</select>

</mapper>
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.dev.careers.controller;

import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
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.request;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.dev.careers.service.error.CuratorExceptionHandler;
Expand All @@ -12,6 +14,7 @@
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.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -24,9 +27,15 @@ class CuratorControllerTest {
MockMvc mockMvc;

@BeforeEach
public void beforeEach() {
public void beforeEach() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(curatorController)
.setControllerAdvice(new CuratorExceptionHandler()).build();

mockMvc.perform(post("/curators/join")
.param("email", "aaa@google.com")
.param("name", "aaa")
.param("password", "test123!@"))
.andExpect(status().isOk());
}

@Test
Expand Down Expand Up @@ -55,10 +64,20 @@ public void violationEmail() throws Exception {
@DisplayName("잘못된 비밀번호 형식 요청")
public void violationPassword() throws Exception {
mockMvc.perform(post("/curators/join")
.param("email", "test@google.com")
.param("name", "홍길동")
.param("password", "123"))
.andDo(print())
.andExpect(status().isBadRequest());
.param("email", "test@google.com")
.param("name", "홍길동")
.param("password", "123"))
.andDo(print())
.andExpect(status().isBadRequest());
}

@Test
@DisplayName("로그인 시 세션발급 확인")
public void checkLoginSession() throws Exception {
mockMvc.perform(post("/curators/login")
.param("email", "aaa@google.com")
.param("password", "test123!@"))
.andExpect(status().isOk())
.andExpect(request().sessionAttribute("sessionInfo", notNullValue()));
}
}
60 changes: 60 additions & 0 deletions src/test/java/com/dev/careers/service/CuratorServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.dev.careers.service;

import com.dev.careers.model.Curator;
import com.dev.careers.model.LoginParamter;
import com.dev.careers.service.error.ViolationException;
import java.security.NoSuchAlgorithmException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -28,4 +31,61 @@ public void dupicatedEmail() throws Exception {
DuplicateKeyException.class,
() -> curatorService.join(curator));
}

@Test
@DisplayName("로그인 성공")
public void successLogin() throws NoSuchAlgorithmException {
Curator curator = new Curator(
"test@google.com",
"홍길동",
"test123!@"
);
curatorService.join(curator);

LoginParamter paramter = new LoginParamter(
"test@google.com",
"test123!@"
);

org.junit.jupiter.api.Assertions.assertDoesNotThrow(
() -> curatorService.login(paramter));
}

@Test
@DisplayName("비밀번호 오류로 인한 로그인 실패")
public void failToLogin() throws NoSuchAlgorithmException {
Curator curator = new Curator(
"test@google.com",
"홍길동",
"test123!@"
);
curatorService.join(curator);

LoginParamter paramter = new LoginParamter(
"test@google.com",
"test445@#"
);
org.junit.jupiter.api.Assertions.assertThrows(
ViolationException.class,
() -> curatorService.login(paramter));
}

@Test
@DisplayName("가입하지 않는 이메일로 인한 로그인 실패")
public void failToLogin2() throws NoSuchAlgorithmException {
Curator curator = new Curator(
"test@google.com",
"홍길동",
"test123!@"
);
curatorService.join(curator);

LoginParamter paramter = new LoginParamter(
"fff@google.com",
"test445@#"
);
org.junit.jupiter.api.Assertions.assertThrows(
ViolationException.class,
() -> curatorService.login(paramter));
}
}

0 comments on commit 1a96838

Please sign in to comment.