회원가입 구현

심야·2023년 9월 4일
0

웹 개발

목록 보기
43/46
post-custom-banner

모의 해킹에 사용할 게시판 회원가입 구현


signup.js

formdata를 생성한 뒤, fetch 함수로 signupprocess 컨트롤러에 전달한다.

const form = document.getElementById("signup-form");

form.addEventListener("submit", (event) => {
    event.preventDefault(); // 기본 제출 동작 막기

    const formData = new FormData(form);
    const username = formData.get("user_id");
    const name = formData.get("name");
    const password = formData.get("user_pw");
    const address = formData.get("address");

    fetch("/hackthebox/signupprocess", {
        method: "POST",
        headers: [["Content-Type", "application/json; charset=utf-8"]],
        body: JSON.stringify({
            user_id: username,
            name: name,
            user_pw: password,
            address: address,
        }),
    })
        .then((response) => {
            if (!response.ok) {
                throw new Error("Network response was not ok");
            }
            return response.json();
        })
        .then((result) => {
            if (result.signup === "success") {
                location.href = "/hackthebox/community";
            } else if (result.signup === "duplication") {
                alert("이미 가입한 아이디입니다.");
            }
        })
        .catch((error) => {
            console.error(error);
            alert("회원가입 중 오류가 발생했습니다. 다시 시도해주세요.");
        });
});

UserInsertController

회원가입에 필요한 파라미터 중에 입력 값이 없으면 에러를 발생한다. 데이터를 정상적으로 받아왔다면 userdao의 UserInsert 메소드로 회원가입을 처리한다.
아이디가 중복되면 JSON으로 "signup", "duplication"을 전달한다.

@WebServlet("/signupprocess")
public class UserInsertController extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String userId;
        String name;
        String userPwd;
        String address;
        JSONObject json;

        // JSON 파싱
        BufferedReader br = req.getReader();
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();

        json = new JSONObject(sb.toString());
        userId = json.getString("user_id");
        name = json.getString("name");
        userPwd = json.getString("user_pw");
        address = json.getString("address");

        // 입력 값 없을 경우 에러 발생
        try {
            if (!userId.isEmpty() && !name.isEmpty() && !userPwd.isEmpty() && !address.isEmpty()) {
                UserDTO uservo = new UserDTO();

                uservo.setUser_id(userId);
                uservo.setName(name);
                uservo.setPassword(userPwd);
                uservo.setAddress(address);

                UserDAO userdao = new UserDAO();
                int result = userdao.UserInsert(uservo);
                if (result == 1) {
                    // 회원가입 성공 -> 로그인 처리 컨트롤러에서 세션 생성 후 community.jsp 리다이렉트
                    req.setAttribute("vo", uservo);
                    req.getRequestDispatcher("loginprocess").forward(req, resp);
                } else if (result == 4) {
                    // 아이디 중복
                    json = new JSONObject();
                    json.put("signup", "duplication");
                    PrintWriter out = resp.getWriter();
                    resp.setContentType("application/json");
                    resp.setCharacterEncoding("UTF-8");
                    out.print(json.toString());
                    out.flush();
                } else {
                    throw new ServletException("Insert Error");
                }
            }
        } catch (NullPointerException e) {
            resp.sendRedirect("api/signup.jsp");
            e.printStackTrace();
        }
    }
}

UserDAO

아이디 중복 검사 기능 구현

회원 가입 기능과 함께 구현해야 하는 기능이 아이디 중복 검사 기능이다.

내 생각에 중복 검사 기능을 설계하는 방법은 크게 2가지가 있다.

  1. 중복 확인 버튼을 누르면 입력한 아이디가 users 테이블에 존재하는지 쿼리를 사용해 확인한다.
  2. 테이블을 생성할 시 중복을 방지하기 위해 UNIQUE 제약 조건 또는 PRIMARY KEY를 사용한다.

위 방법 외에도 여러 방법이 있겠지만 난 2번 방법으로 결정했다. 처음 테이블을 생성할 때 이미 user_id 컬럼을 PRIMARY KEY 로 설정했기 때문이다.. 이 이유 뿐 아니라 보안적으로도 2번이 더 안전한 설계이기 때문이다. 1번 같은 경우는 테이블에 user_id 컬럼을 UNIQUE 제약 조건 또는 PRIMARY KEY 를 사용하지 않고 설계했을 때 매우 위험하다. 예를 들어 프록시 도구인 burp suite 또는 개발자 도구를 사용하면 클라이언트 측에서 중복 버튼 코드를 제거해 우회가 가능하다.

