프로젝트 세팅
사용한 라이브러리

properties 파일 내용 :
# 0. UTF-8 설정
server.servlet.encoding.charset=utf-8
server.servlet.encoding.force=true
# 1. DB 연결
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
# 2. hibernate 세팅
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
# 3. 더미데이터 세팅
spring.sql.init.data-locations=classpath:db/data.sql
spring.jpa.defer-datasource-initialization=true
더미데이터 내용(sql파일) :
insert into user_tb(username, password, email, created_at) values('ssar', '1234', 'ssar@nate.com', now());
insert into user_tb(username, password, email, created_at) values('cos', '1234', 'cos@nate.com', now());
로그인 기능 구현하기
Controller :
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
private final HttpSession session;
@PostMapping("/login")
public String login(UserRequest.LoginDTO loginDTO) {
User sessionUser = userService.로그인(loginDTO);
session.setAttribute("sessionUser", sessionUser);
return "redirect:/";
}
}
@PostMapping("/login")
: 이 메서드는 HTTP POST 요청을/login
경로로 받습니다. 이는 로그인 요청을 처리하는 엔드포인트입니다.
로그인은 반드시 post 요청으로 작성합니다. (민감한 부분이기 때문)
public String login(UserRequest.LoginDTO loginDTO)
:loginDTO
는 사용자가 로그인할 때 입력한 데이터를 포함하는 객체입니다.loginDTO
객체를 통해 사용자 입력 데이터를 받아 처리합니다.userService.로그인(loginDTO)
를 호출하여 로그인 비즈니스 로직을 처리하고, 인증된 사용자 객체를 반환받습니다.session.setAttribute("sessionUser", sessionUser)
를 호출하여 로그인된 사용자 정보를 세션에 저장합니다. 이를 통해 사용자는 세션이 유지되는 동안 인증된 상태를 유지할 수 있습니다.return "redirect:/"
: 로그인이 성공하면 루트 경로로 리다이렉트하여 홈 페이지로 이동합니다.
(해당 예제는 유효성 검사에 대한 로직을 작성하지 않은 상태입니다.)
- 컨트롤러가 반환 받는 객체는 항상 로그인에 정상적으로 성공했을 때만 받게 됩니다.
why? : 전달받은 데이터의 유저가 없거나, 또는 아이디 또는 비밀번호가 틀렸을 때 service 또는 repository 객체에서 예외를 throw 하도록 작성하였기 때문.
service, repository 객체 세부 내용은 아래에 서술.
Service :
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
public User 로그인(UserRequest.LoginDTO loginDTO) {
User userPS = userRepository.findByUsername(loginDTO.getUsername());
if (!userPS.getPassword().equals(loginDTO.getPassword())) {
throw new RuntimeException("아이디 혹은 패스워드가 일치하지 않습니다.");
}
return userPS;
}
}
public User 로그인(UserRequest.LoginDTO loginDTO)
: loginDTO
는 사용자가 로그인할 때 입력한 데이터를 포함하는 객체입니다.userRepository.findByUsername(loginDTO.getUsername())
: 입력된 사용자 이름으로 데이터베이스에서 사용자를 조회합니다.
- 비밀번호 검증: 조회된 사용자의 비밀번호와 입력된 비밀번호가 일치하는지 확인합니다.
- 만약 비밀번호가 일치하지 않으면,
RuntimeException
을 발생시켜 로그인 실패를 처리합니다.
- 비밀번호가 일치하면, 조회된 사용자 객체를 반환합니다.
아직 커스텀예외클래스를 작성하지 않은 상태이기 때문에 런타임예외로 대체하였습니다.
Repository :
@Repository
@RequiredArgsConstructor
public class UserRepository {
private final EntityManager em;
public User findByUsername(String username) {
Query q = em.createQuery("select u from User u where u.username = :username", User.class);
q.setParameter("username", username);
try {
return (User) q.getSingleResult();
} catch (RuntimeException e) {
throw new RuntimeException("아이디 혹은 패스워드가 일치하지 않습니다.");
}
}
}
- 쿼리 작성 및 파라미터 설정:
em.createQuery
: JPQL(JPA Query Language)을 사용하여 사용자 이름으로User
엔티티를 조회하는 쿼리를 생성합니다.q.setParameter("username", username)
: 쿼리 파라미터로 사용자 이름을 설정합니다.
- 결과 반환 및 예외 처리:
q.getSingleResult()
: 쿼리를 실행하여 단일 결과를 반환합니다. 결과가 없거나 여러 결과가 있을 경우 예외가 발생할 수 있습니다.- 예외 처리:
RuntimeException
이 발생하면, "아이디 혹은 패스워드가 일치하지 않습니다."라는 메시지로 새 예외를 던집니다. 이는 사용자에게 적절한 오류 메시지를 전달하기 위함입니다.
Optional 클래스(null처리 관련)를 이용하지 않고 try-catch 문을 사용한 이유?
q.getSingleResult(); 메서드에서 쿼리문으로 조회했을 때 만약 유저가 없다면 null이 반환되는 것이 아니라, NoResultException 예외를 발생시킵니다.
따라서 해당 예외가 발생하였을 때 개발자가 원하는 방향(예제에서는 throw 런타임예외)으로 컨트롤하기 위해 try-catch를 사용하였습니다.
또한 catch로 가지 않을 시 반드시 객체가 반환 될 것이기 때문에 굳이 Optional 클래스로 감싸지 않고 바로 반환하였습니다.
로그인 테스트
- 현재 ‘username = ssar, password = 1234’ 더미데이터가 DB에 저장되어있는 상태입니다.
- Postman으로 테스트를 진행하였습니다.

