카테고리 없음

42 - 세션 / 쿠키 / 필터 / 리스너 (2)

두개의 문 2023. 6. 27. 23:07
필터를 이용한 로그인 체크 

 

 - 필터 (Servlet Filter ) : 특정한 서블릿이나 JSP 등에 도달하는 과정에서 데이터를 필터링하는 역할을 위해서 존재하는 서블릿 API의 특별한 객체 

    → @WebFilter 어노테이션 이용해 특정한 경로에 접근할 때 필터가 동작하도록 설계하면, 동일한 로직을 필터로 분리 가능 

   • 한 개 이상, 여러 개 적용 가능 

   • @WebFilter 어노테이션의 urlPatterns 속성 이용 : { } 안에 여러 url 주소 작성 가능 

 

 

▷ LoginCheckFilter 클래스

 - Filter 인터페이스  

  • javax.servlet.Filter를 import 해야 함 

  • doFilter라는 추상메서드 존재 → 필터가 필터링이 필요한 로직을 구현해야 함 

  • @WebFilter 어노테이션 이용 : 특정한 경로를 지정해 해당 경로의 요청에 대해서 doFilter( ) 메서드를 실행 

     → 현재 @WebFilter(urlPatterns= {"/todo/*"}) 지정 = '/todo/...'로 시작하는 모든 경로에 대해서 필터링 시도함 

  • FilterChain의 doFilter( ) 메서드를 이용해 다음 필터나 목적지(서블릿, JSP)로 갈 수 있도록 doFilter( ) 메서드 마지막 추가 

 

package org.zerock.w2.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;

@WebFilter(urlPatterns= {"/todo/*"}) 
@Log4j2
// LoginCheckFilter 클래스 : doFilter() 메서드를 구현해야 하는 구현클래스 
public class LoginCheckFilter implements Filter {
	@Override
	public void doFilter(
			ServletRequest request, 
			ServletResponse response, 
			FilterChain chain)
			throws IOException, ServletException {
		log.info("Login check filter...");
		
		// chain.doFilter() 메서드 실제 구현해보기 
		// 세션에 로그인 정보를 가지고 있는지 체크 : session.getAttribute("loginInfo") == null
		// Type mismatch: cannot convert from ServletRequest to HttpServletRequest
		// -> Add cast (다운캐스팅 : 업캐스팅된 데이터를 실제 상속된 자기 자신의 데이터로 형변환 )
		// 다운캐스팅은 진짜 자기 자신의 데이터형으로만 변경될 수 있음 
		// 실무에서는 실제 변형하려는 데이터형인지 검사하는 것도 좋은 프로그램밍 습관임 

		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;
		
		// 클라이언트에서 이전에 같은 클라이언트라는 것을 증명하는 것이 세션 정보이므로,
		// 클라이언트 요청 객체에서 얻음 
		HttpSession session = req.getSession();
		
		// 세션 객체에서 우리가 얻고자 하는 key 변수의 값(=userInfo)을 조사함
		// userInfo key가 없다면, 로그인한 적이 없다는 뜻이므로
		// 로그인 페이지로 돌려보낸다.
		if(session.getAttribute("userInfo") == null) {
			res.sendRedirect("/login");
			// 메서드 실행이 끝났으므로, 다음 실행을 막기 위해 return문 사용 
			return;
		}
		
		
		// 메인 필터가 끝났으므로, 남아있는 필터를 실행한다.
		// chain.doFilter() 메서드의 경우, 
		// try - catch - finally 문법에서 finally 부분 같은 기능을 함 
		chain.doFilter(request, response);
		
	}	
	
}

 

 

▷ 로그인 체크 구현 

 - LoginCheckFilter의 경우, '/todo/...'로 시작하는 모든 자원에 접근할 때 동작하도록 설정 

   → 로그인 여부를 체크하도록 수정 

 

@WebFilter(urlPatterns= {"/todo/*"}) 
@Log4j2
// LoginCheckFilter 클래스는 doFilter() 메서드를 구현해야 하는 구현클래스 
public class LoginCheckFilter implements Filter {
	@Override
	public void doFilter(
			ServletRequest request, 
			ServletResponse response, 
			FilterChain chain)
			throws IOException, ServletException {
            
		log.info("Login check filter...");
		
		// ServletRequest : 부모 | HttpServletRequest : 자식 
        	// 다운캐스팅을 해주어야 HTTP와 관련된 작업 가능 
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;
		
		// 클라이언트 요청 객체로부터 세션 정보를 얻음 
        	// ∵ 세션 정보의 로그인기록을 통해 이전의 클라이언트와 동일한 클라이언트라는 것을 증명 가능 
		HttpSession session = req.getSession();
		
		// 세션 객체에서 우리가 얻고자 하는 key 변수의 값(=userInfo)을 조사함
		// userInfo key가 없다면, 로그인한 적이 없다는 뜻이므로
		// 로그인 페이지로 돌려보낸다.
		if(session.getAttribute("userInfo") == null) {
			res.sendRedirect("/login");
			// 메서드 실행이 끝났으므로, 다음 실행을 막기 위해 return문 사용 
			return;
		}
		
		// 메인 필터 종료 → 남아있는 필터 실행 또는 다른 목적지(서블릿, JSP)로 이동 
		chain.doFilter(request, response);
	}	
}

 

 

 - 서버 실행시킨 후 '/todo/list'를 호출하면, 브라우저는 LoginCheckFilter를 통해서 '/login'으로 이동하게 됨 

 

 - 로그인 정보를 전달해서 로그인이 처리되면, 이후로는 '/todo/...' 경로 이용 가능 

 

 

 


