Spring & Springboot/올인원 스프링 프레임워크

전자 도서관 프로젝트 - 로그인 상태 유지 및 로그아웃 기능 구현

YJ_ma 2023. 10. 11. 11:21

로그인 상태 유지 및 로그아웃 기능 구현

쿠키와 세션을 사용하는 이유

웹 서비스를 이용할 때 로그인을 하게 되면 로그아웃을 하거나 브라우저를 닫기 전까지는 로그인 상태가 유지된다.

ex, 포털 사이트에 로그인하면 로그인 계정에 대한 메일을 확인할 수 있다.

ex, 쇼핑몰에 로그인을 하면 로그인 계정으로 장바구니 및 상품 구매 등의 서비스를 이용할 수 있다.

 

일반적으로 웹 서비스에서는 로그인 상태를 유지하기 위한 방법으로 쿠키 또는 세션을 이용한다.

 

HTTP 프로토콜의 특징

웹 서비스는 HTTP 프로토콜을 기반으로 한다.

① 비연결형 프로토콜 : 클라이언트와 서버의 관계를 유지하지 않는 특징이 있다. 즉, 클라이언트의 요청을 서버가 응답하면 더 이상 클라이언트와 서버의 관계는 유지되지 않는다.

클라이언트 요청에 대해 서버의 부하를 줄여주는 효과가 있다.

② 무상태 : 연결이 끊기는 순간 클라이언트와 서버의 상태를 유지하지 않는 특성이 있다.

서버의 응답이 완료된 후 클라이언트와 서버의 연결이 끊기고, 다시 클라이언트가 요청을 하면 서버는 새로운 클라이언트로 인식해서 새로운 연결을 한다.

 

HTTP의 비연결형과 무상태 특성으로 인해 최고 관리자가 로그인했지만, 로그인 상태를 유지할 수 없게 된다.

이를 해결하기 위해 쿠키와 세션은 클라이언트와 서버의 연결 상태를 유지해준다.

· 쿠키 : 클라이언트에서 연결 정보를 관리

· 세션 : 서버에서 연결 정보를 관리

 

쿠키의 개념

누군가 웹 사이트에 접속하게 되면 접속 정보를 남길 필요가 있는데, 이런 정보를 쿠키 부스러기와 같은 흔적을 남긴다 해서 쿠키라고 부르게 되었다.

 

쿠키는 접속 정보를 클라이언트에 작은 데이터 파일로 남기게 된다.

파일에는 쿠키 이름, 유효 기간, 도메인 등의 정보가 이름과 값 형태로 저장되어 있으며, 클라이언트가 서버로 요청할 때 자동으로 서버에 전송된다.

서버는 전송받은 쿠키를 통해 어떤 클라이언트가 접속했는지 알 수 있다.

 

쿠키 실습 : 로그인하면 메뉴 변경되기

로그인 전에는 '로그인' 메뉴가 보이고 로그인 후에는 '로그아웃' 메뉴가 보이도록 해본다.

1. STS에 ch10_pjt_01 프로젝트를 생성한다.

· 프로젝트명 : ch10_pjt_01

· 패키지명 : com.company.cookie

2. pom.xml 파일에서 자바와 스프링 버전을 변경하고 프로젝트를 업데이트 한다.

■ 자바 버전

<java-version>11</java-version>
<org.springframework-version>5.2.9.RELEASE</org.springframework-version>

■ 스프링 버전

<source>11</source>
<target>11</target>

 

3. web.xml에 <filter>를 추가하여 한글 인코딩을 설정한다.

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter
    </filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

4. [views] 폴더에 [member] 폴더를 새로 생성하고 뷰에 해당하는 JSP 파일들을 생성한다.

· home.jsp : 이미 생성되어 있는 파일로, 로그인 똔느 로그아웃 메뉴가 출력되는 화면

· login_form.jsp : 로그인하는 화면

· login_ng.jsp : 로그인 실패한 결과를 출력하는 화면