구현

나는 테이블을 생성할 때 중복을 방지하고자 user_id 컬럼을 PRIMARY KEY 로 지정했다. 따라서 회원 가입 시 중복된 아이디라면 예외가 발생한다. 예외 명은 아래와 같다.

java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (INMO.SYS_C007164) violated

아래 코드는 회원 가입 코드이다. 예외가 발생하므로 예외 처리를 하여 중복 검사 기능을 구현하겠다.

public int UserInsert(UserVO vo) {
            String query = "INSERT INTO users(user_id, name, password, address) VALUES('" + vo.getUser_id() + "', '" + vo.getName() + "', '" + vo.getPassword() + "', + '" + vo.getAddress() + "')"; 
            getConnect()

            int cnt = -1;

            try {
                stmt = conn.createStatement(); 
                cnt = stmt.executeUpdate(query); 
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                dbClose();
            }

SQLIntegrityConstraintViolationException 예외 클래스를 캐치해 Exception 내용이 unique constraint 일 경우 클라이언트 측 에러 코드를 의미하는 숫자 4를 리턴하겠다.

public int UserInsert(UserVO vo) {
            String query = "INSERT INTO users(user_id, name, password, address) VALUES('" + vo.getUser_id() + "', '" + vo.getName() + "', '" + vo.getPassword() + "', + '" + vo.getAddress() + "')"; // Oracle query
            getConnect(); 

            int cnt = -1;

            try {
                stmt = conn.createStatement(); 
                cnt = stmt.executeUpdate(query); 
            } catch (SQLIntegrityConstraintViolationException e) {
                e.printStackTrace();
                String message = e.getMessage();
                if (message.contains("ORA-00001: unique constraint")) {
                    cnt = 4;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                dbClose();
            }
            return cnt; // 1 or 0 or 4
        }

UserInsert 메서드는 UserInsertController 에서 실행된다. 따라서 컨트롤러는 메서드가 1 을 리턴하면 로그인 성공을 안내하고 4를 리턴하면 이미 가입한 아이디라고 안내한다. 만약 0 을 리턴한다면 Insert Error Exception을 발생시키겠다.

중복된 아이디 안내 코드

아래 코드는 UserInsertController 의 로그인 성공을 판별하는 코드이다. result 변수가 “4” 라면 클라이언트 측에 이미 가입한 아이디라고 안내한 뒤, 회원가입 페이지로 리다이렉트한다.

UserDAO userdao = new UserDAO();
                int result = userdao.UserInsert(uservo); // 리턴 값을 result 변수에 담는다.
                if (result == 1) {
                    req.setAttribute("vo", uservo);
                    req.getRequestDispatcher("loginprocess").forward(req, resp);
                } else if (result == 4) {
                    resp.setContentType("text/html;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.println("<script>alert(\"이미 가입한 아이디입니다.\"); location.href='api/signup.html';</script>");
                } else {
                    throw new ServletException("Insert Error");
                }

추후에 해당 코드는 클라이언트 측에 JSON 객체를 응답하는 코드로 수정할 것이다.

시연

중복검사 기능 시연 동영상

UserInsert

public int UserInsert(UserDTO dto) {
        String query = "INSERT INTO users(user_id, name, password, address) VALUES(?, ?, ?, ?)";
        int cnt = -1; // query 실행 결과
        
        // Connecrtion 객체 생성
        conn = driver.getConnect();
        try {
            psmt = conn.prepareStatement(query); 
            psmt.setString(1, dto.getUser_id());
            psmt.setString(2, dto.getName());
            psmt.setString(3, dto.getPassword());
            psmt.setString(4, dto.getAddress());
            cnt = psmt.executeUpdate(); // 전송(SQL 실행) -> 정상적으로 Insert 되었다면 성공한 행은 1개이므로 1이 리턴된다. Insert 실패하면 0을 리턴한다.
        } catch (SQLIntegrityConstraintViolationException e) {
            e.printStackTrace();
            String message = e.getMessage();
            if (message.contains("ORA-00001: unique constraint")) {
                cnt = 4;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            driver.dbClose(psmt, conn);
        }
        return cnt; // 1 or 0 or 4
    }

출처

https://github.com/simyat/Vulnerable-Board-Web-Application

profile
하루하루 성실하게, 인생 전체는 되는대로.
post-custom-banner

0개의 댓글