UTF-8 처리 필터 

 

package org.zerock.w2.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.log4j.Log4j2;

// 모든 요청 주소에 대해서 한글필터 적용 
@WebFilter(urlPatterns= {"/*"})
@Log4j2
public class UTF8Filter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
        log.info("UTF-8 Filter 처리...");
        
		// HTTP에 관련된 작업을 하기 위해 다운캐스팅 필수 
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;
		
		// 요청값으로 들어오는 데이터 호환을 위한 문자코드체계 설정 : 보통 UTF-8로 설정 
		req.setCharacterEncoding("UTF-8");
		
		
		// 웹 브라우저의 요청결과로 보일 페이지의 contentType을 설정
		res.setContentType("text/html; charset=UTF-8");
		
		// 나머지 필터 정리 
		// 들어왔던 필터 정보 그대로 보내야 함 ( req, res 아님 )
		chain.doFilter(request, response);
	}
}

 

 - UTF8Filter 테스트 : TodoRegisterController 작성한 출력스트림 확인 

   • TodoRegisterController에 아래 코드 추가 후 

   • UTF8Filter의 아래 코드를 주석처리할 경우, 한글이 깨져서 출력됨 

   • 주석을 풀고 다시 실행하게 되면, 한글이 깨지지 않고 출력되는 것 확인 가능 

 

 


세션을 이용하는 로그아웃 처리 

 

- 보안 정책 상, 로그아웃하는 과정은 GET 방식 보다는 POST 방식으로!

 • doGet() 메서드 만들지 않는 것이 좋다.

 • doGet() 메서드의 접근제어자를 private로 지정하기 

   (외부에서 접근 못하는 것이 맞지만, 어떤 이유로든 불려질 가능성 있음 애초에 만들지 말자 )

   -> 불필요한 메서드를 만들지 말자. 

 

- HttpSession을 이용하는 경우

  ① 간단하게 로그인 확인 시 사용했던 정보 삭제하는 방식 : removeAttribute( String name ) 메서드 이용 

  ② 현재의 HttpSession이 더 이상 유효하지 않도록 만드는 방식 : invalidate( ) 메서드 이용

  → 관련있는 세션 항목을 삭제한 후, 무효화시키기 

 

package org.zerock.w2.controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import lombok.extern.log4j.Log4j2;

@Log4j2
@WebServlet(name="todoLogoutController", urlPatterns="/logout")
public class TodoLogoutController extends HttpServlet {
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		
        log.info("Logout 처리 중...");
		
        	// 요청객체로부터 세션 얻기 
		HttpSession session = req.getSession();
		
        	// 세션의 원하는 속성을 제거한 후, 세션을 무효화시킨다.
		// ➊ 세션 객체에서 속성 삭제 : removeAttribute("속성 키")
		// ❷ 세션 객체 무효화 : invalidate()
		session.removeAttribute("userInfo");
		session.invalidate();
		
		// 로그아웃했으므로, 정책에서 정해진 페이지로 이동 
		// 보통 메인 페이지(시작페이지)로 이동시킴 
		res.sendRedirect("/");
	}
	
}

 

 

 list.jsp register.jsp 로그아웃 버튼 만들고 실행시켜 보기 

 - 로그아웃을 실행하기 위해 list.jsp 및 register.jsp 수정 

  • <form> 태그 이용해 버튼 추가 

   ( register.jsp에도 동일한 코드 추가함 )

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
세션정보 : <br />
loginInfo : ${ userInfo }<br />
loginInfo.mname : ${ userInfo.mname }
<h1> 해야할 일 목록 리스트 </h1>
<!-- 로그아웃 버튼 추가 -->
<form action="/logout" method="post"> 
	<button>Logout</button>
</form>
</body>
</html>

 

- 로그인한 후, list화면의 Logout 버튼 클릭 → 'localhost'로 이동해 시작페이지 출력 → 그 상태에서 '/todo/register' 호출 시 다시 로그인 화면 출력됨 

 

 

 


