[PHP] PHP와 JSP 간의 Session 공유(WDDX)


Development note/PHP  2019. 11. 8. 09:00

안녕하세요. 명월입니다.


이 글은 PHP와 JSP 간의 Session 공유(WDDX)에 대한 글입니다.


이전에 제가 Apache + PHP + tomcat + JSP로 해서 웹을 운영하는 방법에 대해 소개한 적이 있습니다.

링크 - [PHP] Apache 환경의 같은 호스트 안에서 PHP와 Java(Servlet)를 동시에 기동, 운영하는 방법


PHP와 JSP를 같은 호스트에서 사용할 수 있으나 가장 큰 문제가 session 공유입니다. PHP에서 세션을 넣어서 JSP측에서 세션을 가져오려다 보면 값이 없습니다.

서로 PHP와 JSP와의 세션 체계도 다르기 때문입니다. 그럼 값을 get이나 cookie로 넘겨야 하는데 그럼 보안상의 문제가 생길 수 있습니다.


PHP에서의 세션 기능 중에 세션을 파일로 관리할 수 있는 기능이 있습니다. 그럼 JSP에서 PHP의 session 파일 구조를 읽어서 사용하고 다시 JSP에서 PHP 파일 구조로 session을 저장하게 되면 session을 공유할 수 있지 않을까 하는 생각에서 작성해봤습니다.

참고 - https://www.php.net/manual/en/function.session-save-path.php

참고 - https://www.php.net/manual/en/session.configuration.php#ini.session.serialize-handler

참고 - https://www.php.net/manual/en/book.wddx.php

<?php
  // 세션 직렬화 타입(이 설정을 안하면 php 직렬화 타입으로 저장이 되기 때문에 WDDX 타입으로 저장합니다.
  ini_set('session.serialize_handler','WDDX');
  // 세션을 파일로 저장하는 경로
  ini_set('session.save_path', 'd:/file/session');
  class test{
    private $a = "ee";
    private $b = 1;
  }
  $aa = [];
  $aa["a"] = 1;
  $aa["b"] = "ss";
  session_start();
  //TEST는 hello 값을
  $_SESSION["TEST"] = "hello";
  //TEST1는 string
  $_SESSION["TEST1"] = "test";
  //TEST2는 class
  $_SESSION["TEST2"] = new test;
  //TEST2는 number
  $_SESSION["TEST3"] = 11.1;
  //TEST2는 array
  $_SESSION["TEST4"] = [1,2,3];
  //TEST2는 map
  $_SESSION["TEST5"] = $aa;
?>
<!DOCTYPE html>
<html>

<head>
  <title>title</title>
  <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
</head>

<body>
  <?=$_SESSION["JAVA"]?>
</body>
</html>

먼저 session을 파일로 저장하는 경로를 설정하고 위와 같이 작성하고 페이지를 요청했습니다.

아직 52라인에 JAVA의 이름으로 세션을 저장한 값이 없기 때문에 경고 메시지가 나옵니다. 그건 나중에 다시 확인하도록 하고 위에서 TEST부터 TEST5까지 저장한 session의 파일을 구조를 확인하겠습니다.

(참고로 PHP의 WDDX에 관한 문서는 아무리 구글링해도 나오지를 않네요. 그래서 제가 노가다로 분석했습니다.)

위와 같이 세션 파일이 하나 생겼습니다. 세션 파일명은 sess_세션키의 형태로 저장됩니다.

세션 키는 위의 cookie값을 보면 확인할 수 있습니다.

세션 파일을 열어보면 아래와 같은 모습으로 보입니다.

위의 형태로는 보기가 힘드니 이쁘게 전개를 하면 아래와 같습니다.

<wddxPacket version='1.0'>
  <header/>
  <data>
    <struct>
      <var name='TEST'>
        <string>hello</string>
      </var>
      <var name='TEST1'>
        <string>test</string>
      </var>
      <var name='TEST2'>
        <struct>
          <var name='php_class_name'>
            <string>test</string>
          </var>
          <var name='a'>
            <string>ee</string>
          </var>
          <var name='b'>
            <number>1</number>
          </var>
        </struct>
      </var>
      <var name='TEST3'>
        <number>11.1</number>
      </var>
      <var name='TEST4'>
        <array length='3'>
          <number>1</number>
          <number>2</number>
          <number>3</number>
        </array>
      </var>
      <var name='TEST5'>
        <struct>
          <var name='a'>
            <number>1</number>
          </var>
          <var name='b'>
            <string>ss</string>
          </var>
        </struct>
      </var>
    </struct>
  </data>
</wddxPacket>

구조는 xml 구조이고 data 노드의 struct 노드 안에 var 태그로 세션이 구분되어 저장되어 있습니다.

TEST부터 TEST5까지 모습이 보입니다.


이걸 제가 JSP 서블릿에서 Jsoup를 이용해서 읽고 쓰는 클래스를 만들었습니다.