- 로그인 요청:
- 사용자가
username
과password
를 포함하여http://localhost:8080/login
경로로 HTTP POST 요청을 보냅니다.
- JSESSIONID 생성:
- 서버는 사용자의 인증 정보를 확인한 후, 로그인에 성공하면 고유한 세션 ID (
JSESSIONID
)를 생성합니다. (위 예제는 로그인에 성공한 경우) - 생성된 세션 ID는 서버 측 세션 스토리지에 저장됩니다.
- 쿠키를 통해 세션 ID 전달:
- 서버는 응답으로
JSESSIONID
를 쿠키에 담아 클라이언트(브라우저)로 전송합니다. - 클라이언트는 이 쿠키를 저장하며, 이후 모든 요청에 대해 해당 쿠키를 자동으로 서버에 전달합니다.
- 세션 확인:
- 클라이언트가 이후 다른 요청을 보낼 때마다, 브라우저는
JSESSIONID
쿠키를 함께 전송합니다. - 서버는 요청에 포함된
JSESSIONID
쿠키 값을 확인하여 해당 세션 ID가 유효한지 확인합니다. - 유효한 세션 ID가 있으면, 서버는 사용자가 로그인된 상태로 간주하고 요청을 처리합니다.
POST 요청 후 다른 경로를 요청했을 때

