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

Conversation

Daniel-Taeyoun
Copy link
Collaborator

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

build.gradle Outdated Show resolved Hide resolved
.path(path)
.message(ex.getMessage())
.build();
return handleExceptionInternal(ex, gson.toJson(responseData), httpHeaders, HttpStatus.INTERNAL_SERVER_ERROR, request);
Copy link
Member

Choose a reason for hiding this comment

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

너무 문제를 복잡하게 해결하신 것 같은데 이런 에러메세지를 전달하는 로직을 더 심플하게 짜는법을 알아보시면 좋겠습니다~

@ResponseStatus(HttpStatus.OK)
public void login(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
HttpSession httpSession = request.getSession();
httpSession.setAttribute("loginInfo", memberService.getLoginInfo(userLoginRequest.getEmail(), userLoginRequest.getPassword()));
Copy link
Member

Choose a reason for hiding this comment

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

  1. 로그인 처리는 비즈니스 로직 같습니다. controller에서 처리해줘야할까요?
  2. 패스워드를 응답에 넣어주면 보안에 취약하지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

로그인 처리는 비즈니스 로직이기 때문에 서비스 부분에서 처리해주었습니다.
또한, 패스워드의 경우 응답 값에 넣어주는 것이 아니라 회원 정보 조회의 경우에만 사용되고, 회원번호를 세션 정보로 전달합니다.

Member findByEmail(String email);

@Query("select mb.memNo from Member mb where mb.email = ?1 and mb.password = ?2")
Long findMemnoForLoginInfo(String email, String password);
Copy link
Member

Choose a reason for hiding this comment

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

  1. findMemnoByLoginInfo 가 더 자연스러울 것 같네요
  2. 굳이 이렇게 쿼리를 하나 선언하는것보다 email로 조회해서 password를 검증하는 메소드를 만드는게 더 낫지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

말씀해주신 피드백 내용 반영했습니다. 감사합니다.

@Autowired
private MemberRepository memberRepository;

@Autowired
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.

생성자 주입으로 사용할 경우 주입받을 필드 값을 final로 선언이 가능하고 순환 참조를 방지 할 수 있습니다.
또한, Mockito를 활용한 테스트 코드 작성 시 원하는 구현체를 유연하게 주입 할 수 있습니다.

public String getLoginInfo(String email, String password) {
Long memNo = memberRepository.findMemnoForLoginInfo(email, password);
String encodeMemNo = encryptUtil.aesEncode(String.valueOf(memNo));
logger.info("** encodeMemNo : {}", encodeMemNo);
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.

넵 해당 부분은 삭제했습니다. 감사합니다.

Copy link
Member

Choose a reason for hiding this comment

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

만약 디버깅에 꼭 필요하다면 레벨을 낮추는것도 괜찮습니다~

// //given
// MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
// when(memberRepository.findMemnoAndPasswordByEmail(EMAIL)).thenReturn(testMember);
// memberService = new MemberService(shaService, aesService, memberRepository);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

로그인 성공에 대한 테스트 코드 작성 중 SHAServiceImpl 클래스에 대한 의존성으로 인해 적절한 테스트 코드를 작성하지 못해 주석처리했습니다. 이 부분은 어떻게 테스트 코드 작성해야 할지 의문입니다..

Copy link
Member

Choose a reason for hiding this comment

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

일단 memberService는 필드로 선언하는게 나을 것 같고요.
의존성은 mocking 처리하면 되는데 어디에서 헷갈리시는걸까요?

build.gradle Outdated
@@ -49,10 +47,15 @@ dependencies {
testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.hamcrest:hamcrest-library:2.2'

/**
* (10/11) Intellij 내부적으로 JUnit을 지원해주고 있습니다.
Copy link
Member

Choose a reason for hiding this comment

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

Intellij 지원이면 intellij가 아닌 환경에서는 에러가 나지 않나요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

해당 내용은 개인적으로 블로그 글을 잘못 이해했습니다. 스프링 내부(org.springframework.boot:spring-boot-starter-test)에서 jupiter를 제공해주고 있으며, 따라서 추가했던 jupiter는 삭제했습니다.

@@ -18,10 +17,9 @@
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(value = { MessageException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
@ExceptionHandler(value = { RuntimeException.class })
Copy link
Member

Choose a reason for hiding this comment

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

RuntimeException의 하위 클래스에는 여러가지가 있습니다. 그 예외가 전부 서버의 책임이라고 핸들링하고 있는 것으로 보이네요~

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

해당 피드백 내용은 코드에 반영했습니다.

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

Choose a reason for hiding this comment

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

Pull request는 머지를 위한 것입니다. 주석을 실서버 소스에 머지해도 괜찮을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아닙니다. 해당 피드백 내용은 코드에 반영했습니다.

aesDecode = new String(cipher.doFinal(byteStr), StandardCharsets.UTF_8.name());

} catch (UnsupportedEncodingException 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로 예외처리 했습니다.

throw new RuntimeException("비밀번호가 틀렸습니다");
}

String encodeMemNo = aesService.encrypt(String.valueOf(member.getMemNo()));
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.

세션의 경우 서버 메모리에 존재하기 때문에 굳이 암호화 할 필요는 없다고 생각합니다.
따라서 해당 코드 내용은 암호화 하지 않은 평문으로 회원번호가 저장될 수 있게 수정했습니다.

}

String encodeMemNo = aesService.encrypt(String.valueOf(member.getMemNo()));
httpSession.setAttribute("loginInfo", encodeMemNo);
Copy link
Member

Choose a reason for hiding this comment

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

"loginInfo" 같은 값은 상수로 관리하는게 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

해당 피드백 내용은 코드에 반영했습니다.

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

private static final Logger logger = LogManager.getLogger(SHAServiceImpl.class);
Copy link
Member

Choose a reason for hiding this comment

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

이것도 lombok을 사용할 수 있지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

해당 피드백 내용은 코드에 반영했습니다.

}

@Test
public void 로그인_실패_비밀번호_일치_X() {
Copy link
Member

Choose a reason for hiding this comment

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

메소드 이름을 한글로 해주지 않아도 junit5의 기능을 이용하면 테스트에 이름을 지어줄 수 있습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

해당 피드백 내용은 코드에 반영했습니다.

// //given
// MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
// when(memberRepository.findMemnoAndPasswordByEmail(EMAIL)).thenReturn(testMember);
// memberService = new MemberService(shaService, aesService, memberRepository);
Copy link
Member

Choose a reason for hiding this comment

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

일단 memberService는 필드로 선언하는게 나을 것 같고요.
의존성은 mocking 처리하면 되는데 어디에서 헷갈리시는걸까요?

@Daniel-Taeyoun
Copy link
Collaborator Author

해당 코드에서 세션 정보 저장 시 회원번호(memNo)를 저장하게끔 코드를 작성했습니다. 하지만, 회원번호의 경우 DB에서 자동 생성해주고 있어서 이런 경우 어떻게 해야할지 의문입니다.

이런 경우 @DataJpaTest 를 추가하여 테스트 코드를 작성하는 것이 적절할까요? 아님 따로 회원번호에 관한 Mock을 생성해줄 수 있는지 궁금합니다.

Copy link
Member

@f-lab-dev f-lab-dev left a comment

Choose a reason for hiding this comment

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

질문해주신 내용은 적절하게 mocking을 해주면 될 것 같네요~
단위테스트를 작성하는데 DB에서 자동생성해준다라는 사실이 중요할까요?


ResponseData responseData = ResponseData.builder()
.localDateTime(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.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에 들어가지 않을까요?

@@ -99,12 +102,12 @@ public void setUp() {
}

@Test
public void 로그인_실패_비밀번호_일치_X() {
@DisplayName("로그인_실패_비밀번호_일치_X")
Copy link
Member

Choose a reason for hiding this comment

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

  • 여기는 띄어쓰기를 쓰셔도 되는데 굳이 언더바를 넣어주신 이유가 무엇인가요?
  • 서술형으로 쓰는데 X 같은 말을 쓰는것은 프로페셔널해보이지 않네요~

* - https://d2.naver.com/helloworld/318732
* - https://dailyworker.github.io/AES-Algorithm-and-Chiper-mode/
* - https://javaplant.tistory.com/26
*/
public interface CryptoService {
Copy link
Member

Choose a reason for hiding this comment

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

어떤 의도의 추상화인지 의도는 주석에 남겨주시는게 좋겠네요

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants