2015년 12월 3일 목요일

Redis Spring Session

이번 포스팅에서는 Redis Spring Session을 이용하여 클라우드 환경에서 일관된 세션 유지를 수행하는 방법을 알아본다.

이를 처리하기 위해 간단한 로그인을 수행하는 프로젝트를 생성하고 로그인 한 사용자의 세션 정보가 유지되는 환경을 구성해 본다.

먼저 Spring mvc 프로젝트를 하나 생성한다.

생성 시 클라우드 환경에서 동작하는 방식을 시뮬레이션 하기 위해 Tomcat WAS 셋팅을 아래와 같이 한다.



Spring Redis Session을 이용하기 위한 Maven 설정

          <dependency>
  <groupId>org.springframework.session</groupId>
   <artifactId>spring-session-data-redis</artifactId>
   <version>1.0.2.RELEASE</version>
   <type>pom</type>
  </dependency>
  <dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.2.0</version>
  </dependency>


Redis Spring session에 관한 정보는 아래의 사이트에서 확인이 가능하다.

http://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-redis

Spring Redis Session을 이용하기 위한 Web.xml 설정

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 <!-- The definition of the Root Spring Container shared by all Servlets 
  and Filters -->
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
  /WEB-INF/spring/appServlet/sessionRedis.xml
  /WEB-INF/spring/root-context.xml
  </param-value>
 </context-param>

 <!-- Creates the Spring Container shared by all Servlets and Filters -->
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

 <!-- Processes application requests -->
 <servlet>
  <servlet-name>appServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>appServlet</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
 <!--  WAS에서 제공하는 세션 정보를 redis를 통해서 전달하게 처리하는 Filter 설정 -->
 <filter>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
</web-app>



Redis 서버 설정 파일

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

 <context:annotation-config />
 <bean
  class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" />
 <bean
  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" 
  p:port="8000" p:hostName="192.168.0.105" />
</beans>



간단한 로그인 처리를 위한 view 와 Controller 선언

@Controller
public class HomeController {

 private static final Logger logger = LoggerFactory
   .getLogger(HomeController.class);

 @RequestMapping({ "/session", "/stats" })
 public String showSession(HttpSession session, Model model) {
  if (session.getAttribute("id") == null)
   return "redirect:/Login";

  model.addAttribute("sessionId", session.getId());
  model.addAttribute("sessionNew", session.isNew());
  model.addAttribute("name", session.getAttribute("id"));
  return "session";
 }

 @RequestMapping(value = "/", method = RequestMethod.GET)
 public String home(HttpSession session, Model model) {
  if (session.getAttribute("id") == null)
   return "redirect:/Login";

  return "home";
 }
}


@Controller
public class LogInController {

 @RequestMapping(value = "/Login", method = RequestMethod.GET)
 public String login(Locale locale, Model model) {

  return "Login";
 }

 @RequestMapping(value = "/LoginCheck", method = RequestMethod.POST)
 public String loginCheck(@RequestParam String username,
   @RequestParam String password, HttpSession session) {
  // Temporary password matching
  if (password.equals("pass")) {
   session.setAttribute("id", username);
   return "home";
  }
  return "Login";
 }
}


<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Log In page</title>
</head>
<body>
 <div id="form-login">
  <h2>login</h2>
  <form:form method="post" action="/redisSession/LoginCheck">
   <input type="text" name="username" placeholder="UserName" />
   <input type="password" name="password" placeholder="Password" />
   <input type="submit" value="login" />
  </form:form>
 </div>
</body>
</html>


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
    <title>Spring Session Abstraction</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p>Current session:  ${sessionId}</p>
<p>Is new session:  ${sessionNew}</p>
  <p>your id :  ${id}</p>
</body>
</html>


모든 설정이 완료되었다. 이제 테스트를 수행해보자.

먼저 localhost:8080 주소를 가진 WAS를 띄운다.
localhost:8080/redisSession 주소를 입력하여 로그인 폼을 띄우고 로그인을 수행한다.


 로그인이 완료되었으면 localhost:8080/redisSession/session 페이지로 이동하여 현재 로그인 한 세션 정보를 확인한다.


세션 정보를 확인한 뒤 8080 포트의 WAS를 내린 후 8888 포트를 가진 WAS를 실행한다.
localhost:8888/redisSession/session로 주소를 변경하고 이동한다.



 웹 개발에 익숙한 개발자라면 서로 다른 2개의 WAS에서는 세션정보가 공유되지 않는 사실을 알고 있을 것이다.

당연히 새로 로드된 WAS에는 사용자 정보가 세션이 존재하지 않기 때문에 다시 로그인 폼이 떠야한다.  어떻게 된 것 일까? 확인해보자.

 Redis 서버가 설치된 리눅스로 접속하여 Redis cli를 실행하자.
그리고 Keys 명령어를 이용해서 현재 저장된 Key 정보를 확인해 보자.

처음 발급된 session ID를 키로 가지고 있는 session 데이터가 저장되어 있는 것을 확인할 수 있다.



원리는 간단하다. 

Web.xml에서 Filter로 선언 된 DelegatingFilterProxy를 통해서 WAS 쪽에서 Session 정보를 가져올 때 먼저 Redis 서버에 세션 정보가 들어 있는 지 확인하는 것이다.

 앞서 설명한 Cache가 동작하는 방식을 생각하면 이해가 편할 것이다.

기존의 세션 컨트롤과 동일하게 세션에 정보가 요청될 때마다 세션 Expire 시간이 초기화되며 Expire 대기 시간도 별도로 설정이 가능하다.

 여러 개의 WAS를 띄워놓고 세션 정보를 redis를 통해 공유할 수 있기 때문에 HAproxy와 같은 로드 밸런서를 이용한다면 클라우드 환경과 비슷하게 운용할 수 있다.

소스코드는 아래의 주소에서 확인할 수 있다.
https://github.com/wargen99/wargen-repo

댓글 없음:

댓글 쓰기