· login_ok.jsp : 로그인을 성공한 결과를 출력하는 화면

5. [src/main/java] 폴더에 com.company.cookie.member 패키지를 생성하고 MemberController 클래스를 만들고 다음과 같이 코딩한다.

■ MemberController 코드

package com.company.cookie.member;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/member")
public class MemberController {

	// 회원홈
	@GetMapping({"", "/"})
	public String home() {
		System.out.println("[MemberController] home()");

		String nextPage = "member/home";

		return nextPage;
	}
}

 

6. home.jsp를 다음과 같이 코딩한다.

<c:choose>를 이용해서 로그인과 로그아웃 메뉴를 쿠키 값에 따라 다르게 출력하고 있다.

· {cookie.loginMember.value eq null } : 쿠키 값(loginMember)이 null이면 true이고 그렇지 않으면 false이다.

   - null인 경우 : 로그인 전이라는 의미로 로그인 메뉴 노출

   - null이 아닌 경우 : 로그인 상태로 로그아웃 메뉴가 노출

■ home.jsp 코드

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<!DOCTYPE html>
<html>
<head>
	<title>Home</title>
</head>
<body>
	<h3>MEMBER HOME</h3>
	
	<c:choose>
		<c:when test="${cookie.loginMember.value eq null}">
			<a href="<c:url value='/member/loginForm'/>">로그인</a>
		</c:when>
		<c:otherwise>
			<a href="<c:url value='/member/logoutForm'/>">로그아웃</a>
		</c:otherwise>
	</c:choose>
	
</body>
</html>

 

· 로그인 메뉴를 클릭해서 로그인 화면으로 이동하기 위해 MemberController의 loginForm()을 다음과 같이 선언한다.

■ MemberController 코드

// 로그인
@GetMapping("/loginForm")
public String loginForm() {
    System.out.println("[MemberController] loginForm()");

    String nextPage = "member/login_form";

    return nextPage;
}

 

■ login_form.jsp 코드

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>MEMBER LOGIN FORM</h3>
	
	<form action="<c:url value='/member/loginConfirm'/>" method="post">
		<input type="text" name="m_id"><br>
		<input type="password" name="m_pw"><br>
		<input type="submit" value="login">
		<input type="reset" value="reset">
	</form>
</body>
</html>

 

사용자 로그인을 위해 입력한 아이디(m_id), 비밀번호(m_pw)라는 이름으로 서버에 전송된다.

로그인 요청을 서버에서 처리하기 위해 MemberController에 loginConfirm()을 다음과 같이 선언한다.

· 서버 URI : /member/loginConfirm

■ MemberController 코드

// 로그인 확인
@PostMapping("/loginConfirm")
public String loginConfirm(@RequestParam("m_id") String m_id,
        @RequestParam("m_pw") String m_pw,
        HttpServletResponse response) {

    System.out.println("[MemberController] loginConfirm()");

    String nextPage = "member/login_ok";

    if (m_id.equals("user") && m_pw.equals("1234")) {
        Cookie cookie = new Cookie("loginMember", m_id);
        cookie.setMaxAge(60 * 30);
        response.addCookie(cookie);
    } else {
        nextPage = "member/login_ng";
    }
    return nextPage;		
}

· if문으로 사용자가 입력한 아이디가 user이고 비밀번호가 1234이면 로그인 성공이고, 그렇지 않으면 로그인 실패로 여긴다.

· Cookie() 생성자 : 쿠키의 이름(loginMember)과 값(m_id)를 이용해서 Cookie 객체를 생성한다.

· 생성된 쿠키는 setMaxAge()을 이용해 쿠키의 유효 시간을 설정하게 된다.

   - 시간단위 : 초

· 이렇게 생성된 쿠키는 response에 추가되어 클라이언트에 전달된다.

 

다음으로 로그인 성공 또는 실패했을 때 응답되는 login_ok.jsp와 login_ng,jsp을 코딩한다.

