1. 로그인 Controller 기능 구현
@RequestMapping(value = "login", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView getLogin() {
ModelAndView modelAndView = new ModelAndView("member/login");
return modelAndView;
}
@RequestMapping(value = "login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String postLogin(UserEntity user) {
Enum<? extends IResult> result = this.memberService.login(user);
JSONObject responseObject = new JSONObject();
responseObject.put("result", result.name().toLowerCase());
return responseObject.toString();
}
- login은 GET, POST 요청방식만 필요하다(PATCH 요청방식은 필요 X)
- GET요청방식을 통해 클라이언트가 최초로 보는 로그인 페이지를 보여주고 POST요청방식은 로그인하기 버튼을 클릭했을시 값(email, password)을 전송해주는 역할을 한다.(JSONObject를 사용하여 result결과와 같이 값을 반환한다.)
2. 로그인 JS 기능 구현(xhr)
form.onsubmit = (e) => {
e.preventDefault();
if (form['email'].value === '') {
Warning.show('이메일을 작성해 주십시오.');
form['email'].focus();
return;
} else {
Warning.hide();
}
if (form['password'].value === '') {
Warning.show('비밀번호를 입력해 주세요.');
form['password'].focus();
return;
}
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('email', form['email'].value);
formData.append('password', form['password'].value);
console.log(form['email'].value);
console.log(form['password'].value);
xhr.open('POST', './login');
xhr.onreadystatechange = () => {
if(xhr.readyState === XMLHttpRequest.DONE){
if(xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'success':
alert('로그인 완료');
break;
default:
alert('로그인 실패')
Warning.show('입력한 정보와 일치하는 회원이 없습니다.');
break;
}
} else {
Warning.show('알 수 없는 이유로 로그인을 완료하지 못하였습니다. 잠시 후 다시 시도해 주세요.');
}
}
};
xhr.send(formData);
};
- 로그인 기능 구현에서 주의해야 할 점은 로그인 하기 Button의 타입이 submit이기 때문에 onsubmit 함수를 이용하여 값을 전송해야 한다는 점이다.
- 최초 onsubmit(e) 함수 매개변수에 e라는 임의 변수를 넣고 e.preventDefault를 통해 새로고침 이벤트를 막아주는 역할을 수행한다.(addEventListener의 경우에는 특정 값을 전달하는 역할보다는 태그의 이벤트발생을 기본적으로 실행하는 함수이기때문에 로그인과 같이 값이 전송되었을 경우 특정 페이지로 이동하기 위해서는 submit 타입을 사용해야 한다.)
- 이러한 경우를 시멘틱에 맞게 태그를 사용한다라고 볼 수 있지 않을까 싶다. 이메일 찾기, 비밀번호 재설정 등은 결국 한페이지안에서 type이 button인 태그들을 위해 addEventListener로 해결이 되지만 로그인 같은 경우에는 submit타입을 사용하여 실제 user가 로그인에 성공했을시 그유저의 정보를 담고있는 브라우저에서 추가적인 이벤트가 발생하기 때문에 저장된 클라이언트의 정보로 무엇인가에 대한 행동이 취해져야 하기 때문이 아닐까라는 추측.(아직 배우기 전 단계)을 해본다.
- form.append()를 통해 email과 password를 담아 send()메서드를 통해 전송하여 성공적으로 완료가 되었을 경우 alert('성공')이 뜨면 된다.
3. 로그인 Service 기능 구현
- 로그인 관련한 Service로직은 의외로 간단하다. UserTable에 저장되어있는 email과 password만 비교해주면 된다.
- 이때 주의해야 할 점은 email의 값은 그대로 비교하되, password값은 클라이언트가 입력하는 password를 Hashing하여 DB에 저장된 user의 비밀번호(Hashing)값과 일치할 경우 SUCCESS를 반환하면 된다.
4. 로그인 관련 Session 및 Cookie
- 지금까지 구현한 로그인은 DB와의 접촉에 의해서 단순히 email과 password가 일치하냐 안하냐의 성공과 실패여부만을 반환하고 있다.
- 브라우저가 정보를 오랫동안 기억하려면(클라이언트가 탐색섹션을 모두 종료하는 시점) Session이라는 객체에 로그인 관련 클라이언트 정보를 담고 있어야 한다. 일반적으로 email, password의 일치여부만을 가지고 로그인을 구현했다고 할 경우 GC(Garbage Collection)에서는 단순히 값으로 인식하고 시간이 지나면 치워버린다.
- Session 객체도 modelAndView 객체 처럼 setAttribute라는 메서드를 통해 키값쌍의 값을 넣을 수 있다.
@RequestMapping(value = "login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String postLogin(UserEntity user, HttpSession session) {
Enum<? extends IResult> result = this.memberService.login(user);
if(result == CommonResult.SUCCESS) {
session.setAttribute("user", user);
}
JSONObject responseObject = new JSONObject();
responseObject.put("result", result.name().toLowerCase());
return responseObject.toString();
}
이런식으로 postLogin메서드에 HttpSession 타입의 객체를 매개변수로 받고 그값의 setAttribute를 통해 ("user", user[실제 UserEntity의 값])을 추가 해줌으로써 브라우저의 쿠키에 저장해 둘 수 있다.
- 실제 유동적인 로그인 방식으로는 Html에서 로그인 성공시 로그인(li태그),회원가입(li태그)는 보이지 않아야 한다. 그리고 로그아웃, 마이페이지 등 로그인에 성공한 클라이언트에게만 보여주는 것이 있어야 한다.
<ul class="--menu">
<li class="--item">
<a class="--link" th:href="@{/member/login}" th:if="${session.user == null}">로그인</a>
</li>
<li class="--item">
<a class="--link" th:href="@{/member/register}" th:if="${session.user == null}">회원가입</a>
</li>
<!-- 위까지는 로그인을 안했을 때-->
<li class="--item">
<a class="--link" th:href="@{/member/logout}" th:if="${session.user != null}">로그아웃</a>
</li>
<li class="--item">
<a class="--link" th:href="@{/member/my}" th:if="${session.user != null}">마이페이지</a>
</li>
<!-- 로그아웃, 마이페이지는 로그인이 되었을 때 보여주어야 함.-->
</ul>
- 이를 위해 thymeleaf의 th:if를 사용하여 session값이 null일때는(쿠키에 값이 없다는 뜻) 로그인을 하지 않았다는 뜻이므로 로그아웃, 마이페이지를 보이지 않게 하고 session값이 null이 아닐때는(쿠키에 클라이언트의 정보가 들어가 있는 상태) 로그인을 한 상태이므로 로그인,회원가입을 보이지 않게 한다.