이미지와 같이 위에서 전달 받은 JSESSIONID 의 Value 값을 쿠키로 함께 전송하는 것을 확인할 수 있습니다.
필터(Filter)
필터(Filter)는 서블릿 컨테이너에서 요청과 응답을 가로채고 처리할 수 있는 객체입니다. 주로 웹 애플리케이션의 특정 기능을 구현하기 위해 사용되며, 요청이나 응답을 변경하거나 로깅, 인증, 인가 등의 작업을 수행하는 데 유용합니다.
필터의 주요 개념
- 요청 및 응답 가로채기:
- 필터는 클라이언트에서 서버로 들어오는 요청이나 서버에서 클라이언트로 나가는 응답을 가로채서 처리할 수 있습니다.
- 재사용 가능한 컴포넌트:
- 필터는 여러 서블릿이나 JSP에 대해 공통적으로 수행해야 하는 작업을 하나의 필터로 구현하여 재사용할 수 있습니다.
- 체인 구조:
- 여러 필터를 체인(chain) 구조로 연결하여 순차적으로 실행할 수 있습니다. 각 필터는 다음 필터를 호출하거나 체인을 중단할 수 있습니다.
필터의 라이프사이클
init()
:- 필터가 초기화될 때 한 번 호출됩니다. 초기화 작업을 수행할 수 있습니다.
doFilter()
:- 필터의 핵심 메서드로, 클라이언트의 요청을 가로채고 필요한 작업을 수행한 후 다음 필터 또는 서블릿을 호출합니다.
destroy()
:- 필터가 제거될 때 호출됩니다. 리소스를 해제하는 등의 정리 작업을 수행할 수 있습니다.
예시 :
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
resp.sendRedirect(req.getContextPath() + "/login-form");
}
위 내용은 session에 sessionUser라는 값이 있는지 확인 후 (로그인 체크) 없다면 로그인 페이지로 강제 이동시키는 내용의 일부입니다.
글 쓰기, 수정, 삭제, 댓글달기 등등은 모두 로그인이 되어있는 상태에서만 가능해야하며, 결과적으로 위의 코드 내용을 필요한 컨트롤러 내부의 메서드마다 작성해야하는 불편함이 있습니다.
해당 문제를 필터를 이용하여 해결해보겠습니다.
필터 클래스 생성
public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
HttpSession session = req.getSession();
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
resp.sendRedirect(req.getContextPath() + "/login-form");
} else {
chain.doFilter(request, response);
}
}
}
- 필터 클래스 정의:
public class AuthenticationFilter implements Filter {
AuthenticationFilter
클래스는 Filter
인터페이스를 구현합니다. 이 필터는 클라이언트의 요청을 가로채고, 요청 처리 전에 인증을 검사합니다.- doFilter 메서드:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
doFilter
메서드는 필터의 핵심 메서드로, 요청을 가로채고 처리를 수행한 후 다음 필터 또는 서블릿으로 전달합니다.- HttpServletRequest 및 HttpServletResponse 캐스팅, 세션 가져오기:
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
HttpSession session = req.getSession();
ServletRequest
와 ServletResponse
객체를 HttpServletRequest
와 HttpServletResponse
로 캐스팅하여, HTTP 요청과 응답에 대한 구체적인 기능을 사용할 수 있습니다.HttpServletRequest
객체를 통해 세션을 가져옵니다. 세션은 사용자와 서버 간의 상태 정보를 저장하는 공간입니다.- 세션에서 사용자 정보 가져오기:
User sessionUser = (User) session.getAttribute("sessionUser");
세션에서
sessionUser
라는 속성을 가져옵니다. 이 속성은 로그인된 사용자 정보를 담고 있습니다.- 인증 여부 검사:
if (sessionUser == null) {
resp.sendRedirect(req.getContextPath() + "/login-form");
}
sessionUser
가 null
이면 사용자가 로그인되지 않은 상태로 간주하고, 로그인 페이지(/login-form
)로 리다이렉트합니다.- 인증된 사용자 처리:
else {
chain.doFilter(request, response);
}
sessionUser
가 null
이 아니면 사용자가 인증된 상태로 간주하고, 요청을 다음 필터 또는 서블릿으로 전달합니다.Filter를 import 할때는 jakarta (구버전은 javax)로 해야 합니다.
import jakarta.servlet.Filter;
설정 파일 생성
@Configuration
public class FilterConfig {
@Bean // 리턴값이 IoC에 등록됨
public FilterRegistrationBean addAuthFilter() {
FilterRegistrationBean rg = new FilterRegistrationBean();
rg.setFilter(new AuthenticationFilter());
rg.addUrlPatterns("/board/*");
rg.addUrlPatterns("/user/*");
rg.setOrder(1);
return rg;
}
}
Spring Framework에서 필터를 설정하고 등록하는 구성 클래스입니다.
FilterConfig
클래스는 인증 필터인 AuthenticationFilter
를 특정 URL 패턴에 적용하도록 구성합니다.
- @Configuration 어노테이션:
@Configuration
public class FilterConfig {
- @Bean 어노테이션:
@Bean // 리턴값이 IoC에 등록됨
public FilterRegistrationBean addAuthFilter() {
addAuthFilter
메서드는 FilterRegistrationBean
을 반환하며, 이는 IoC 컨테이너에 등록됩니다.- FilterRegistrationBean 생성:
FilterRegistrationBean rg = new FilterRegistrationBean();
rg.setFilter(new AuthenticationFilter());
FilterRegistrationBean
객체를 생성하고, AuthenticationFilter
를 필터로 설정합니다. AuthenticationFilter
는 사용자 인증을 처리하는 필터입니다.- URL 패턴 등록:
rg.addUrlPatterns("/board/*");
rg.addUrlPatterns("/user/*");
/board/*
와 /user/*
패턴에 대해 필터가 적용됩니다. 따라서, 이 경로에 대한 요청은 모두 AuthenticationFilter
를 거치게 됩니다.- 필터 순서 설정:
rg.setOrder(1);
setOrder(1)
는 이 필터가 첫 번째로 실행됨을 의미합니다.- FilterRegistrationBean 반환:
return rg;
FilterRegistrationBean
을 반환하여 IoC 컨테이너에 등록합니다.참고 사항
필터는 @Component 어노테이션을 필터 클래스 상단에 붙여서 만들수도 있습니다.
다만 아래와 같은 차이점이 있습니다.
- 기존의 설정 파일로 등록 : 더 많은 제어와 세부 설정이 가능 (URL 패턴, 필터 순서 등).
- @Component로 등록 : 간단하게 필터를 등록하고 자동 감지. 기본적으로 모든 요청에 대해 적용.
Share article