링크 - [Java] Jsoup를 이용해서 XML파일(HTML)을 다루는 방법

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/*")
public class Index extends HttpServlet {
  private static final long serialVersionUID = 1L;

  public Index() {

  }

  private String getSessionPath(HttpServletRequest request) {
    // PHP의 세션 키값을 이용해서 session 파일의 경로를 작성한다.
    Cookie[] cookies = request.getCookies();
    String sessionpath = "d:\\file\\session\\sess_";
    for (Cookie cookie : cookies) {
      if (cookie.getName().equals("PHPSESSID")) {
        return sessionpath + cookie.getValue();
      }
    }
    return null;
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // PHP의 세션 파일을 찾는다.
    String sessionpath = getSessionPath(request);
    if (sessionpath != null) {
      // Session 클래스를 생성한다.
      PHPSession session = new PHPSession(sessionpath);
      //TEST의 세션을 가져오면 화면에는 hello의 화면이 표시된다.
      response.getWriter().append(session.getAttribute("TEST").toString());
      // PHP세션에 JAVA 키로 OK!! got it!!이라는 문구를 넣는다.
      session.setAttribute("JAVA", "OK!! got it!!");
    }
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    doGet(request, response);
  }
}
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class PHPSession {
  private File file;
  // 클래스가 선언될 때는 PHPSession의 파일 경로를 넣는다.
  public PHPSession(String path) throws FileNotFoundException {
    this.file = new File(path);
    if (!this.file.exists()) {
      throw new FileNotFoundException();
    }
  }
  // PHP Session안의 키를 통해 값을 가져온다.
  public Object getAttribute(String name) throws IOException {
    // Jsoup 라이브러리를 이용해서 xml를 파싱하여 가져온다.
    Document document = Jsoup.parse(this.file, "UTF-8");
    // 가장 큰 경로는 data의 struct의 var 태그에서 name이 key된 엘리먼트를 가져온다.
    Element element = document.selectFirst("data > struct > var[name=" + name + "]");
    // 없으면 session이 선언되지 않은 것이므로 null를 리턴한다.
    if (element == null) {
      return null;
    }
    // 값을 리턴한다.
    return getValue(element);
  }
  // PHPSession에서 Key리스트를 출력한다.
  public List<String> getAttrubuteList() throws IOException {
    Document document = Jsoup.parse(this.file, "UTF-8");
    Elements elements = document.select("data > struct > var");
    List<String> ret = new ArrayList<>();
    for (int i = 0; i < elements.size(); i++) {
      ret.add(elements.get(i).attr("name"));
    }
    return ret;
  }
  // PHPSession에서 세션을 추가하여 PHP에서도 세션이 공유되도록 한다.
  public void setAttribute(String name, String value) throws IOException {
    Document document = Jsoup.parse(this.file, "UTF-8");
    Element element = document.selectFirst("data > struct > var[name=" + name + "]");
    if (element != null) {
      element.remove();
    }
    element = document.selectFirst("data > struct");
    element = element.appendElement("var");
    element.attr("name", name);
    element = element.appendElement("string");
    element.appendText(value);
    try (FileOutputStream stream = new FileOutputStream(this.file)) {
      // 불필요한 공백제거
      document.outputSettings().prettyPrint(false);
      String output = document.body().child(0).outerHtml();
      // 세션을 작성한다.
      stream.write(output.getBytes());

    }
  }
  // 이게 여기서 제일 중요한 재귀 함수일 듯 싶습니다. 값을 가져오는 함수
  private Object getValue(Element element) {
    Elements childs = element.children();
    // 자식 노드가 하나 이상이면 에러이기 때문에 null를 리턴한다.
    if (childs.size() != 1) {
      return null;
    }
    Element child = childs.get(0);
    // 태그가 string이나 number이면 그대로 리턴한다.
    if ("string".equals(child.tagName()) || "number".equals(child.tagName())) {
      return child.text();
    }
    // array일 경우 string이나 number의 값이 아닌 array안에 array나 클래스나 맵이 들어갈 수 있기 때문에 최종 string이 나올때까지 재귀를 통해 작성한다.
    // 즉 array[0]가 array일 경우 list의 list를 작성하는 것입니다.
    if ("array".equals(child.tagName())) {
      List<Object> ret = new ArrayList<>();
      ret.add(getValue(child));
      return ret;
    }
    // struct는 클래스나 map의 형태일 경우인데 위 array와 같은 로직으로 string이나 number가 나올 때까지 재귀를 한다.
    if ("struct".equals(child.tagName())) {
      Map<String, Object> ret = new HashMap<>();
      Elements vars = child.children();
      for (int j = 0; j < vars.size(); j++) {
        ret.put(vars.get(0).attr("name"), vars.get(0));
      }
      return ret;
    }
    return null;
  }
}

자바의 virtual 디렉토리를 요청하게 되면 PHP에서 설정한 TEST의 세션 값이 출력이 되었습니다. 이렇게 되면 PHP에서 설정한 세션 값이 java에서도 나오게 되었습니다. 이번엔 java에서 설정한 세션값이 PHP에서도 제대로 나오면 PHP와 Java간의 세션 공유는 완성이네요.

PHP와 Java간의 세션 공유는 확인이 되었습니다. 사실 제가 만든 소스는 완벽한 것은 아닙니다. 제대로 만들려면 시간이 많이 걸릴 듯싶습니다.

먼저 class형식은 PHP측에서의 class는 일반 맵 형식입니다만 이걸 java에서 class로 만들기 위해서는 조금 복잡하겠네요. List나 Map형식도 마찬가지입니다. 단순하게 String이나 int같은 단일 자료형 타입은 주고 받기가 쉬운데 Class나 List같은 Object 타입은 조금 어렵네요.


관련된 자료나 정보가 너무 없어서 만드는데 너무 헤맸습니다. 좀 시간을 두고 정리해서 오픈 소스에다 등록해야 겠네요..

참조 - CombinePHP.zip

참조 - index.zip


여기까지 PHP와 JSP 간의 Session 공유(WDDX)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.