지금까지 로그인 및 Session에 관해서 사용한 이유는 게시판 만들기에서 이유가 발생한다.
4. 게시판 기능 구현(쓰기)
- 일반적으로 생각했을때(지금 우리가 만드는 프로젝트내) 로그인을 했을때! 게시판 글을 쓸 수 있는 권한 즉, 글쓰기 페이지를 클라이어튼에게 보여주는 것이 옳다
- 그럼 Login이 인증이 된 클라이언트 정보를 어디서 가져오느냐? 바로 Session에 저장되어 있는 값을 가지고 오는 것이다(떡밥 풀리는 순간)
- @SessionAttribute 어노테이션을 통해 value에 MemberController에서 login메서드에 session의 user값을 가지고 온다. 이때 중요한 점은 MemberController에서 user의 값이 null이다. 즉, 로그인을 하지 않은 사람에 대해서 우리가 처리를 해놓은 상태인데 아래의 코드와 같이 required = false를 작성하지 않은 상태에서 실행할 경우 400오류가 발생한다. 이유는 UserEntity타입의 user가 null값이 들어가서 값이 없는 상태로 자바스프링이 인식하기 때문이다(required의 기본값이 true이기 때문에)이로 인해서 오류를 방지 하기 위해서(혹은 user의 null값을 처리하기 위해서)는 required = false라고 처리를 해줌으로써 UserEntity타입의 user가 필수로 필요한 조건이 아니게 됨을 자바스프링에 인지시켜준다.
@Controller(value = "dev.babsang.studymemberbbs.controllers.BbsController")
@RequestMapping(value = "/bbs")
public class BbsController {
@RequestMapping(value = "write", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView getWrite(@SessionAttribute(value = "user", required = false)UserEntity user) {
// 여기서 세션값("user")는 memberController에 있는 키 "user"에서 가지고 온것.
// required = false가 없는 상태로 실행을 할경우 user는 필수조건으로 있어야 하는 것이됨으로 Bad request가 뜬다(MemberController에서 로그인이 안된 상태인경우 user = null로 지정해 주었기 때문에)
// 이때 user가 없어도 BacRequest가 안뜨게 하기 위해서는 required값을 false로 지정해주면 400방지가 된다.
// SessionAttribute는 어떠한 값을 세션 저장소로 부터 불러오기 위해 사용한다.
ModelAndView modelAndView;
if( user == null) {
// 위 매개변수로 지정한 @SessionAttribute에서 value값 user를 가지고 온것
modelAndView = new ModelAndView("redirect:/member/login");
}else {
modelAndView = new ModelAndView("bbs/write");
}
return modelAndView;
}
}
- required = false를 작성해주었을경우에는 localhost:8080/member/login에서 로그인을 정상적으로 시도하였을 경우 localhost:8080/bbs/write 주소를 클릭하였을 때 정상적으로 페이지가 보여진다.
'SpringBoot' 카테고리의 다른 글
SpringBoot 게시판 기능구현 1 (0) | 2022.11.20 |
---|---|
SpringBoot 회원가입 기능구현 4 , 비밀번호 재설정 기능구현 (0) | 2022.11.15 |
SpringBoot 회원가입 기능구현 3 (0) | 2022.11.13 |
SpringBoot 회원가입 기능구현 2 (1) | 2022.11.11 |
SpringBoot 회원가입 기능구현 1 (2) | 2022.11.11 |