요구사항 | 접근하고 있는 사용자가 "로그인" 상태일 경우(Cookie 값이 logined=true) http://localhost:8080/user/list로 접근했을 때 사용자 목록을 출력한다. 만약 로그인하지 않은 상태라면 로그인 페이지(login.html)로 이동한다. |
이 문제는 요구사항 5에서 구현한 Cookie 헤더 값을 활용해 현재 요청을 보내고 있는 클라이언트가 로그인을 한 상태인지의 유무를 판단하는 것이다. 실제 웹 애플리케이션을 서비스할 때도 로그인한 사용자만 접근을 허용해야 한다는 요구사항은 자주 발생한다.
현재 로그인 상태 유무에 따라 사용자 목록 페이지를 출력하는 기능은 다음과 같이 구현할 수 있다.
- RequestHandler.java
package webserver;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.File;
import java.nio.file.Files;
import java.net.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RequestHandler extends Thread {
private static final Logger log = LoggerFactory.getLogger(RequestHandler.class);
private Socket connection;
public RequestHandler(Socket connectionSocket) {
this.connection = connectionSocket;
}
public void run() {
log.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(), connection.getPort());
try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) {
// TODO 사용자 요청에 대한 처리는 이 곳에 구현하면 된다.
BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String line = br.readLine();
log.debug("request line : {}", line);
if (line == null) {
return;
}
String[] tokens = line.split(" ");
boolean logined = false;
// int contentLength = 0;
while (!line.equals("")) {
log.debug("header : {}", line);
line = br.readLine();
if (line.contains("Cookie")) {
// contentLength = getContentLength(line);
logined = isLogin(line);
}
}
String url = tokens[1];
// POST
if ("/user/create".equals(url)) {
String body = IOUtils.readData(br, contentLength);
Map<String, Object> params = HttpRequestUtils.parseQueryString(body);
User user = new User(params.get("userId"), params.get("password"), params.get("name"), params.get("email"));
DataBase.addUser(user);
} else if ("/user/login".equals(url)) {
String body = IOUtils.readData(br, contentLength);
Map<String, String> params = HttpRequestUtils.parseQueryString(body);
User user = DataBase.findUserById(params.get("userId"));
if (user == null) {
responseResource(out, "/user/login_failed.html");
return;
}
if (user.getPassword().equals(params.get("password"))) {
DataOutputStream dos = new DataOutputStream(out);
response302LoginSuccessHeader(dos);
} else {
responseResource(out, "/user/login_failed.html");
}
} else if ("/user/list".equals(url)) {
if (!logined) {
responseResource(out, "/user/login.html");
return;
}
Collection<User> users = DataBase.findAll();
StringBuilder sb = new StringBuilder();
sb.append("<table border='1'>");
for (User user : users) {
sb.append("<tr>");
sb.append("<td>" + user.getUserId() + "</td>");
sb.append("<td>" + user.getName() + "</td>");
sb.append("<td>" + user.getEmail() + "</td>");
sb.append("</tr>");
}
sb.append("</table>");
byte[] body = sb.toString().getBytes();
DataOutputStream dos = new DataOutputStream(out);
response200Header(dos, body.length);
responseBody(dos, body);
} else {
responseResource(out, url);
}
} catch (IOException e) {
log.error(e.getMessage());
}
}
private boolean isLogin(String line) {
String[] headerTokens = line.split(":");
Map<String, String> cookies = HttpRequestUtils.parseCookies(headerTokens[1].trim());
String value = cookies.get("logined");
if (value == null) {
return false;
}
return Boolean.parseBoolean(value);
}
private void responseResource(OutputStream out, String url) throws IOException {
DataOutputStream dos = new DataOutputStream(out);
byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath());
response200Header(dos, body.length);
responseBody(dos, body);
}
private void response302LoginSuccessHeader(DataOutputStream dos) {
try {
dos.writeBytes("HTTP/1.1 302 Redirect \r\n");
dos.writeBytes("Set-Cookie: logined=true \r\n");
dos.writeBytes("Location: /index.html \r\n");
dos.writeBytes("\r\n");
} catch (IOException e) {
log.error(e.getMessage());
}
}
private int getContentLength(String line) {
String[] headerTokens = line.split(":");
return Integer.parseInt(headerTokens[1].trim());
}
private void response302Header(DataOutputStream dos, String url) {
try {
dos.writeBytes("HTTP/1.1 302 Redirect \r\n");
dos.writeBytes("Location: " + url + " \r\n");
dos.writeBytes("\r\n");
} catch {
log.error(e.getMessage());
}
}
private void response200Header(DataOutputStream dos, int lengthOfBodyContent) {
try {
dos.writeBytes("HTTP/1.1 200 OK \r\n");
dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n");
dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n");
dos.writeBytes("\r\n");
} catch (IOException e) {
log.error(e.getMessage());
}
}
private void responseBody(DataOutputStream dos, byte[] body) {
try {
dos.write(body, 0, body.length);
dos.flush();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
요구사항 5번과 요구사항 6번 구현 과정에서 쿠키 값을 추가(Set-Cookie 필드 사용)하고 클라이언트가 쿠키 값을 전달(Cookie 필드 사용)하는 전체 과정을 그림으로 살펴보면 다음과 같다.
HTTP 웹 서버
/user/login ┌────────────────────────────────────┐
───────────────────────────→ · 로그인 성공 │
│ · Set-Cookie 헤더에 값 추가 │
웹 ←─────────────────────────── │
브 HTTP 응답 + Set-Cookie └────────────────────────────────────┘
라
우 /user/list + Cookie ┌────────────────────────────────────┐
저 ───────────────────────────→ · Cookie 필드 값을 통해 │
│ 로그인 유무 판단 │
←─────────────────────────── │
사용자 목록 응답 └────────────────────────────────────┘
HTTP는 기본적으로 무상태 프로토콜이라 각 요청 간에 상태 데이터를 공유하지 못 한다. 이 같은 한계를 극복하고 로그인 상태 유무와 같이 각 요청 간에 상태 정보를 공유하기 위한 방법으로 쿠키를 사용한다. HTTP에서 각 요청 간의 상태를 공유할 수 있는 유일한 방법이다. 서버가 전달하는 쿠키 정보는 클라이언트에 저장해 관리하기 때문에 보안 이슈가 있다. 이 같은 단점을 보완하기 위해 세션이 등장했다. 세션 또한 쿠키를 기반으로 하는 것은 같지만, 좀 더 보안을 강화하기 위한 방법으로 상태 데이터를 서버에 저장한다는 것만 다르다. 세션과 관련해서는 6장에서 세션을 직접 구현해 보면서 더 자세히 다루도록 한다.
참고도서 : https://roadbook.co.kr/169
[신간안내] 자바 웹 프로그래밍 Next Step
● 저자: 박재성 ● 페이지: 480 ● 판형: 사륙배변형(172*225) ● 도수: 1도 ● 정가: 30,000원 ● 발행일: 2016년 9월 19일 ● ISBN: 978-89-97924-24-0 93000 [강컴] [교보] [반디] [알라딘] [예스24] [인터파크] [샘
roadbook.co.kr
'교재 실습 > 자바 웹 프로그래밍 Next Step' 카테고리의 다른 글
4.3 추가 학습 자료 (0) | 2025.03.13 |
---|---|
4.2.1.7 요구사항 7 - CSS 지원하기 (6) | 2025.03.12 |
4.2.1.5 요구사항 5 - 로그인하기 (0) | 2025.02.25 |
4.2.1.4 요구사항 4 - 302 status code 적용 (2) | 2025.02.21 |
4.2.1.3 요구사항 3 - POST 방식으로 회원가입 하기 (2) | 2025.02.20 |
댓글