데이터베이스에서 회원 정보 이용하기 

 

 - 이제 실제 데이터베이스를 이용해서 회원정보를 구성한 후, 활용해보자

 

- localhost:3306 연결된 localhost 이름 변경

 

 

 - SQL 편집기 열어 member 테이블 생성 

 • 식별자에 ‘_’를 이용한 스네이크표기법 이용 

   테이블이라는 것을 쉽게 파악할 수 있게 ‘tbl_’를 표기해주기  

   ex ) 컬럼명 : ‘col_’

          데이터베이스명 : ‘db_’

   archer : 문자열

 

 

 - 테이블에 사용자 데이터 추가 : INSERT

 

 

 - 실제 로그인 시, 이용할 SQL 테스트  : 로그인 사용자 정보 조회 질의(Query)

 

 


자바에서 회원 데이터 처리하기 

 

- 자바의 변수명과 DB 테이블의 컬럼명을 서로 동일하게 처리함 (다만, 표기법의 차이 - 카멜 / 스네이크 )

- 자바에서 객체로 처리하도록 V0 / DAO 등을 구현함 

 

▷ MemberVO

package org.zerock.w2.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter		// VO : ReadOnly
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MemberVO {
	private String mid;
	private String mpw;
	private String mname;
	private String uuid;
}

 

 

▷ MemberDAO

package org.zerock.w2.domain;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.zerock.jdbcex.dao.ConnectionUtil;
import lombok.Cleanup;

public class MemberDAO {
	
    public MemberVO getWithPassword(String mid, String mpw) throws Exception {
		// DB에 연결하기 위해 SQL문 작성 
        String query = """
				SELECT mid, mpw, mname FROM tbl_member
					WHERE mid = ? AND mpw = ?;
				""";
		
        	// MemberVO를 지역변수로 선언 
		MemberVO memberVO = null;
		
		@Cleanup Connection conn = ConnectionUtil.INSTANCE.getConnection();
		@Cleanup PreparedStatement pstmt = conn.prepareStatement(query);
		
		// pstmt에 파라미터 추가 
		pstmt.setString(1, mid);
		pstmt.setString(2, mpw);
		
		@Cleanup ResultSet rs = pstmt.executeQuery();
		
		// 데이터가 하나 있다는 것을 알고 있지만, 실제로는 while 루프문 이용하기 
		rs.next();
		
		memberVO = MemberVO.builder()
				.mid(rs.getString(1))
				.mpw(rs.getString(2))
				.mname(rs.getString(3))
				.build();
                
		return memberVO;
	}
}

 

• Lombok의 @Builder를 통해 builder 메서드 이용
  → 필요한 데이터에 대해서 메서드를 통해 step-by-step으로 값을 입력 받음
  → build( ) 메서드를 통해 최종적으로 하나의 인스턴스를 리턴하는 방식 

 

 

 

▷ MemberDTO 

package org.zerock.w2.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data 
@Builder		
@AllArgsConstructor
@NoArgsConstructor
public class MemberDTO {
	private String mid;
	private String mpw;
	private String mname;
	private String uuid;
}

 

 

 

▷ MemberService 

 - 컨트롤러들의 비즈니스 로직을 처리하는 역할 → CRUD(등록/조회/수정/삭제) 기능들을 서비스 객체에 모아서 구현함 

 - 내부적으로 DAO를 이용해 처리함 → 멤벼변수로 DAO를 가짐 

 - 모델로 보낼 데이터를 비즈니스 로직에 맞게 정제하는 역할 

 - ModelMapper 이용해 DTO VO 간의 변환 처리함 

 

 - 로그인 처리를 위한 login( ) 메서드 작성 

package org.zerock.w2.service;

import org.modelmapper.ModelMapper;
import org.zerock.jdbcex.util.MapperUtil;
import org.zerock.w2.domain.MemberDAO;
import org.zerock.w2.domain.MemberDTO;
import org.zerock.w2.domain.MemberVO;
import lombok.extern.log4j.Log4j2;

@Log4j2
public enum MemberService {
	INSTANCE;
	
	private MemberDAO dao;
	private ModelMapper modelMapper;
	
	MemberService(){
		dao = new MemberDAO();	// 생성자를 이용해 DAO 초기화 
		modelMapper = MapperUtil.INSTANCE.get(); 
	}
	
	// 로그인 체크 
	public MemberDTO login(String mid, String mpw) throws Exception {
    	// MemberDAO의 getWithPassword() 메서드를 통해 MemberVO 반환 
		MemberVO vo = dao.getWithPassword(mid, mpw);
        // 반환된 MemberVO를 modelMapper를 이용해 MemberDTO로 변환 
		MemberDTO memberDTO = modelMapper.map(vo, MemberDTO.class);
		
		return memberDTO;
		
	}

	public void updateUuid(String mid, String uuid) throws Exception {
	
		dao.updateUuid(mid, uuid);
		
	}
}