■ login_ok.jsp 코드

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>MEMBER LOGIN SUCCESS!!</h3>
	<a href="<c:url value='/member'/>">홈</a>
</body>
</html>

■ login_ng.jsp 코드

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>MEMBER LOGIN FAIL!!</h3>
	<a href="<c:url value='/member'/>">홈</a>
</body>
</html>

 

마지막으로 로그아웃 기능을 MemberController에 선언해준다.

■   MemberController 코드

// 로그아웃 확인
@GetMapping("/logoutForm")
public String logoutForm(@CookieValue(value="loginMember", required=false) 
String loginMember, HttpServletResponse response) {
    System.out.println("[MemberController] loginConfirm()");

    String nextPage = "redirect:/member/";

    Cookie cookie = new Cookie("loginMember", loginMember);
    cookie.setMaxAge(0);

    response.addCookie(cookie);

    return nextPage;
}

· 클라이언트가 서버에 요청을 하면 자동으로 쿠키가 전달된다. 그리고 서버에서는 @Cookie Value를 이용해서 쿠키를 받을 수 있다.

· value 속성 : 쿠키의 이름

· required : loginMember 쿠키가 꼭 필요하지는 않다고 명시

만약 required를 명시하지 않으면 loginMember 쿠키가 없을 경우 예외가 발생하게 된다.

· new Cookie로 loginMember 쿠키를 생성하고 setMaxAge()을 0으로 설정하여 loginMember 쿠키는 유효 기간이 종료되어 더 이상 사용할 수 없는 로그아웃 상태가 된다.

 

이제, 로그인이 성공했을 때 쿠키가 잘 생성되는지와 쿠키에 따라 홈(home.jsp)의 메뉴가 변경되는지 확인해본다.

첫 홈 화면 로그인 페이지 로그인 성공 여부 페이지 로그인 후 홈 화면

 

로그인 성공시 loginMember 쿠키를 서버는 클라이언트에게 전달하고, 클라이언트는 이를 활용한다.

전달되는 쿠키 정보(name, value)를 다음과 같이 브라우저에서 확인할 수 있다.

 

브라우저에서 쿠키를 강제로 삭제하면 어떻게 될까?

해당 쿠키를 삭제하면 사용자는 더 이상 로그인 상태가 아니게 된다. 따라서 브라우저를 새로고침하면 로그인 전 화면으로 변경되는 것을 확인할 수 있다.

 

세션의 개념

세션도 쿠키와 마찬가지로 사용자 접속 정보를 남길 때 사용한다. 쿠키가 정보를 클라이언트에서 관리했다면, 세션은 서버에서 관리한다.

· 세션ID : 클라이언트가 서버에 요청을 보내면 서버가 클라이언트를 구분할 수 있게 클라이언트에게 전달하는 ID 

· 세션ID는 브라우저가 종료하기 전까지 사용 가능하다.

· 톰캣 환경에서 서버는 처음 접속할 때만 세션ID를 생성한다. (이후 동일한 세션ID를 요청 받으면 세션ID 생성❌)

 

도서 대출 서비스 프로젝트에서 서버에서 자동으로 생성된 세션ID를 확인해본다.

서버에서 생성된 세션ID는 클라이언트로 전달될 때 쿠키를 이용한다.

· 쿠키의 이름(Name) : JESSIONID

· 쿠키의 값(Value) : 세션ID(DDCFB0...)

 

 

세션 실습 : 관리자 로그인

도서 대출 서비스 프로젝트에 관리자 로그인 상태 유지 구현을 해본다.

AdminMemberService의 loginConfirm()에서 반환하는 값을 살펴본다.

