1. 무상태에서 과거 기억하기(세션 트렉킹)
- 무상태: 과거의 상태를 유지 x. 이유는 여러 요청 처리 시 자원 소모가 낮기 때문이다.
- 세션: 특정 시점에서의 구간을 의미
- 세션 트랙킹: 무상태를 해결하기 위해 만들어진 것으로 과거에 방문기록(특정 구간에서의 기록)을 찾아 로그인 등의 상태를 유지해주는 것
HTTP에선 세션 트랙킹 기술로 쿠키를 사용한다. 쿠키는 문자열로 만들어진 것으로 요청 및 응답 시 주고받는다. 형태는 name:value 구조이다.
쿠키를 주고 받는 과정
Browser Server
| |
| 1. HTTP 요청 | (쿠키 없음)
|--------------->|
| |
| 2. HTTP 응답 | (Set-Cookie)
|<---------------|
| |
| 3. 브라우저에 쿠키 저장
| |
| 4. HTTP 요청 | (쿠키 포함)
|--------------->|
| |
| 5. 서버에서 쿠키 확인
| 6. HTTP 응답 |
|<---------------|
1. 브라우저에서 최초로 서버 호출. 서버에서 발행한 쿠키가 브라우저에는 없기에 아무것도 전송 x
GET / HTTP/1.1
Host: example.com
2. 서버는 쿠키 값을 저장하고 응답을 보낼 때 쿠키도 함께 보냄. 쿠키를 보낼 때 서버는 'Set-Cookie'라는 HTTP 헤더 사용.
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/abc; #abc로 시작하는 곳에서만 sessionId라는 이름의 쿠키 사용 가능
3. 브라우저는 쿠키를 받아 그 정보를 본다. 유효기간에 따라 파일 형태로 보관할 지, 메모리 상에서 처리할지 결정.
4. 브라우저가 보관하는 쿠키는 다음에 서버에 요청을 보낼 때 HTTP 헤더에 'Cookie'라는 이름으로 같이 딸려 보낸다.
GET /dashboard HTTP/1.1
Host: example.com
Cookie: sessionId=abc123
5. 쿠키를 받은 서버는 쿠키 값을 보고 사용자 세션 유지, 쿠키 갱신 등의 응답을 반환한다.
쿠키 생성 방법
1) 서버에서 자동으로 생성(세션 쿠키)
- 응답 메시지에 정해진 쿠키가 없는 경우 자동으로 생성된다(WAS에서 발행된다. 톰캣은 JSESSIONID 라는 이름의 쿠키를 자동 생성한다.
- 서버에서 생성한 쿠키는 브라우저 메모리에 저장된다.
- Expires나 Max-Age 속성이 없어 브라우저 종료 시 서버에서 생성된 쿠키는 자동 삭제된다.
- 서버에서 생성한 쿠키의 경로는 '/'이다.
2) 개발자가 생성
- 원하는 이름을 지정할 수 있다.
- 유효기간을 지정할 수 있으며 유효기간이 존재하면 브라우저는 이를 파일 형태로 보관한다.
- 반드시 Response에 추가해야 한다.
- 경로나 도메인 등을 지정할 수 있다(Path=/abc;) 경로나 도메인 지정 시 특정 경로에서만 쿠키를 사용한다.
2. 서블릿 컨텍스트와 세션 저장소
톰캣의 특징
- 하나의 톰캣에 여러 웹 어플리케이션 실행이 가능하다(프로젝트 실행 경로를 '/'외에 각각 다른 이름으로 저정)
- 웹 어플리케이션마다 별도의 도메인으로 분리되어 운영된다.
서블릿 컨텍스트
- 개념: 웹 어플리케이션이 사용하는 고유의 메모리 영역
- 서블릿이나 jsp 등을 인스턴스 생성
세션 저장소
- 개념: 톰캣이 발행하는 세션 쿠키를 저장하기 위한 메모리 영역
- key:value의 형태의 데이터를 보관
- key는 JSESSIONID이라는 쿠키의 값이 된다. 즉 서버는 브라우저에 있는 JSESSIONID 쿠키의 값을 key로 가진다. 이렇게 하는 이유는 세션 저장소의 value 값을 노출시키지 않기 위해서이다.
- 톰캣은 session-timeout 설정을 통해 지정된 시간보다 오래된 값을 주기적으로 지운다. 기본으로 지정된 시간은 30분이다.
세션을 통한 상태 유지 메커니즘
- 코드에서 HTTPServletRequest의 getSession() 메소드 실행
- 톰캣에서 JSESSIONID 이름의 쿠키가 request 시 있는지 확인
- 없으면 새로운 값을 만들어 세션 저장소에 보관
세션 저장소는 JSESSIONID의 값마다 공간을 가짐
세션 저장소에 유저 1의 로그인 정보와 유저 2의 로그인 정보가 있으므로 유저 1과 유저 2는 로그인을 했다고 인식한다.
JSESSIONID | value | 세션 저장소(Key- JSESSIONID의 value) | 세션 저장소(Value) |
A10 | saf123 | 유저 1 로그인 정보 | object |
B11 | 1asd12 | 유저 1 사용자 정보 | object |
C12 | asdqw1 | 유저 1 권한 정보 | object |
D13 | dasas11 | 유저 2 로그인 정보 | object |
HttpServletRequest getSession(): 세션 객체 생성
세션은 HttpSession 타입으로 표현되며 HttpSession 객체를 이용해 세션에 대한 CRUD가 가능하다.
세션(HttpSession 객체)의 생성은 getSession()으로 이루어지며 그 결과는 아래와 같은 세션 저장소 내의 공간(key, value)이다.
세션 저장소(Key- JSESSIONID의 value):반환 | 세션 저장소(Value):반환 |
유저 1 로그인 정보 | object |
- JSESSIONID가 없을 경우: 세션 저장소에 새 번호로 공간을 만들고 해당 공간에 접근할 수 있는 객체(HttpSession) 반환한다. 새로운 번호는 브라우저에 JSESSIONID의 값으로 전송(세션 쿠키)
- JSESSIONID가 있을 경우: 세션 저장소에서 브라우저에 있는 JSESSIONID의 값을 이용해 할당된 공간을 찾고 그 공간에 접근할 수 있는 객체를 반환한다.
세션 관련 메소드들
메소드 | 설명 |
Object getSession() | 기존의 세션 객체가 존재하면 반환하고 없으면 새로 생성 |
Object getSession(true) | 기존의 세션 객체가 존재하면 반환하고 없으면 새로 생성 |
Object getSession(false) | 기존의 세션 객체가 존재하면 반환하고 없으면 null 반환 |
void setAttribute(String name, Object value) | 세션에 속성을 저장 |
Object getAttribute(String name) | 세션에서 해당 이름의 속성을 가져옵니다. 없으면 null 반환 |
void removeAttribute(String name) | 세션에서 해당 속성을 제거 |
Enumeration<String> getAttributeNames() | 세션에 저장된 속성들의 이름 목록을 반환 |
String getId() | 현재 세션의 고유 ID를 반환 |
long getCreationTime() | 세션이 생성된 시간을 long 타입(밀리초)으로 반환 |
long getLastAccessedTime() | 마지막으로 세션이 접근된 시간을 반환 |
void invalidate() | 세션을 무효화하고 삭제 |
void setMaxInactiveInterval(int interval) | 세션의 유효 시간을 초 단위로 설정 |
int getMaxInactiveInterval() | 세션의 유효 시간을 초 단위로 반환 |
ServletContext getServletContext() | 현재 세션이 속한 서블릿 컨텍스트를 반환 |
boolean isNew() | 현재 세션이 새로 생성된 것인지(새로운 공간을 만든 것인지), 아닌지(기존 공간을 재사용하는지) 여부를 반환 |
세션을 이용한 로그인 체크
package org.zerock.w3.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;
import org.zerock.w3.dto.TodoDto;
import org.zerock.w3.service.TodoService;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@WebServlet(name = "todoRegisterController", value = "/todo/register")
@Log4j2
public class TodoRegisterController extends HttpServlet {
private TodoService todoService = TodoService.INSTANCE;
// db에 저장되어 있는 타임포맷을 특정 형태로 바꾸기 위해 선언
private final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info(".........../todo/register GET");
// 세션 객체 가져오기
HttpSession session = request.getSession();
// 기존 JSESSIONID가 없는 새로운 사용자면(아예 세션 쿠키가 없는 상태)
if (session.isNew()) {
log.info("JSESSIONID 쿠키가 새로 만들어진 사용자" );
response.sendRedirect("/login");
return;
}
// JSESSIONID는 있지만 해당 세션 저장소에 loginInfo라는 이름으로 저장된 객체가 없는 경우
// (세션 쿠키가 있지만 우리 사이트와 관련된 세션 쿠키가 아닌 상태 혹은 우리 사이트엔 들어왔지만 로그인은 안한 상태)
if(session.getAttribute("loginInfo") == null) {
log.info("로그인한 정보가 없는 사용자");
response.sendRedirect("/login");
return;
}
// 정상적인 로그인 시 이동
request.getRequestDispatcher("/WEB-INF/todo/register.jsp").forward(request, response);
}
}
로그인 컨트롤러
package org.zerock.w3.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;
import org.zerock.w3.dto.TodoDto;
import java.io.IOException;
import java.time.LocalDate;
@WebServlet("/login")
@Log4j2
public class LoginController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info(".........../login GET");
// 정상적인 로그인 시 이동
request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info(".........../login POST");
// 요청 객체에서 각 파라미터의 값 가져오기
String mid = request.getParameter("mid");
String pwd = request.getParameter("pwd");
// 임시 세션 쿠키 값 생성
String str = mid + pwd;
// 세션 객체 생성
HttpSession session = request.getSession();
// 세션 객체에 세션 쿠키 생성 및 보관
session.setAttribute("loginInfo", str);
log.info("test: " + session.getAttribute("loginInfo"));
// 성공 후 리스트로 이동
response.sendRedirect("/todo/list");
}
}
로그인jsp
<%--
Created by IntelliJ IDEA.
User: ape07
Date: 25. 1. 30.
Time: 오후 8:14
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<h1>login Page</h1>
</head>
<body>
<form method="post" action="/login">
<div>
<input type="text" name="mid" placeholder="아이디 입력"/>
</div>
<div>
<input type="text" name="pwd" placeholder="비밀번호 입력"/>
</div>
<button type="submit">LOGIN</button>
</form>
</body>
</html>
필더를 이용한 로그인 체크
필터의 개념과 특징
- 필터는 서블릿 객체로 특정 서블릿이나 jsp등에 도달하는 과정에서 필터링 역할을 한다. 즉 인터셉터처럼 요청을 가로채어 특정 작업을 수행하고 요청을 하는 것과 비슷하다.
- 필터를 사용하는 이유는 모든 컨트롤러에 하나하나 로그인 여부를 체크하는 로직을 둘 수 없어서 로그인 체크는 따로 필터로 분리한다.
- 필터는 여러 개가 될 수 있다.
필터의 작동과정
- @WebFilter로 설정한 url에 요청이 들어오면 doFilter 메소드가 호출된다.(즉 인터셉터처럼 중산에 요청을 가로채 doFilter가 실행되도록 한다)
- 호출된 doFilter()에서 내가 구현한 기능이 작동한다
- filterChain은 다음 필터를 가리키고 filterChain.doFilter()는 다음 필터를 호출한다. 이떄 만약 다음 필터에도 filterChain.doFilter()가 있다면 또 다음 필터를 호출한다. 필터가 없다면 요청한 리소스가 호출된다.
로그인 체크 필터
package org.zerock.w3.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;
import java.io.IOException;
// 필터를 사용하는 이유는 모든 컨트롤러에 하나하나 로그인 여부를 체크하는 로직을 둘 수 없어서 로그인 체크는 따로 필터로 분리함
@WebFilter(urlPatterns = {"/todo/*"}) // todo/ 로 시작하는 모든 것에 필터를 적용
@Log4j2
public class LoginCheckFilter implements Filter {
// 1) @WebFilter로 설정한 url에 요청이 들어오면 doFilter 메소드가 호출된다.(즉 인터셉터처럼 중산에 요청을 가로채 doFilter가 실행되도록 한다)
// 2) 호출된 doFilter()에서 내가 구현한 기능이 작동한다
// 3) filterChain은 다음 필터를 가리키고 filterChain.doFilter()는 다음 필터를 호출한다.
// 이떄 만약 다음 필터에도 filterChain.doFilter()가 있다면 또 다음 필터를 호출한다. 필터가 없다면 요청한 리소스가 호출된다.
// 서블릿이 실행되기 전에 처리할 작업은 filterChain.doFilter() 이전에, 서블릿이 실행된 후에 처리할 작업은 filterChain.doFilter() 이후에 작성한다.
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 서블릿 실행 전에 해야할 작업
log.info("LoginCheckFilter");
// HTTP 요청과 응답을 담을 객체 생성(다운캐스팅)
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 세션 객체 생성
HttpSession session = request.getSession();
if (session.getAttribute("loginInfo") == null) {
response.sendRedirect("/login");
return;
}
// 다음 필터호출 or 요청이 목적지로 갈 수 있도록 함
filterChain.doFilter(servletRequest, servletResponse);
}
}
요청에 UTF-8 적용해주는 필터
package org.zerock.w3.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;
import java.io.IOException;
// 필터를 사용하는 이유는 모든 컨트롤러에 하나하나 로그인 여부를 체크하는 로직을 둘 수 없어서 로그인 체크는 따로 필터로 분리함
@WebFilter(urlPatterns = {"/*"})
@Log4j2
// 한글 깨짐 방지를 위해 모든 요청이 UTF-8로 인코딩이 되도록 필터 설정
public class UTF8Filter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 서블릿 실행 전에 해야할 작업
log.info("UTF8Filter");
// HTTP 요청과 응답을 담을 객체 생성(다운캐스팅)
HttpServletRequest request = (HttpServletRequest) servletRequest;
// UTF-8로 인코딩하기
request.setCharacterEncoding("UTF-8");
// 다음 필터호출 or 요청이 목적지로 갈 수 있도록 함
filterChain.doFilter(servletRequest, servletResponse);
}
}
세션을 활용한 로그아웃
package org.zerock.w3.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;
import java.io.IOException;
@WebServlet("/logout")
@Log4j2
public class LogoutController extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info(".........../logout");
// 세션 객체 생성
HttpSession session = request.getSession();
// loginInfo를 세션 저장소에서 제거
session.removeAttribute("loginInfo");
// 생성한 세션 객체를 무효화하고 삭제
session.invalidate();
// 성공 후 리스트로 이동
response.sendRedirect("/");
}
}
회원 정보 입력으로 로그인 하기
jsp에서 id, password 입력 -> Controller -> Service -> DAO
VO, DTO
package org.zerock.w3.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
// VO
@Getter
@Setter
@ToString
@Builder
public class MemberVo {
private String mid;
private String mname;
private String mpw;
}
// DTO
package org.zerock.w3.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
@Builder
@Data // getters and setters, toString, equals, hashCode 등의 메소드를 모두 생성해줌
@NoArgsConstructor
@AllArgsConstructor
public class MemberDto {
private String mid;
private String mname;
private String mpw;
}
Controller
package org.zerock.w3.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;
import org.zerock.w3.dto.MemberDto;
import org.zerock.w3.dto.TodoDto;
import org.zerock.w3.service.MemberService;
import java.io.IOException;
import java.time.LocalDate;
@WebServlet("/login")
@Log4j2
public class LoginController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info(".........../login GET");
// 정상적인 로그인 시 이동
request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info(".........../login POST");
// 요청 객체에서 각 파라미터의 값 가져오기
String mid = request.getParameter("mid");
String pwd = request.getParameter("pwd");
try {
// id와 pwd로 회원 dto 생성
MemberDto memberDto = MemberService.INSTANCE.login(mid, pwd);
HttpSession session = request.getSession();
// 세션에 회원 정보 저장
session.setAttribute("loginInfo", memberDto);
response.sendRedirect("/todo/list");
} catch (Exception e) {
response.sendRedirect("/login?result=error");
}
}
}
Service
package org.zerock.w3.service;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.zerock.w3.dao.MemberDao;
import org.zerock.w3.dao.TodoDao;
import org.zerock.w3.domain.MemberVo;
import org.zerock.w3.domain.TodoVo;
import org.zerock.w3.dto.MemberDto;
import org.zerock.w3.dto.TodoDto;
import org.zerock.w3.util.MapperUtil;
import java.util.List;
import java.util.stream.Collectors;
@Log4j2
public enum MemberService {
INSTANCE;
private MemberDao dao;
private ModelMapper modelMapper;
/**
* 생성자
*/
MemberService() {
dao = new MemberDao();
modelMapper = MapperUtil.INSTANCE.getModelMapper();
}
/**
* 로그인 처리
* @param mid 입력한 id
* @param mpwd 입력한 비밀번호
* @return memberDto
* @throws Exception 데이터베이스에서 데이터 조회를 했는데 일치하는 회원 데이터가 없을 시
*/
public MemberDto login(String mid, String mpwd) throws Exception {
MemberVo memberVo = dao.findByMemberByMidAndMpw(mid, mpwd);
if (memberVo == null) {
throw new RuntimeException("member is null");
}
MemberDto memberDto = modelMapper.map(memberVo, MemberDto.class);
return memberDto;
}
}
DAO
package org.zerock.w3.dao;
import lombok.Cleanup;
import org.zerock.w3.domain.MemberVo;
import org.zerock.w3.domain.TodoVo;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class MemberDao {
public MemberVo findByMemberByMidAndMpw(String mid, String mpw) throws Exception {
MemberVo vo;
String sql = "select * from tbl_member where mid =? and mpw=?";
@Cleanup Connection connection = ConnectionUtil.INSTANCE.getConnection();
@Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, mid);
preparedStatement.setString(2, mpw);
//실행 결과를 resultSet에 담기
@Cleanup ResultSet resultSet = preparedStatement.executeQuery();
// 행 한번 이동
resultSet.next();
vo = MemberVo.builder()
.mid(resultSet.getString("mid"))
.mpw(resultSet.getString("mpw"))
.mname(resultSet.getString("mname"))
.build();
return vo;
}
}
login.jsp
<%--
Created by IntelliJ IDEA.
User: ape07
Date: 25. 1. 30.
Time: 오후 8:14
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>Title</title>
<h1>login Page</h1>
</head>
<body>
<%--쿼리 파라미터 중 result의 값이 error면 보여줌--%>
<c:if test="${param.result == 'error'}">
<h1>Error</h1>
</c:if>
<form method="post" action="/login">
<div>
<input type="text" name="mid" placeholder="아이디 입력"/>
</div>
<div>
<input type="text" name="pwd" placeholder="비밀번호 입력"/>
</div>
<button type="submit">LOGIN</button>
</form>
</body>
</html>
EL의 스코프
EL(Expression Language)에서 스코프(scope)란 변수나 객체가 접근 가능한 범위를 의미한다.
즉, EL에서 변수를 저장할 수 있는 영역을 가리키며, 보통 JSP나 Servlet 환경에서 데이터를 공유하는 범위를 결정한다.
EL의 주요 스코프 종류
EL에서는 JSP와 Servlet에서 제공하는 4가지 기본 스코프를 활용한다. 스코프의 범위는 아래와 같이 page - request - session - application 순이다.
- pageScope
- 현재 JSP 페이지 내에서만 접근 가능
- 같은 페이지 내에서만 데이터를 공유할 때 사용
- <c:set>으로 저장한 변수
- requestScope
- 하나의 요청(HTTP Request) 내에서만 데이터가 유지됨
- 요청이 끝나면 데이터가 사라짐
- 주로 컨트롤러(Servlet)에서 JSP로 데이터를 전달할 때 사용
- HttpServletRequest에 setAttribute()로 저장한 변수
- sessionScope
- 하나의 세션 동안 데이터가 유지됨 (브라우저가 닫히거나 세션이 만료되기 전까지)
- 사용자별 데이터를 저장할 때 사용 (예: 로그인 정보)
- HttpSession의 setAttribute()로 저장한 변수
- applicationScope
- 웹 애플리케이션이 종료될 때까지 데이터가 유지됨
- 모든 사용자와 요청이 공유하는 데이터를 저장할 때 사용
- ServletContext를 이용해 setAttribute()로 저장한 변수
<%--
Created by IntelliJ IDEA.
User: ape07
Date: 25. 1. 30.
Time: 오후 8:14
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>Title</title>
<h1>List Page</h1>
<%--EL은 HTTPServletRequest에서 저장된 객체를 못찾으면 HttpSession에서 저장된 객체를 찾음(스코프 범위 확장). 따라서 ${loginInfo}를 하면 세션에서 값을 찾아 출력함
스코프: 변수나 객체가 접근 가능한 범위
--%>
<h2>${loginInfo}</h2>
<h2>${loginInfo.uname}</h2>
<c:forEach var="dto" items="${todoList}">
<li>
<span><a href="/todo/view?tno=${dto.tno}">${dto.tno}</a></span>
<span>${dto.title}</span>
<span>${dto.dueDate}</span>
<span>${dto.finished == true ? "DONE" : "NOT YET"}</span>
</li>
</c:forEach>
<form method="POST" action="/logout">
<button type="submit">로그아웃</button>
</form>
</head>
<body>
<script>
</script>
</body>
</html>
🔗레퍼런스
참고 강의/글
- 자바 웹 개발 워크북
https://tomining.tistory.com/172
https://curiousjinan.tistory.com/entry/spring-filterchain-dofilter
https://atoz-develop.tistory.com/entry/Servlet-Filter-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
'Web 개발 기초' 카테고리의 다른 글
리스너 (0) | 2025.03.03 |
---|---|
사용자 정의 쿠키(Cookie) (0) | 2025.03.02 |
웹 MVC와 JDBC 결합 (0) | 2025.02.23 |
JDBC (1) | 2024.05.15 |
모델(Model) (1) | 2024.04.28 |