로그인 기능을 만들어보자
로그인 기능을 만들기 위해 궁금한 점을 하나씩 해결해보겠습니다.
1. 로그인 방식은 POST일까 GET일까?
로그인은 사용자의 정보를 조회하는 것이니까 GET을 써야 하지 않을까?라고 생각했습니다.
하지만 GET 방식의 경우 입력받은 정보들을 URL 쿼리문으로 보내며 POST 방식은 body로 감싸서 보냅니다.
즉, GET의 경우에는 그대로 노출되고 POST의 경우에는 보안성이 증가됩니다.
로그인의 경우에는 민감한 정보로 보안이 필요하기 때문에 POST를 사용합니다.
하지만 만약 SSL을 사용한다면 GET/ POST둘다 암호화되기 때문에 GET을 사용하면 될 것 같다고 생각했습니다.
하지만 로그인하는 과정을 통해서 토큰 / 세션이 생성된다면 이는 POST가 또 맞다고 볼 수 있습니다.
또한 사용자가 URL을 복사하거나 화면캡쳐등을 사용하여 정보가 노출될 수 있으므로 일반적으로 POST를 사용하는 것이 맞는 것 같습니다.
결론 POST를 사용하자
2. 사용자가 입력한 값을 컨트롤러에 어떻게 받아와야 할까?
컨트롤러에 인자값으로 받을 수 있는 다양한 방법들이 존재합니다.
https://junuuu.tistory.com/283?category=997278
Controller에 인자값은 어떤값이 올 수 있을까?
Controller가 매우 다양한 인자 값을 받아올 수 있기 때문에 어떤 값들을 받아 올 수 있는지 어떻게 받아오는 게 적절할지 조사해보고자 합니다. Spring Controller에서 Parameter를 받는 방식 HttpServletReques
junuuu.tistory.com
@ModelAttribute와 @RequestBody 2가지 선택지 중에서 고민을 하였습니다.
@ModelAttribute는 파라미터 값으로 DTO객체에 바인딩을 하는 방식이기 때문에 바인딩하려는 DTO객체에 Setter메서드가 반드시 있어야 합니다.
@RequestBody는 요청 본문의 body에 json이나 xml값으로 요청을 하여 HttpMessageConveter를 반드시 거쳐 DTO객체에 맞는 타입으로 바꿔서 바인딩을 시켜준다.
결론 여기서는 추가적인 Setter메서드가 필요 없는 ReqeustBody를 통해 받도록 하겠습니다.
3. 컨트롤러에서 어떤 상태코드를 반환해야 할까?
요청이 정상적으로 처리되었을때는 200 OK 상태 코드를 반환합니다.
만약 계정의 비밀번호나 아이디가 틀려서 로그인이 불가능할 경우에는 인증이 되지 않은 401 상태 코드를 반환합니다.
결론 정상 요청에는 200 OK , 인증이 되지 않았을 때는 401 Unauthorized를 반환하자
Spring Security의 경우에는 이전 페이지로 redirect를 할 수 있다고도 합니다.
@PostMapping("/login")
public ResponseEntity<String> loginMember(@RequestBody MemberLoginRequestDTO memberLoginRequestDTO){
if(memberService.login(memberLoginRequestDTO)){
return ResponseEntity.status(HttpStatus.OK).body("로그인 완료");
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인 실패");
}
4. 서비스에서는 기능을 어떻게 구현해야 할까?
memberLoinRequestDTO를 만들어 id와 password를 입력받습니다.
그리고 id를 기반으로 Member를 조회하고 해당 id를 가진 Member의 비밀번호와 입력받은 password가 일치하는지를 true/ false로 반환합니다.
@Override
public boolean login(MemberLoginRequestDTO memberLoginRequestDTO) {
Optional<Member> userId = memberRepository.findByUserId(memberLoginRequestDTO.getUserId());
//Optional 사용했으니 isPresent 대신 userId.orElseThrow()를 쓰는게 좋아보인다
if(!userId.isPresent()){
return false;
}
return userId.get().getPassword().equals(memberLoginRequestDTO.getPassword());
}
5. 테스트
컨트롤러 테스트
@Test
@DisplayName("로그인 성공시 200 상태코드 반환")
public void memberLoginSuccessTest() throws Exception {
//given
MemberLoginRequestDTO memberLoginRequestDTO = MemberLoginRequestDTO.builder().userId("test").password("123456789").build();
String body = (new ObjectMapper()).writeValueAsString(memberLoginRequestDTO);
boolean loginResult = true;
given(memberService.login(any())).willReturn(loginResult);
//when
ResultActions resultActions = mvc.perform(post("/members/login")
.content(body)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
);
//then
resultActions
.andExpect(status().isOk());
}
@Test
@DisplayName("로그인 실패시 409 상태코드 반환")
public void memberLoginFailTest() throws Exception {
//given
MemberLoginRequestDTO memberLoginRequestDTO = MemberLoginRequestDTO.builder().userId("test").password("123456789").build();
String body = (new ObjectMapper()).writeValueAsString(memberLoginRequestDTO);
boolean loginResult = false;
given(memberService.login(any())).willReturn(loginResult);
//when
ResultActions resultActions = mvc.perform(post("/members/login")
.content(body)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
);
//then
resultActions
.andExpect(status().isUnauthorized());
}
서비스 테스트
@Test
@DisplayName("회원 로그인 성공")
public void memberLoginSuccessTest(){
//given
Member member = Member.builder().userId("Test").name("Test").nickName("Test").password("Test").phoneNumber("01012345678").address(new Address("a1", "a2", "a3")).build();
memberRepository.save(member);
//when
MemberLoginRequestDTO memberLoginRequestDTO = MemberLoginRequestDTO.builder().userId("Test").password("Test").build();
boolean result = memberService.login(memberLoginRequestDTO);
//then
assertThat(result).isEqualTo(true);
}
@Test
@DisplayName("회원 로그인 실패")
public void memberLoginFailTest(){
//given
Member member = Member.builder().userId("Test").name("Test").nickName("Test").password("Test").phoneNumber("01012345678").address(new Address("a1", "a2", "a3")).build();
memberRepository.save(member);
//when
MemberLoginRequestDTO memberLoginRequestDTO = MemberLoginRequestDTO.builder().userId("Test").password("1234").build();
boolean result = memberService.login(memberLoginRequestDTO);
//then
assertThat(result).isEqualTo(false);
}
출처
로그인 방식은 왜 Post 방식일까?
GET방식? POST 방식?
velog.io
https://stackoverflow.com/questions/43965316/for-login-get-or-post
For Login GET or POST?
I want to know which HTTP method i use for developing a login API, GET or POST ?. I have developed my login API in PHP using post method, but my BOSS say that, why you use POST method instead of GET
stackoverflow.com
https://stackoverflow.com/questions/6110672/correct-http-status-code-for-login-form
Correct HTTP status code for login form?
I am implementing the authentication for an app, and I am using a pluggable system with "authentication methods". This allows me to implement both HTTP Basic as well as HTML-based authentication. ...
stackoverflow.com
https://gist.github.com/subicura/8329759
유명 서비스들의 API Response 분석
유명 서비스들의 API Response 분석. GitHub Gist: instantly share code, notes, and snippets.
gist.github.com