IT/development

[springBoot] 세션 인증 로그인/로그아웃

알 수 없는 사용자 2023. 5. 8. 22:49
반응형

table

CREATE TABLE USER_INFO" 
   ("USER_NO" NUMBER(10,0) NOT NULL ENABLE, 
	"ID" VARCHAR2(20) NOT NULL ENABLE, 
	"PASSWORD" VARCHAR2(50) NOT NULL ENABLE, 
	"NAME" VARCHAR2(20) NOT NULL ENABLE, 
	"EMAIL" VARCHAR2(50) NOT NULL ENABLE, 
	"CREATE_DATE" DATE NOT NULL ENABLE, 
	"UPDATE_DATE" DATE DEFAULT SYSDATE, 
	 CONSTRAINT "USER_PK" PRIMARY KEY ("USER_NO", "ID")
); 

COMMENT ON COLUMN USER_INFO.USER_NO IS '사용자 순번(시퀀스:USER_NO_SEQ)';
COMMENT ON COLUMN USER_INFO.ID IS '아이디';
COMMENT ON COLUMN USER_INFO.PASSWORD IS '비밀번호';
COMMENT ON COLUMN USER_INFO.NAME IS '이름';
COMMENT ON COLUMN USER_INFO.EMAIL IS '이메일';
COMMENT ON COLUMN USER_INFO.CREATE_DATE IS '등록일';
COMMENT ON COLUMN USER_INFO.UPDATE_DATE IS '수정일';

xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="study.example.thboard.mapper.UserMapper">

    <!--로그인한 회원 정보-->
    <select id="selectById" parameterType="map" resultType="UserVo">
        SELECT USER_NO AS userNo
             , ID AS id
             , PASSWORD AS password
             , NAME AS name
             , EMAIL AS email
          FROM USER_INFO
         WHERE 1=1
           AND ID = #{id}
    </select>

</mapper>

Vo

package study.example.thboard.vo;

import lombok.Data;

@Data
public class UserVo extends CommonVo{

    private Long userNo;
    private String id;
    private String password;
    private String name;
    private String email;
}

mapper interface

package study.example.thboard.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import study.example.thboard.vo.BoardVo;
import study.example.thboard.vo.UserVo;

import java.util.List;

@Repository @Mapper
public interface UserMapper {
    
    /* 아이디로 사용자 정보 조회 */
    UserVo selectById(@Param("id") String id);
}

service

package study.example.thboard.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import study.example.thboard.mapper.BoardMapper;
import study.example.thboard.mapper.UserMapper;
import study.example.thboard.vo.BoardVo;
import study.example.thboard.vo.UserVo;

import java.util.List;

@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {

    private final UserMapper userMapper;

    /**
     * 아이디/패스워드 확인
     * @param id
     * @param password
     * @return
     */
    public String  login(String id, String password) {
        UserVo useInfo = userMapper.selectById(id);

        return useInfo.getPassword().equals(password) ? useInfo.getId() : null;
    }
}

controller

package study.example.thboard.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import study.example.thboard.service.UserService;
import study.example.thboard.vo.UserVo;

import javax.servlet.http.HttpSession;

@Controller
@RequiredArgsConstructor
@Slf4j
public class UserController {

    private final UserService userService;

    /**
     * 로그인 화면
     * @param session
     * @return
     */
    @GetMapping("/login")
    public String loginForm(HttpSession session) {
        String id = (String) session.getAttribute("id");
        log.info("세션에 저장된 사용자 아이디 = {} ", id);
        return id != null ? "redirect:/" : "pages/login" ;
    }

    /**
     * 로그인 처리 
     * @param userVo
     * @param session
     * @return
     */
    @PostMapping("/login")
    public String login(UserVo userVo, HttpSession session) {
        String id = userService.login(userVo.getId(), userVo.getPassword());
        if(id == null) return "redirect:/login";

        session.setAttribute("id", id);
        return "redirect:/";
    }

    /**
     * 로그아웃
     * @param session
     * @return
     */
    @PostMapping("/logout")
    public String logout(HttpSession session) {
        log.info("로그아웃!");
        session.invalidate();
        return "redirect:/login";
    }
}

login.html

디자인은 무시하고 로그인 클릭 이벤트post방식으로 id와 password만 서버로 넘김

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/default_layout}">

    <script layout:fragment="script" th:inline="javascript" type="text/javascript">
        $(document).ready(function () {

        });
    </script>

