로그인에 대해 설명하기 앞서 도메인에 대해 잠깐 이야기해보겠다.
도메인은 시스템이 구현해야 하는 핵심 비즈니스 업무 영역으로 향후 웹 기술이 다른 기술로 바뀌어도 도메인은 그대로 유지되어야 하므로 웹이 도메인을 의존해도 되지만, 도메인은 웹에 의존해선 안된다.
로그인을 하면 로그인 상태를 유지해야하는데 HTTP는 비연결성이므로 이를 해결하기 위해 쿠키 라는 것을 사용한다.
쿠키에도 두가지 쿠키가 있다.
영속 쿠키 : 만료 날짜를 기입하여 해당 날짜까지만 유지하는 쿠키
세션 쿠키 : 만료 날짜를 생략하여 브라우저 종료시까지 유지
보통은 브라우저를 종료했을 때 로그아웃 되길 바라므로 세션 쿠키로 구현해보겠다.
Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
로그인에 성공하면 쿠키(이름 : memberId, 값 : 회원의 id) 를 생성하고 response에 담는다.
@Controller
@RequiredArgsConstructor
public class HomeController {
private final MemberRepository memberRepository;
@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId,
Model model) {
if (memberId == null) {
return "home";
}
Member loginMember = memberRepository.findById(memberId);
if (loginMember == null) {
return "home";
}
model.addAttribute("member", loginMember);
return "loginHome";
}
}
@CookieValue를 사용하여 쿠키를 조회할 수 있다. 여기서 required 옵션은 로그인을 하지 않은 사용자도 메인 홈에는 접근할 수 있어야하기 때문에 false로 주었다.
이렇게 되면 쿠키 값이 null이면 home으로 보내고, 쿠키 값이 있어도 회원이 없으면 home으로 보낸다.
쿠키도 있고 회원 정보도 있으면 통과한다.
쿠키를 이용하여 로그인 기능을 구현했는데, 로그아웃 기능은 어떻게 구현해야 할까?
간단하게 쿠키의 종료 날짜를 0으로 지정하면 된다.
@PostMapping("/logout")
public String logout(HttpServletResponse response) {
Cookie cookie = new Cookie("memberId", null);
cookie.setMaxAge(0);
response.addCookie(cookie);
return "redirect:/";
}
이렇게 쿠키의 setMaxAge 메서드를 이용하여 0으로 설정하면 쿠키가 즉시 만료된다.
이렇게 쿠키만 사용해서 구현하게 된다면 심각한 보안 문제가 발생한다.
그 이유는 쿠키 값은 임의로 변경이 가능하다. 임의로 변경이 가능하다는 것은 다른 사용자 행세를 할 수 있다는 말이 된다. 당장 개발자 도구를 키고 Cookie: memberId=1 값을 2로 변경하면 다른 사용자로 변경이 된다.
그리고 쿠키에 보관된 정보도 훔쳐갈 수 있으며, 도난당한 쿠키 정보를 통해 악의적인 요청을 계속해서 시도할 수 있다.
이러한 문제를 해결하기 위해 나온 방식이 세션 동작 방식이다.
세션 동작 방식을 사용하게 되면 로그인 순서는 다음과 같다
1. 먼저 POST로 ID 값과 비밀번호 값을 사용자가 전달하면 서버는 해당 사용자가 맞는지 확인한다.
2. 사용자가 맞다면 추정 불가능한 랜덤 값을 이용하여 세션 ID를 생성한다. 그리고 생성된 세션 ID와 세션에 보관할 값을 서버의 세션 저장소에 보관한다. -> sessionId: 랜덤한 값, value: 사용자
3. 서버는 클라이언트에게 쿠키 이름 : JSESSIONID, 값: 세션 ID 형태로 쿠키 저장소에 저장한다.
즉, 회원과 관련된 정보는 클라이언트가 일절 모른다는 것이다. 오로지 추정 불가능한(랜덤 값) 세션 ID만 쿠키의 값으로 전달한다.
이후 사용자가 클라이언트 요청시 JSESSIONID 쿠키를 전달하면 서버는 해당 쿠키의 값(세션ID)을 세션 저장소에서 조회하여 로그인시 보관한 세션 정보를 사용한다.
서블릿은 세션을 위해 HttpSession을 제공한다.
HttpSession session = request.getSession(); // 세션이 있으면 세션 반환, 없으면 세션 생성
session.setAttribute("loginMember", loginMember); // 세션에 로그인 정보 보관
로그인 로직이 성공하면 세션에 회원 정보를 담아준다.
여기서 request.getSession()에는 두가지 옵션이 있는데 기능은 다음과 같다.
request.getSession(true) : 세션이 있으면 기존 세션을 반환하고 없으면 새로 생성하여 반환
request.getSession(false) : 세션이 있으면 기존 세션을 반환하고 없으면 생성하지 않고 null을 반환
옵션을 주지 않으면 자동으로 true가 사용된다.
그리고 session.setAttribute()는 세션에 데이터를 저장한다.
로그인 로직을 보자
@GetMapping("/login")
public String LoginForm(HttpServletRequest request, Model model) {
HttpSession session = request.getSession(false);
if (session == null) {
return "home";
}
Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);
if (loginMember == null) {
return "home";
}
model.addAttribute("member", loginMember);
return "loginHome";
}
로그인을 살펴보면 세션정보가 없거나, 매핑되는 로그인 정보가 없다면 로그인 창으로 보내고 로그인에 성공하면 이후 로직이 출력된다.
로그인 정보는 session.getAttribute()로 불러오고 타입 캐스팅을 해주어서 검증한다.
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
로그아웃 로직은 이와 같이 세션이 없다면 세션을 제거하는 로직으로 짜면 된다.
스프링에서는 이를 편리하게 사용할 수 있게 @SessionAttribute라는 어노테이션을 지원한다.
@GetMapping("/login")
public String LoginForm(
@SessionAttribute(name = "loginMember", required = false) Member loginMember,
Model model) {
if (loginMember == null) {
return "home";
}
model.addAttribute("member", loginMember);
return "loginHome";
}
세션을 찾고 세션 정보를 통해 데이터를 얻는 과정을 편리하게 해결해준다.
이렇게 로그인을 처음 시도하게 되면
http://localhost:8080/;jsessionid=123456789ABCDEF
형태로 jsessionid 파라미터가 붙는다.
이것이 붙는 이유는 서버 입장에서는 웹 브라우저가 쿠키를 지원하는지를 모르고, 혹시라도 웹브라우저가 쿠키를 지원하지 않을 때 쿠키 대신 URL을 통해 세션을 유지하기 위함이다.
URL 전달 방식을 이용하지 않고 쿠키를 통해서만 세션을 유지하려면(URL 뒤에 jsession 정보가 안붙게) 다음과 같이 입력한다.
application.properties
server.servlet.session.tracking-modes=cookie
세션의 타임아웃(세션 만료) 값 설정은 다음과 같다.
글로벌 설정 (분 단위로 설정해야 하고, 입력 값(1800) 은 초단위임)
server.servlet.session.timeout=1800
마지막 작업을 기준으로 세션 시간 설정
session.setMaxInactiveInterval(1800);
'Spring > SpringMVC' 카테고리의 다른 글
스프링 검증 - Bean Validation (0) | 2023.02.11 |
---|---|
스프링 검증 (0) | 2023.02.10 |
메시지, 국제화 (0) | 2023.02.05 |
타임리프 - 스프링 통합과 폼 (0) | 2023.02.05 |
타임리프 기본 기능 모음 (0) | 2023.02.04 |