bdfgdfg
서블릿에서의 쿠키와 세션 본문
HTTP 프로토콜을 사용하는 웹 환경은 기본적으로 stateless 환경.
어느 한 웹페이지에서 로그인요청을하면 다른 웹페이지에서는 로그인 한 정보를 알 수 없기때문에 이를 공유할 수 있어야한다.
대표적인 방법
쿠키 : 클라이언트 PC의 Cookie파일에 정보를 저장한 후 웹페이지들이 공유
세션 : 서버 메모리에 정보를 저장한 후 웹페이지들이 공유
일일이 로그인 상태를 확인하기위해 로그인 정보를 다른 웹페이지에 전송하는 방식도 있지만, 비효율적.
쿠키를 이용한 웹 페이지 연동
쿠키란 클라이언트 PC의 Cookie파일에 정보를 저장한 후 웹페이지들이 공유하는 것이라고 했다.
- 정보가 클라이언트 PC에 저장됨
- 저장 정보 용량에 제한이 있음
- 보안에 취약
- 클라이언트 브라우저에서 사용 유무를 설정할 수 있음
- 웹 사이트당 하나의 쿠키가 만들어짐
쿠키는 크게 2가지 종류로 나눌 수 있다.
쿠키의 생성과정은 다음과 같다.
- 브라우저로 사이트에 접속하면 서버는 정보를 저장한 쿠키를 생성하고 클라이언트에 응답.
쿠키는 javax.servlet.http.Cookie를 이용하며 addCookie메소드를 이용해 클라이언트에 쿠키를 전송한다
또한 내부 메소드로 setMaxAge메소드에 인자값의 종류를 지정해서 Persistence쿠키와 Session쿠키를 구별해서 만들 수 있음.
-> setMaxAge메소드를 이용하지않거나 음수를 전달하면 session쿠키. 양수값을 지정하면 Persistence쿠키
@WebServlet("/setCookie")
public class setCookieValue extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
Date d = new Date();
// CookieTest라는 이름으로 프로그래밍이라는 정보를 한글로 인코딩해 쿠키에 저장.
Cookie c = new Cookie("cookieTest",URLEncoder.encode("프로그래밍","utf-8"));
c.setMaxAge(24 * 60 * 60 ); // persistence 쿠키.
response.addCookie(c);
System.out.println("현재시간 : " + d);
}
}
@WebServlet("/getCookie")
public class GetCookieValue extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
Cookie[] allValues = request.getCookies();
for(Cookie cookie : allValues)
{
if(cookie.getName().equals("cookieTest")) {
out.println("<h2>쿠키 값 가져오기! : " + URLDecoder.decode(cookie.getValue(),"utf-8"));
}
}
}
}
실행해보면
위 예제는 Persistence쿠키이며, 세션쿠키는 setMaxAge를 음수로 지정해서 세션 쿠키로 만들어주자.
세션을 이용한 웹 페이지 연동
쿠키와 개념은 같지만, 차이는 쿠키는 클라이언트 PC에 저장된다는점과 세션은 서버 메모리에 저장을 한다는점.
- 쿠키보다 보안에 유리하지만 서버 메모리에 저장되므로 부하를 줄 수 있음
- 브라우저당 하나의 세션ID가 발급되며 유효시간을 가짐
- 주로 로그인 상태 유지 기능이나 쇼핑몰의 장바구니 담기 기능등에 사용된다.
세션 실행과정
- 서블릿이 세션 객체를 생성한 후 세션객체에 대한 세션ID를 클라이언트에 응답.
--> 서버로부터 전송된 세션ID도 쿠키이다.
- 재접속할 때 세션 쿠키에 저장된 세션ID를 서버에 전송하면 서버에는 해당 ID를 통해 세션 객체에 접근.
- 즉 서버는 request객체를 통해서 클라우저의 sessionID를 알 수 있다.
서블릿에서 세션을 이용할려면 HttpSession 객체를 생성해서 사용한다.
@WebServlet("/sess")
public class sessionTest extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
// 최초 요청시 세션 객체를 새로생성. 기존에 있었다면 반환.
HttpSession session = request.getSession();
if(session.isNew())
{
System.out.println("새로운 세션 생성");
}
out.println(" 세션 아이디 : " + session.getId() + "<br>");
out.println(" 최초 세션 생성 시각 : " + new Date(session.getLastAccessedTime()) +"<br>");
out.println(" 세션 유효시간 : " + session.getMaxInactiveInterval() + "<br>");
}
}
세션 유효시간은 따로 설정하지 않으면 기본 30분.
이제 세션을 이용한 간단한 로그인을 구현해보자.
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>회원 가입 창</title>
</head>
<body>
<form name="frmLogin" method="post" action="login" enctype="UTF-8">
아이디 : <input type="text" name="user_id"><br>
비밀번호 : <input tpye="password" name="user_pwd"><br>
<input type="submit" value="로그인"/>
<input type="reset" value="초기화" />
</form>
</body>
</html>
로그인 서블릿
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doHandle(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doHandle(request,response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException
{
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
String user_id = request.getParameter("user_id");
String user_pwd = request.getParameter("user_pwd");
MemberVO vo = new MemberVO();
vo.id = user_id;
vo.pwd = user_pwd;
MemberDAO dao = new MemberDAO();
boolean result = dao.isExisted(vo);
// 로그인정보가 있다면
if(result)
{
HttpSession session = request.getSession();
session.setAttribute("IsLogon", true);
session.setAttribute("login.id", user_id);
session.setAttribute("login.pwd", user_pwd);
out.println("<html><body>");
out.println("안녕하세요 " + user_id + " 님!");
out.println("<a href='show'> 회원정보보기 </a>");
out.print("</body></html>");
}
else
{
out.println("<html><body>");
out.println("로그인 정보가 없음.");
out.println("<a href='memberForm.html'> 다시로그인 </a>");
out.print("</body></html>");
}
}
}
주의할점은 같은 웹브라우저에서 여러 아이디/비밀번호로 로그인시 같은 세션으로 취급이 된다는점.
show 서블릿
@WebServlet("/show")
public class ShowMember extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
String id = "", pwd ="";
Boolean isLogon = false;
HttpSession session = request.getSession(false);
if(session != null)
{
isLogon = (Boolean)session.getAttribute("isLogon");
if(isLogon)
{
id = (String)session.getAttribute("login.id");
pwd = (String)session.getAttribute("login.pwd");
out.print("<html><body>");
out.print("세션을 활용한 로그인 성공! id : " + id + " PWD : " + pwd);
out.print("</body></html>");
}
else
{
response.sendRedirect("memberForm.html");
}
}
else
{
response.sendRedirect("memberForm.html");
}
}
}
로그인 실패시
로그인 성공시
회원정보보기
이렇게 서블릿들의 스코프(접근 범위)를 정리해보면
스코프 종류 | 해당 서블릿 api | 속성의 스코프 |
애플리케이션 스코프 | ServletContext | 속성은 애플리케이션 전체서 접근가능 |
세션 스코프 | HttpSession | 속성은 브라우저(사용자)에서만 접근 |
리퀘스트 스코프 | HttpServletRequest | 속성은 해당 요청사이클ㅇ에서만 접근 dispatch등으로 다른 서블릿/jsp에 전달은 가능. |
서블릿의 여러가지 URL 패턴
URL이란 리소스의 위치(Locator)를 의미.
URL 패턴이란 실제 서블릿의 매핑 이름을 말한다. 클라이언트가 브라우저에 요청할 때 사용되며 반드시 /(슬래시)로 시작해야한다.
종류 1 : 정확히 이름까지 일치하는지
@WebServlet("/first/test")
public class LoginServlet extends HttpServlet {
종류 2 : 디렉터리 이름만 일치하는 URL 패턴
@WebServlet("/first/*")
public class LoginServlet extends HttpServlet {
종류 3 : 매핑 이름에 상관없이 확장자가 일치하면 실행.
@WebServlet("*.do")
public class LoginServlet extends HttpServlet {
Filter API
필터는 브라우저에서 서블릿에 요청하거나 응답할 때 미리요청과 응답과 관련해 여러가지 작업을 처리하는 기능.
-> 서블릿에서 반복적으로 처리하는 작업이 있을 때(공통작업) 미리 필터에서 처리.
항상 이때까지 작업을 할 때
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException
{
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
위와 같이 한글 인코딩 설정을 서블릿마다 상단에 추가해주어야 했음.
하지만 모든 서블릿에서 공통으로 처리하는 작업을 먼저 필터에서 추가해준다면 매번 서블릿에 추가하지않더라도 작업이 가능해진다.
필터의 종류
- 요청 필터 : 사용자 인증 및 권한 검사, 요청시 요청 관련 로그 작업,인코딩 가능
- 응답 필터 : 응답 결과에 대한 암호화 작업, 서비스 시간 츶겅
필터는 Filter 인터페이스를 구현한 사용자 정의 클래스가 필요하며, init,doFilter,destory의 메소드를 구현해야한다.
또한 해당 필터를 적용할 Pattern또한 정의해주어야함. 여기서는 모든 필터에 대해 수행하도록 /*로 구현한다.
@WebFilter("/*")
public class EncoderFilter extends HttpFilter implements Filter {
private ServletContext context;
/**
* @see HttpFilter#HttpFilter()
*/
public EncoderFilter() {
super();
// TODO Auto-generated constructor stub
}
public void init(FilterConfig fConfig) throws ServletException {
context = fConfig.getServletContext();
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 모든 요청에 대한 인코딩 설정
request.setCharacterEncoding("utf-8");
chain.doFilter(request, response);
}
}
응답에 의한 필터도 위에서 처리가 가능하다.
여러가지 서블릿 관련 Listener API
위와 같이 특정 이벤트가 발생했을 때 우리가 호출하는게 아닌, 컨테이너 내에서 서블릿과 관련된 Listener를 상속받은 클래스의 인스턴스의 메소드를 직접 호출해준다.
-> 만약 그러지않았다면, 멀티 쓰레드 환경의 톰캣 컨테이너에서 사용자가 실수로 경쟁상태를 방관하는 코드를 짠다면 대참사가 난다..
예로들어 HttpSessionBindingListener를 구현한 밑의 클래스를 보면
@WebListener
public class LoginImpl implements HttpSessionBindingListener {
private String user_id;
private String user_pwd;
private static int total_user = 0;
/**
* @see HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)
*/
public LoginImpl(String id, String pwd)
{
user_id = id;
user_pwd = pwd;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
++total_user;
}
/**
* @see HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)
*/
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
--total_user;
}
}
위 객체가 생성되고, 세션(HttpSession) 객체의 setAttribute() 메서드를 통해 바인딩되거나 removeAttribute() 메서드를 통해 언바인딩될 때에 대한 이벤트를 처리할 수 있도록 하는 리스너.
즉 밑과같이
String user_id = request.getParameter("user_id");
String user_pwd = request.getParameter("user_pwd");
// 리스너 객체 생성.
LoginImpl loginuser = new LoginImpl(user_id, user_pwd);
HttpSession session = request.getSession();
if(session.isNew())
{
// 세션에 리스너 객체 바인딩. setAttribute로 바인딩되었으므로 valueBound가 호출된다.
session.setAttribute("loginUser", loginuser);
}
반대로 session객체의 invalidate메소드를 호출하면 리스너 객체의 valueUnbound가 호출된다.
'웹프로그래밍 > JSP_Servlet' 카테고리의 다른 글
JSP - 2 (0) | 2023.08.11 |
---|---|
JSP (0) | 2023.08.11 |
서블릿(Servlet) - 2 (0) | 2023.08.07 |
서블릿(Servlet) 기초 (0) | 2023.08.07 |
웹 어플리케이션의 기본구조 및 학습 (0) | 2023.08.07 |