<!-- Content -->
<div layout:fragment="content">
    <form id="frm" action="/login" method="post">
    <div id="layoutAuthentication">
            <div id="layoutAuthentication_content">
                <main>
                    <div class="container">
                        <div class="row justify-content-center">
                            <div class="col-lg-5">
                                <div class="card shadow-lg border-0 rounded-lg mt-5">
                                    <div class="card-header"><h3 class="text-center font-weight-light my-4">Login</h3></div>
                                    <div class="card-body">
                                        <form>
                                            <div class="form-floating mb-3">
                                                <input class="form-control" id="id" name="id" type="id" placeholder="name@example.com" />
                                                <label for="inputEmail">ID</label>
                                            </div>
                                            <div class="form-floating mb-3">
                                                <input class="form-control" id="password" name="password" type="password" placeholder="Password" />
                                                <label for="inputPassword">Password</label>
                                            </div>
                                            <div class="form-check mb-3">
                                                <input class="form-check-input" id="inputRememberPassword" type="checkbox" value="" />
                                                <label class="form-check-label" for="inputRememberPassword">Remember Password</label>
                                            </div>
                                            <div class="d-flex align-items-center justify-content-between mt-4 mb-0">
                                                <a class="small" href="password.html">Forgot Password?</a>
                                                <button type="submit" class="btn btn-success">Login</button>
                                            </div>
                                        </form>
                                    </div>
                                    <div class="card-footer text-center py-3">
                                        <div class="small"><a href="register.html">Need an account? Sign up!</a></div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </main>
            </div>
            <div id="layoutAuthentication_footer">
                <footer class="py-4 bg-light mt-auto">
                    <div class="container-fluid px-4">
                        <div class="d-flex align-items-center justify-content-between small">
                            <div class="text-muted">Copyright &copy; Your Website 2023</div>
                            <div>
                                <a href="#">Privacy Policy</a>
                                &middot;
                                <a href="#">Terms &amp; Conditions</a>
                            </div>
                        </div>
                    </div>
                </footer>
            </div>
        </div>
    </form>
</div>
</html>

logout.html

타임리프로 레이아웃을 나눠 놨지만 무시하고 로그아웃 버튼 클릭 이벤트로

post방식으로 서버 호출만 하면 됨

<html lagn="ko" xmlns:th="http://www.thymeleaf.org">
    <!--headerFragment 선언-->
    <nav th:fragment="headerFragment" class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
        <!-- Navbar Brand-->
            <a class="navbar-brand ps-3" th:href="@{/}">Start Bootstrap</a>
            <!-- Sidebar Toggle-->
            <button class="btn btn-link btn-sm order-1 order-lg-0 me-4 me-lg-0" id="sidebarToggle" href="#!"><i class="fas fa-bars"></i></button>
            <!-- Navbar Search-->
            <form class="d-none d-md-inline-block form-inline ms-auto me-0 me-md-3 my-2 my-md-0">
                <div class="input-group">
                    <input class="form-control" type="text" placeholder="Search for..." aria-label="Search for..." aria-describedby="btnNavbarSearch" />
                    <button class="btn btn-primary" id="btnNavbarSearch" type="button"><i class="fas fa-search"></i></button>
                </div>
            </form>
            <!-- Navbar-->
            <ul class="navbar-nav ms-auto ms-md-0 me-3 me-lg-4">
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="fas fa-user fa-fw"></i></a>
                    <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                        <li><a class="dropdown-item" href="#!">Settings</a></li>
                        <li><a class="dropdown-item" href="#!">Activity Log</a></li>
                        <li><hr class="dropdown-divider" /></li>
                        <li><a class="dropdown-item" id="logout" href="#">Logout</a></li>
                        <script>
                            $(document).ready(function () {
                                $("#logout").on("click", function () {
                                    let form = $("<form></form>");
                                    form.attr("method","post");
                                    form.attr("action", "/logout");
                                    form.appendTo("body");
                                    form.submit();
                                });
                            });
                        </script>
                    </ul>
                </li>
            </ul>
    </nav>
</html>