public AdminMemberVo loginConfirm(AdminMemberVo adminMemberVo) {
    System.out.println("[AdminMemberService] loginConfirm()");

    AdminMemberVo loginedAdminMemberVo =
            adminMemberDao.selectAdmin(adminMemberVo);

    if (loginedAdminMemberVo != null)
        System.out.println("AdminMemberService] ADMIN MEMBER LOGIN SUCCESS!!");
    else
        System.out.println("AdminMemberService] ADMIN MEMBER LOGIN FAIL!!");

    return loginedAdminMemberVo;
}

loginedAdminMemberVo

- loginConfirm()이 DAO에 요청한 회원 검색 결과로 조건에 맞는 회원이 있으면 null이 아니고, 조건에 맞는 회원이 없으면 null이다.

- loginConfirm()을 호출한 컨테이너에게 다시 반환된다. 만약 로그인에 성공하면 컨트롤러는 loginedAdminMemberVo를 세션에 저장하게 된다.

 

이제, AdminMemberController의 loginConfirm()에 HttpSession 타입의 매개변수를 받을 수 있는 session 매개변수를 추가해준다. 추가된 session 매개변수에는 세션 정보(로그인에 성공한 관리자 정보(loginedAdminMemberVo))가 들어있다.

· setAttribute(저장할 데이터 이름, 실제 데이터)

// 로그인 확인
@PostMapping("/loginConfirm")
public String loginConfirm(AdminMemberVo adminMemberVo, HttpSession session) {
    System.out.println("[AdminMemberController] loginConfirm()");

    String nextPage = "admin/member/login_ok";

    AdminMemberVo loginedAdminMemberVo = 
            adminMemberService.loginConfirm(adminMemberVo);

    if (loginedAdminMemberVo == null) {
        nextPage = "admin/member/login_ng";
    } else {
        session.setAttribute("loginedAdminMemberVo", loginedAdminMemberVo);
        session.setMaxInactiveInterval(60 * 30);
    }

    return nextPage;
}

세션에 정보를 저장하기 위해 String 타입의 데이터 이름과 Object 타입의 데이터 값을 넣어준다. 

데이터 값의 타입이 Object이기 때문에 어떠한 데이터 타입도 저장 가능하다.

 

관리자가 로그인에 성공하면 세션에 loginedAdminMemberVo 이름으로 loginedAdminMemberVo를 저장한다.

그리고 setMaxInactiveInterval()을 이용해서 세션의 유효기간을 설정하는데, 단위는 초이다. 기간동안 아무런 동작을 하지 않게 되면 서버의 세션은 종료되고 관리자는 다시 로그인해야한다.

 

로그인 성공과 실패에 다른 세션을 달리 설정했으므로 뷰(view)에서 로그인 성공과 실패에 따라 메뉴가 어떻게 구성되는지 확인한다.

로그인 전 로그인 후

관리자가 로그인에 성공한 경우 login_ok.jsp을 응답하게 되는데, login_ok.jsp는 메뉴를 출력하기 위해 nav.jsp를 include하고 있다.

이제 Admin Home을 클릭하며 새로고침해도 로그인 상태를 유지하는 것을 확인할 수 있다.

 

세션 실습 : 관리자 로그아웃

nav.jsp에 로그아웃 메뉴는 다음과 같이 정의되어 있다.

<a href="<c:url value='/admin/member/logoutConfirm' />">로그아웃</a>

따라서 /logoutConfirm 요청을 처리할 수 있는 메서드를 AdminMemberController에 선언해준다.

■ AdminMemberController 코드

// 로그아웃 확인
@RequestMapping(value="/logoutConfirm", method=RequestMethod.GET)
public String logoutConfirm(HttpSession session) {
    System.out.println("[AdminMemberController] logoutConfirm()");

    String nextPage = "redirect:/admin";

    session.invalidate();

    return nextPage;
}

logoutConfirm()은 HttpSession을 매개변수로 받고 invalidate()를 호출한다.

· invalidate() : 세션을 무효화시키는 것

/admin으로 리다이렉트되면 AdminHomeController의 home()이 호출되어 admin/home.jsp가 클라이언트에 응답한다.