[spring] 쿠키, 세션, 리다이렉트, 인터셉트
주제
http프로토콜의 stateless 특성 및 문제점과 대안으로서 쿠키, 세션에 대해 다룬다.
리다이렉트, 인터셉트에 대해 알아본다.
세션, 쿠키
사용 이유: stateless Protocol
웹 서비스에서 사용하는 http protocol은 클라이언트와 서버 간 요청 및 응답이 이뤄지면 연결을 해제한다.
이를 connectioneless 혹은 stateless 하다고 부른다.
이 방식은 서버 부담을 줄여주나, 사용자 인증이 필요한 작업(로그인 상태 유지, 회원 기능)을 구현하기 어렵다.
이처럼 요청자를 기억하기 위한 수단으로서 세션과 쿠키를 사용한다.
세션(server-side session)
먼저 세션 데이터란, 클라이언트나 브라우저 양쪽 모두에 존재하는 데이터 저장 방식의 하나로, 비영구적 저장 데이터다. 즉, 클라이언트는 탭이나 창이 닫히면 소실되는 데이터이고, 서버는 가동이 멈추면 사라지는 데이터다.
로그인 구현에서는 서버 사이드 세션에 로그인한 유저 정보를 저장하고 세션에 저장된 유저 정보의 고유 아이디를 클라이언트에 쿠키로 부여한다.
그리고 유저가 재요청 시 쿠키에 담긴 세션 아이디가 서버 사이드 세션에 존재하는지 비교하여 회원여부를 확인한다.
쿠키
클라이언트 측 브라우저에서 세션 스토리지, 로컬 스토리지 등과 더불어 대표적인 저장소이다.
저장용량은 도메인 당 4KB정도로 작으며 기본적으로 만료 기한 동안 저장되는 반영구적 저장 방식이다.
쿠키는 특정 도메인에 rquest 전송 시, 자동으로 함께 전달된다.
보안 측면에서 클라이언트에 저장되므로 탈취의 위험성이 있어서 민감한 정보는 저장하지 않고, 기본적으로 세션 아이디를 저장하는데 사용된다.
HTTPS를 통해 전송할 때만 사용되는 Secure 속성, 클라이언트 스크립트에서 접근하지 못하도록 하는 HttpOnly 속성 등이 있다,
로그인 기능 구현하기(세션)
1. 회원가입 form, 로그인 form 구성(jsp, jstl)
spring form 라이브러리를 사용하여 커맨드 객체에 바인딩하였다.
사용하기 위해서는 해당 jsp를 호출한 컨트롤러 메소드에서 커맨드 객체가 필요하다.
로그인 form도 이와 비슷하게 user 객체에 바인딩하는 방식으로 구현하였다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>MEMBER JOIN</h1>
<form:form commandName="user" action="${cp}/user/joinSubmit" method="post">
<table>
<tr>
<td>ID</td>
<td><form:input path="id" /></td>
</tr>
<tr>
<td>PW</td>
<td><form:password path="password" /></td>
</tr>
<tr>
<td>닉네임</td>
<td><form:input type="text" path="username" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Join" >
<input type="reset" value="Cancel" >
</td>
</tr>
</table>
</form:form>
<a href="${cp}/">MAIN</a>
</body>
</html>
2. 컨트롤러: login 확인 후 세션 부여
컨트롤러에서 클라이언트가 입력한 정보가 유저 db에 있는지 확인하는 메소드를 만들었다.
이 후, 로그인을 인정하면 httpServletRequest 혹은 httpSession 사용하여 session에 유저의 정보를 담았다.
세션은 서버 사이드에 저장되며, request의 sessionId로 참조가능하다.
@RequestMapping(value = "/loginSubmit", method = RequestMethod.POST)
public String loginSubmit(User user, HttpServletRequest req) {
User selectUser= userService.memberSearch(user.getId(), user.getPassword());
if(selectUser == null) return "user/loginFail";
HttpSession httpSession = req.getSession();
httpSession.setAttribute("sessionUser", selectUser);
return "user/loginOK";
}
3. 세션으로 로그인 여부 확인하기
el 표현식과 jstl로 session 내 sessionUser 여부를 확인한다.
<%@ 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>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>home</h2>
<c:choose>
<c:when test="${!empty sessionUser}">
<h3>USER</h3>
<p>id : ${sessionUser.id}</p>
<p>id : ${sessionUser.password}</p>
<p>id : ${sessionUser.username}</p>
</c:when>
<c:otherwise>
<h3>GUEST</h3>
</c:otherwise>
</c:choose>
<c:if test="${empty sessionUser}">
<a href="${cp}/user/joinForm">Join</a>
<a href="${cp}/user/loginForm">LoginForm</a>
</c:if>
<c:if test="${!empty sessionUser}">
<a href="${cp}/user/logout">logout</a>
</c:if>
</body>
</html>
4. 로그아웃
아래처럼 로그아웃 시에는 session 자체를 날리거나, 로그인 관련 session 키 데이터를 삭제해주면 된다.
스프링 웹 프로젝트에서는 아래처럼 유저에게 할당된 session 자체를 삭제하면 request.cookie에 자동 할당된 JSESSIONID를 삭제하고 새로 부여한다.
@RequestMapping(value = "logout", method =RequestMethod.GET)
public String logout(HttpServletRequest req) {
HttpSession httpSession = req.getSession();
httpSession.invalidate();
return "home";
}
5. 주요 메소드
- getId() 세션 ID를 반환
- setAttribute() 세션 객체에 속성을 저장
- getAttribute() 세션 객체에 저장된 속성을 반환
- removeAttribute() 세션 객체에 저장된 속성을 제거
- setMaxInactiveInterval() 세션 객체의 유지시간을 설정
- getMaxInactiveInte rval() 세션 객체의 유지시간을 반환
- invalidate() 클라이언트에 할당된 세션 객체를 제거
쿠키
1. 생성 및 삭제
new Cookie로 생성
setMaxAge로 삭제
@RequestMapping(value = "mall/submit", method =RequestMethod.GET)
public String mallSubmit(Mall mall, HttpServletResponse res) {
Cookie genderCookie =new Cookie("gender",mall.getGender());
if(mall.isCookieDel()) {
genderCookie.setMaxAge(0);
mall.setGender(null);
} else {
genderCookie.setMaxAge(60*60*24*30);
}
res.addCookie(genderCookie);
return "user/mallSubmit";
}
2. 쿠키 꺼내기
@CookieValue 어노테이션으로 참조 가능하다.
value로 가져올 쿠키 키를 설정하고, required로 익셉션 발생 여부를 제어 가능하다.
@RequestMapping(value = "mall/index", method =RequestMethod.GET)
public String mallIndex(Mall mall,
@CookieValue(value = "gender", required = false) Cookie genderCookie
,HttpServletRequest req) {
if(genderCookie !=null) mall.setGender(genderCookie.getValue());
return "user/mall";
}
리다이렉트
리다이렉트는 model을 사용하는 경우와 modelAndView를 사용하는 경우로 구분된다.
model
if(member == null) {
return "redirect:/";
}
else {
model.addAttribute("user", userService.Search(user));
}
modelAndview
@RequestMapping(value = "/user/login")
public ModelAndView login(HttpServletRequest req) {
ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:/");
return mav;
}
인터셉트
인터셉터는 리퀘스트를 컨트롤러가 받기 이전이나 이후에 요청을 가로채서 작업하는 미들웨어의 역할을 한다.
주로 로깅, 인증, 권한 부여, 성능 모니터링 및 기타 전처리 또는 후처리 작업에 사용된다.
방법
인터페이스 구현
handlerInterceptor 인터페이스를 상속하여 인터셉터 클래스를 구현한다.
메소드
- preHandle | 컨트롤러가 요청을 받기 이전에 실행. true 반환 시 요청 재개. false 반환 시 요청 중단. 일반적으로 가장 많이 사용한다.
- postHandle | 컨트롤러의 요청 처리 이후, 뷰 호출 이전에 실행된다.
- afterCompletion | 뷰 로직의 실행 이후, 응답 이전에 실행된다.
package com.bs.user.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.bs.user.service.entity.User;
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("preHandle: 요청 처리 전");
// TODO Auto-generated method stub
HttpSession httpSession = request.getSession(false);
if(httpSession !=null) {
User user= (User) httpSession.getAttribute("sessionUser");
if(user!=null) {
return true;
}
}
response.sendRedirect(request.getContextPath() +"/");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 요청 처리 후, 뷰 렌더링 전");
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion: 뷰 렌더링 후");
// TODO Auto-generated method stub
}
}
url 매핑
인터셉터를 url에 매핑하기 위한 작업으로 스프링 컨텍스트 설정파일을 이용할 때는 다음같이 한다.
<interceptors>
<interceptor>
<mapping path="user/profile/dashboard" />
<mapping path="user/profile/dashboard/edit" />
<mapping path="user/profile/dashboard/remove" />
<beans:bean
class="com.bs.user.interceptor.UserInterceptor"></beans:bean>
</interceptor>
</interceptors>
범용 url로 설정할 때는 다음 같이 한다.
<interceptors>
<interceptor>
<mapping path="/user/**/*" />
<exclude-mapping path="/user/join/form" />
<exclude-mapping path="/user/join/submit" />
<exclude-mapping path="/user/login/form/submit" />
<beans:bean
class="com.bs.user.interceptor.UserInterceptor"></beans:bean>
</interceptor>
</interceptors>
@Configure 클래스 파일에서는 다음같이 등록한다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**") // 모든 경로에 대해 인터셉터 적용
.excludePathPatterns("/exclude/**"); // 특정 경로 제외
}
}