index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/default_layout}">

    <script layout:fragment="script" th:inline="javascript" type="text/javascript">
        $(document).ready(function () {
            //글쓰기
            $("#btnSave").on("click", function () {
                $("#frm").attr("action", "/regForm").submit();
            });

            //검색
            $("#btnSearch").on("click", function (e) {
                e.preventDefault();
                $("#pageNum").val(1);
                $("#frm").submit();
            });
        });

        //삭제
        function del(no) {
            let boardNo = $("<input>").attr("type", "hidden").attr("name", "boardNo").val(no);
            $("#frm").attr("method", "post")
                     .attr("action", "del")
                     .append(boardNo)
                     .submit();
        }
    </script>

<!-- Content -->
<div layout:fragment="content">
      <form id="frm" th:object="${pageMaker}" action="" method="get">
      <main>
        <div class="container-fluid px-4">
          <h1>게시글 관리</h1>

            <div class="input-group mb3">
                <select name="type" th:field="${cri.type}">
                    <option value="">--</option>
                    <option value="W">작성자</option>
                    <option value="T">제목</option>
                    <option value="C">내용</option>
                </select>
                <input type="text" name="keyword" th:field="${cri.keyword}">
                <input type="hidden" id="pageNum" name="pageNum" th:value="${cri.pageNum}">
                <input type="hidden" id="amount" name="amount" th:value="${cri.amount}">
                <button type="button" id="btnSearch" class="btn btn-secondary">검색</button>
            </div>

            <table class="table table-striped table-sm">
                <thead>
                    <tr>
                        <th>순번</th>
                        <th>제목</th>
                        <th>내용</th>
                        <th>작성자</th>
                        <th>등록일</th>
                        <th>수정일</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    <tr th:if="${#lists.size(list) >0}" th:each="list : ${list}">
                        <td th:text="${list.no}"></td>
                        <td><a th:href="@{/detail(boardNo=${list.boardNo})}" th:text="${list.title}"></a></td>
                        <td th:text="${list.content}"></td>
                        <td th:text="${list.writer}"></td>
                        <td th:text="${list.createDate}"></td>
                        <td th:text="${list.updateDate}"></td>
                        <td><a href="#" th:href="'javascript:del('+${list.boardNo}+')'" class="btn btn-danger">삭제</a></td>
                    </tr>
                </tbody>
            </table>
        </div>

        <!-- 게시판 하단 페이지네이션 영역 start -->
        <nav aria-label="Page navigation">
            <ul class="pagination justify-content-center">
                <!-- prev -->
                <li class="page-item" th:if="${pageMaker.prev} == true">
                    <a class="page-link" th:href="@{/(pageNum=${pageMaker.startPage}-1, type=${pageMaker.cri.type}, keyword=${pageMaker.cri.keyword})}">Prev</a>
                </li>
                <!-- pageMaker의 startPage부터 endPage까지 루프, a태그의 href에 idx를 링크(get방식으로 pageNum을 붙여서) -->
                <li class="page-item" id="paginate_btn" th:each="idx: ${#numbers.sequence(pageMaker.startPage, pageMaker.endPage)}" th:classappend="${pageMaker.cri.pageNum} == ${idx} ? active : null">
                    <a class="page-link" th:href="@{/(pageNum=${idx}, type=${pageMaker.cri.type}, keyword=${pageMaker.cri.keyword})}" th:text="${idx}"></a>
                </li>
                <!-- next -->
                <li class="page-item" th:if="${pageMaker.next} == true and ${pageMaker.endPage > 0}">
                    <a class="page-link" th:href="@{/(pageNum=${pageMaker.endPage}+1, type=${pageMaker.cri.type}, keyword=${pageMaker.cri.keyword})}">Next</a>
                </li>
            </ul>
        </nav>
        <!-- // 게시판 하단의 페이지네이션 영역 end -->
		  <!-- 로그인 한 사용자만 글쓰기 버튼 표시 -->	
          <th:block th:if="${session.id != null}">
          <button type="button" id="btnSave" class="btn btn-info">글쓰기</button>
          </th:block>
      </main>
      </form>
</div>
</html>

참조: https://velog.io/@wooryung/Spring-Boot-Session%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

[Spring Boot] Session으로 로그인, 회원가입 구현하기

요구사항 / 로그인 전이면 로그인 페이지(/login)로 이동 로그인한 사용자만 / 접근 가능 로그인한 사용자 정보 표시 탈퇴하기 버튼 → /delete 로그아웃 버튼 → /logout 수정하기 버튼 → /update /si

velog.io

반응형