스프링 세션의 구현
지난 포스팅에서 세션에 대해서 몽실이가 아마존에서 아이폰12를 구입하는 과정을 예로들어서 설명을 했다. 세션이라는 기술이 존재하면, 어떤 편리함이 있는지 글로써 설명을 했다. 이번 포스팅에서는 글로 예시로든 내용을 코드로 구현해보자. 먼저 세션을 이용해서 2가지 문제를 해결하고자 한다. 첫번째로 로그인을 했는데, 창을닫고 다시 들어오면 또 다시 로그인을 해야되는 문제를 해결한다. 즉, 세션을 이용해서 자동로그인을 구현한다. 두번째는 쇼핑하다가 창이 닫혔는데, 방금 보았던 상품이 기억이 안나는 문제를 해결한다. 창을 닫고 들어와도 내가 클릭한 상품을 세션을 이용해서 띄워줄 수 있도록 해보자.
1.프로젝트 생성
스프링 프로젝트로 진행을 한다. 먼저 sts 또는 이클립스를 열고, 프로젝트 생성을 해준다. 프로젝트는 SPRING MVC PROJECT를 선택해 주면된다.
2.pom.xml 셋팅
pom.xml에서 java-version을 1.8로 변경해준다. 그리고 아래쪽에 maven-compiler-plugin 버전도 sorce , target 각각 1.8로 설정해 준다. 그리고 화면과 서버가 통신할때 데이터를 json 타입으로 전달하고 Java 객체로 받고 처리하기 위한 라이브러리도 넣어준다.
<!-- json parser -->
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
<!-- jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.4.2</version>
</dependency>
3.화면 생성
views 폴더안에 메인화면(home.jsp) , 가입화면(joinPage.jsp) , 로그인화면(loginPage.jsp) 을 각각 아래 코드처럼 생성을 해준다.
home.jsp
메인화면에는 로그인화면, 가입화면으로 이동할 수 있게 만들어져있다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html; charset=UTF-8" language="java"%>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>메인화면</h1>
<br>
<a href="/loginPage">login</a>
<a href="/joinPage">join</a>
<a href="/">home</a>
<a href="/logout">logout</a>
</body>
</html>
joinPage.jsp
가입화면에는 form 태그로 유저이름, 비밀번호, 이메일을 입력받아서 서버로 전달한다. action 주소와 method를 체크하자.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>joinPage</title>
</head>
<body>
<h1>join</h1>
<hr/>
<form action="/joinProc" method="post">
<input type="text" name="username" placeholder="Username"/> <br/>
<input type="password" name="password" placeholder="Password"/> <br/>
<input type="email" name="email" placeholder="Email"/> <br/>
<button>join</button>
</form>
</body>
</html>
loginPage.jsp
로그인 페이지에는 이름과 비밀번호를 입력해서 서버로 전달한 뒤 로그인 처리를 한다. 회원가입을 안했으면, 가입페이지로 이동해서 회원가입을 할 수 있다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>login</h1>
<hr/>
<!-- 참고로 시큐리티는 x-www-form-url-encoded 타입만 인식 -->
<form action="/loginProc" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
<button>login</button>
</form>
<a href="/joinPage">회원가입을 아직 안하셨나요?</a>
</body>
</html>
4.VO 생성
화면에서 던져줄 데이터를 받을 UserVO를 생성해보자. 위에서 부터 아이디, 유저명, 비밀번호, 이메일, 권한이다.
public class User {
private String id;
private String username;
private String password;
private String email;
private String role;
//셋터 겟터 생략
}
5.Repository 생성
회원 정보를 저장할 UserRepository를 생성한다. 원래는 DB에 저장을 해야한다. 이번 포스팅은 세션구현이 중심이라 DB연동은 하지 않는다. 각각의 회원정보는 MAP에 담아 List에 저장하도록 한다. 그리고 UserRepository 클래스 생성자를 만들어서 이 클래스가 생성될때, 몇몇의 회원 정보를 생성해서, 가입시켜 놓는다. 그리고 아래 saveUser는 컨트롤러에서 받은 Map 타입의 유저 정보를 유저리스트에 저장하는 메소드다.
package com.session.my;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
//유저 저장소
private final List<Map<String, String>> userList = new ArrayList<>();
public UserRepository() {
System.out.println("UserRepository 초기화");
Map<String, String> userInfoOne = new HashMap<String, String>();
userInfoOne.put("username", "kim");
userInfoOne.put("password", "1111");
userInfoOne.put("email", "kim@naver.com");
userInfoOne.put("role", "ADMIN");
Map<String, String> userInfoTwo = new HashMap<String, String>();
userInfoTwo.put("username", "hong");
userInfoTwo.put("password", "2222");
userInfoTwo.put("email", "hong@naver.com");
userInfoTwo.put("role", "ADMIN");
//배열에 저장.
userList.add(userInfoOne);
userList.add(userInfoTwo);
System.out.println("UserRepository userList 개수 : " + userList.size());
}
//리스트에 회원 저장
public void saveUser(Map<String, String> userInfoMap) throws Exception{
userList.add(userInfoMap);
System.out.println("UserRepository userList 개수 : " + userList.size());
}
}
6.Controller 생성
먼저 메인으로 들어왔을때, 호출되는 컨트롤러를 작성한다. 간단하다. / 경로로 들어왔을 때, viewResolver를 통해서 위에서 생성한 home.jsp로 이동시켜준다.
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpServletRequest request, Model model) {
return "home";
}
}
그다음 사용자가 가입을 했을 때, 호출되는 JoinController를 생성한다. 제일 위에 유저정보를 저장하는 역할을 하는 UserRepository가 있다. 그 아래 가입페이지로 이동하는 메소드가 있다. 메소드 안에는 서버가 생성한 sessionId를 출력해 놓았다. 그리고 마지막으로 가입처리 하는 메소드다. 이 부분이 중요하다. 화면에서 사용자의 데이터를 위에서 만든 User객체로 받고, 하나씩 뽑은 다음 Map 타입으로 만들어서 UserRepository의 배열에 저장을 담당하는 메소드에 넣어서 호출해 준다.그리고 유저의 이름을 세션객체에 넣는다. 그리고 home화면으로 이동시켜 준다. 참고로 화면에서 스프링 컨트롤러로 데이터를 던질때 사용되는 어노테이션은 이곳을 참고했다
package com.session.my;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class JoinController {
//유저 저장 리파지토리
@Autowired
private UserRepository userRepository;
//가입페이지 이동
@RequestMapping(value = "/joinPage", method = RequestMethod.GET)
public String joinPage(HttpSession session) throws Exception{
System.out.println("joinPage - 진입");
System.out.println("session id : " + session.getId());
return "joinPage";
}
//가입처리
@RequestMapping(value = "/joinProc", method = RequestMethod.POST)
//@ModelAttribute 가 Uesr 앞에 암묵적으로 붙어있음.
//@RequestBody 는 클라이언트에서 content-type 을 application/json으로 보낼때 사용.
//@ModelAttribute 는 클라이언트에서 content-type 을 application/x-www-form-urlencoded 일때 사용.
//https://blog.naver.com/writer0713/221853596497
public String joinProc(User user, HttpServletRequest request, HttpServletResponse response) throws Exception{
System.out.println("joinProc - 진입");
//세션
HttpSession session = request.getSession();
//가입자 정보
String sessionId = session.getId();
String username = user.getUsername();
String password = user.getPassword();
String email = user.getEmail();
//회원정보 저장
Map<String, String> userInfoMap = new HashMap<String, String>();
userInfoMap.put("id", sessionId);
userInfoMap.put("username", username);
userInfoMap.put("password", password);
userInfoMap.put("email", email);
userInfoMap.put("role", "USER");
//db 에 저장
userRepository.saveUser(userInfoMap);
//스프링 서버에서 세션을 생성한다.
//스프링에 세션객체가 존재한다.
//서버에서 생성한 세션은 서버에서 삭제해줘야한다.
//웹브라우저에서 캐쉬 삭제해도 남는다.
session.setAttribute("userSession", username);
return "home";
}
}
7.서버 실행
서버를 실행하면 레파지토리에서 만들어놓은 유저 객체들이 배열에 들어가고, 그 결과 값을 로그로 출력이 될 것이다.
UserRepository 초기화
UserRepository userList 개수 : 2
8.웹브라우저 실행
localhost:8080으로 웹브라우저에 접속해보자. 메인화면은 아래와 같이 출력된다. 그리고 오른쪽에 JSESSIONID를 보자. 이 웹브라우저에 있는 쿠키의 D112E.. 로 시작되는 세션 id는 톰켓이 생성해서 웹브라우저로 넣어준것이다. 우리는 이 session id를 사용자 id로 사용을 할 것이다.
가입화면으로 이동해서 데이터를 입력한다. 데이터를 입력하고 join 버튼을 누르자.
로그를 확인해보니 저장이 잘되었다. 서버로 넘어간 session id를 보면 웹브라우저의 쿠키 저장소에 있던 세션id 값과 동일하다. 일단 여기서는 이 값을 user의 id로 지정해 주었다.
joinPage - 진입
session id : D112E299897B8086C0F9C62B30E2C948
joinProc - 진입
id : D112E299897B8086C0F9C62B30E2C948
username : jin
password : 1111
email : jin@naver.com
UserRepository userList 개수 : 3
가입이 완료되고 메인화면으로 넘어 오는데 조금전과 달라진 것이 없다! 가입을 했는데, 가입표시도 없고, 가입하고 바로 로그인도 안되고.. 이상하다. 뭔가 잘못된것 같다. 다행히도 우리는 서버에서 세션을 만들어 놓았다. 만약 이상황에서 세션이라는 녀석이 없다면 DB에 유저 정보를 저장하고 끝나버리고(?) 만다. 세션을 이용해보자. 스프링에서 컨트롤러에서 만든 세션을 JSP 화면에서도 가져와서 사용할 수 있다.
세션을 사용하기 위해서 home.jsp 화면을 조금 수정해 보자. request 객체를 이용해서 서버에서 만든 session을 가지고 왔다. 그리고 세션이 존재하면 홍길동님 반갑습니다. 인사말과 함께 로그아웃 버튼을 보여주고, 세션이 없으면 가입 또는 로그인을 하지 않은 것이라 판단하고 가입 버튼과 로그인 버튼을 보여주도록 처리하자.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html; charset=UTF-8" language="java"%>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>메인화면</h1>
<%
session = request.getSession();
if(session.getAttribute("userSession") != null){
out.print("<h3>" + session.getAttribute("userSession")+ "님 반갑습니다." + "</h3>" + "<br>");
out.print("<a href='/logout'>logout</a>" +"<br>");
}else{
out.print("<a href='/loginPage'>login</a>" +"<br>");
out.print("<a href='/joinPage'>join</a>" +"<br>");
}
%>
</body>
</html>
다시 웹브라우저를 실행해보자. 웹브라우저에서 내가 가입한 유저의 닉네임을 보여주고 있다. 뭔가 로그인 상태인것 같다. 한가지 더 시험해 볼일이 남았다. 이번에는 브라우저를 종료시켜보자. 그리고 다시 localhost:8080을 입력하자. 웹브라우저는 조금전과 동일하게 아래와 같은 로그인되어 있는 상태의 페이지를 보여 줄것이다.
그렇다면 세션은 언제 지워질까? 앞의 포스팅(세션의 개념과 원리 - 몽실이의 아이폰12 구입하기)에서도 설명했듯이 기본 디폴트 시간은 30분이다. 그리고 서버에서 세션을 삭제를 해주면 세션은 사라진다. 그럼 마지막으로 LoginController를 만들어서 logout 버튼을 클릭했을때 세션을 삭제해주는 처리를 하자. 아래 로직을 작성하고 로그인상태의 화면에서 logout 버튼을 누르면 홍길동님 반갑습니다. 인사말이 없어지고 아래 login, join 버튼이 생성 된 것을 볼 수 있다.
@Controller
public class LoginController {
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpServletRequest request) {
System.out.println("logout - 진입");
//세션 끊기
HttpSession session = request.getSession();
session.invalidate();
return "redirect:/";
}
}
마무리
앞의 포스팅의 상황을 코드로 구현해 보았다. 몽실이가 아이폰12를 구입하기 위해서 아마존 웹사이트를 둘러보다가 창이 닫혀져 버려서 다시 로그인해야되는 상황을 해결하기 위한 코드를 작성해보았다. 세션을 이용해서 창이 닫혀도 다시 같은 주소로 들어갔을때 로그인 상태로 쇼핑을 할 수 있도록 구현해 보았다. 이 포스팅은 단순히 세션의 개념을 그려보기위한 포스팅이라서 실제 업무에서 위의 코드가 그대로 사용되기에는 무리가 있다. 다음 포스팅에서는 세션으로 방금 클릭한 상품을 띄워주는 코드를 구현해 보자.
'웹개발 > 보안' 카테고리의 다른 글
카카오 로그인 OAuth2.0 (0) | 2020.12.08 |
---|---|
OAuth2 인증 - 인가 코드 그랜트 (0) | 2020.12.05 |
OAuth2 인증 - 암시적 코드 그랜트 (0) | 2020.12.05 |
스프링 Session 쇼핑몰 방금 본 상품&권한체크 (0) | 2020.12.05 |
세션의 개념과 단점 (0) | 2020.12.01 |