이전 게시글에서는 커스텀 로그인 폼을 적용하여 로그인을 수행하였다. Authentication Provider를 사용하여 회원정보 테이블을 사용하여 UserDetailsService를 사용하여 사용자 프로파일을 세션에 저장하여 시큐리티에서 사용하도록 하였다.

여기서는 시큐리티 적용에 따른 세션 principal 정보를 이용하여 로그인 여부에 따른 메뉴를 다르게 보여주도록 하고, 로그아웃 처리를 하고자 한다.

현재 오른쪽 위에 있는 Dropdown 메뉴는 아래 그림과 같이 임의의 메뉴를 배치하여 자리만 확보하고 있는 상태이다. 이것을 로그인 여부에 따라 메뉴를 달리하도록 수정한다.

로그인 여부에 따라 메뉴를 다르게 하기 위하여 시큐리티에서 로그인 여부를 나타내는 sec:authorize="isAuthenticated()"나 sec:authorize="isAnonymous()"를 사용하여 메뉴를 달리한다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="setContent(content)">
<head>
<meta charset="utf-8" />
<meta name="viewport"
	content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
<title>Talanton 스프링 부트 홈 페이지</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" th:href="@{/assets/favicon.ico}" />
<!-- Core theme CSS (includes Bootstrap)-->
<link th:href="@{/css/styles.css}" rel="stylesheet" />
<!-- Bootstrap core JS-->
<script
	src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/scripts.js}"></script>
</head>
<body>
<div class="d-flex" id="wrapper">
	<!-- Sidebar-->
	<div class="border-end bg-white" id="sidebar-wrapper">
		<div class="sidebar-heading border-bottom bg-light">Start
			Bootstrap</div>
		<div class="list-group list-group-flush">
			<a class="list-group-item list-group-item-action list-group-item-light p-3"
				href="#!">Dashboard</a>
			<a class="list-group-item list-group-item-action list-group-item-light p-3"
				href="#!">Shortcuts</a>
			<a class="list-group-item list-group-item-action list-group-item-light p-3"
				href="#!">Overview</a>
			<a class="list-group-item list-group-item-action list-group-item-light p-3"
				href="#!">Events</a>
			<a class="list-group-item list-group-item-action list-group-item-light p-3"
				href="#!">Profile</a>
			<a class="list-group-item list-group-item-action list-group-item-light p-3"
				href="#!">Status</a>
		</div>
	</div>
	<!-- Page content wrapper-->
	<div id="page-content-wrapper">
		<!-- Top navigation-->
		<nav
			class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
			<div class="container-fluid">
				<button class="btn btn-primary" id="sidebarToggle">Toggle
					Menu</button>
				<button class="navbar-toggler" type="button"
					data-bs-toggle="collapse"
					data-bs-target="#navbarSupportedContent"
					aria-controls="navbarSupportedContent" aria-expanded="false"
					aria-label="Toggle navigation">
					<span class="navbar-toggler-icon"></span>
				</button>
				<div class="collapse navbar-collapse" id="navbarSupportedContent">
					<ul class="navbar-nav ms-auto mt-2 mt-lg-0">
						<li class="nav-item active"><a class="nav-link" href="#!">Home</a></li>
						<li class="nav-item"><a class="nav-link" href="#!">Link</a></li>
						<li class="nav-item dropdown"><a
							class="nav-link dropdown-toggle" id="navbarDropdown" href="#"
							role="button" data-bs-toggle="dropdown" aria-haspopup="true"
							aria-expanded="false">Dropdown</a>
							<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
								<a sec:authorize="isAuthenticated()" class="dropdown-item logoutBtn" href="#">Logout</a>
                            	<a sec:authorize="isAuthenticated()" class="dropdown-item" th:href="@{/member/profile(email=${#authentication.principal.member.email})}">MyPage</a>
                            	<a sec:authorize="isAnonymous()" class="dropdown-item" href="/member/login">Login</a>
                            	<a sec:authorize="isAnonymous()" class="dropdown-item" href="/member/join">Join</a>
                            	<a sec:authorize="isAnonymous()" class="dropdown-item" href="#">아이디 찾기</a>
                            	<a sec:authorize="isAnonymous()" class="dropdown-item" href="#">비밀번호 찾기</a>
							</div>
						</li>
					</ul>
				</div>
			</div>
		</nav>
		<!-- Page content-->
		<div class="container-fluid">
			<th:block th:replace = "${content}"></th:block>
		</div>
	</div>
</div>
<script>
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");

// 사이드바 메뉴 토글
$("#sidebarToggle").click(function(e) {
    e.preventDefault();
    $("#wrapper").toggleClass("toggled");
});

// 로그아웃 처리
$(".logoutBtn").click(function(e) {
	e.preventDefault();
	$.ajax({
		url: '/member/logout',
		type: 'post',
		// CSRF 토큰 값을 헤더로 전송
		beforeSend: function(xhr) {
		    xhr.setRequestHeader(header, token);
		},
		success: function(data) {
			alert("로그아웃 되었습니다.");
			location.href = "/member/login";
		}
	});
});
</script>
</body>
</th:block>
</html>

로그인이 되지 않은 경우 아래와 같은 메뉴가 보여진다.

Login 메뉴를 클릭한 후 로그인을 거치면 다음과 같이 메뉴가 표시됨을 알 수 있다.

이전에 구현한 것이지만 Toggle Menu 버튼을 누르면 왼쪽의 sidebar 메뉴가 토글되는 것을 역시 확인할 수 있다. 이 부분은 basic.html의 하단부에 있는 자바스크립트에서 메뉴에 대하여 이벤트를 등록하고 이벤트가 발생하면 sidebar 메뉴를 토글시켜줌으로써 동작시킬 수 있다.

<script>
$("#sidebarToggle").click(function(e) {
    e.preventDefault();
    $("#wrapper").toggleClass("toggled");
});
</script>

로그아웃 처리는 오른쪽 위의 Dropdown 메뉴를 누르면 Logout 메뉴가 나타나는데, 그것을 누르면 basic.html 하단에서 자바스크립트에서 이벤트로 처리를 한다

<!-- head 영역에 저장 -->
<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

<!-- 자바스크립트 영역 -->
<script>
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");

// 사이드바 메뉴 토글
$("#sidebarToggle").click(function(e) {
    e.preventDefault();
    $("#wrapper").toggleClass("toggled");
});

// 로그아웃 처리
$(".logoutBtn").click(function(e) {
	e.preventDefault();
	$.ajax({
		url: '/member/logout',
		type: 'post',
		// CSRF 토큰 값을 헤더로 전송
		beforeSend: function(xhr) {
		    xhr.setRequestHeader(header, token);
		},
		success: function(data) {
			alert("로그아웃 되었습니다.");
			location.href = "/member/login";
		}
	});
});
</script>

.basic.html의 <head> 영역에서 <meta> 태그를 사용하여 "_csrf"와 "_csrf_header"에 대하여 값을 저장한 후 자바스크립트에서 $("meta[name='_csrf']".attr("content")를 사용하여 값을 가져와 사용한다.

CSRF가 활성화 되어 있는 경우 로그아웃을 POST 방식으로 처리를 하여야 하며, Ajax를 사용하여 처리를 한다. 또한 CSRF 값을 전송하여야 하므로 시큐리티에서 제공하는 "_csrf"와 "_csrf_header" 값을 사용한다.

기본적으로 security에서 로그아웃 처리를 지원하므로 다음과 같이 SecurityConfig에서 설정하면 동작한다. 아래와 같이 http.logout().logoutUrl("/member/logout"); 로그아웃 url을 추가해 준다.

package com.example.sboot.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.example.sboot.security.service.CustomUserDetailsService;

import lombok.extern.log4j.Log4j2;

@Configuration
@Log4j2
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
    private CustomUserDetailsService userDetailsService;
	
	@Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin().loginPage("/member/login");
		http.userDetailsService(userDetailsService);
		http.logout().logoutUrl("/member/logout");
	}
}

Dropdown 메뉴에서 Logout 버튼을 클릭하면 아래 그림과 같이 로그아웃이 처리되었다고 경고창이 뜨고, 다시 로그인 창으로 이동함을 알 수 있다.

Dropdown 메뉴도 로그인 이전의 메뉴가 표시됨을 알 수 있다.

소스코드는 아래와 같다.

sboot.zip
0.23MB

Posted by 세상을 살아가는 사람
,

다음은 웹을 배우면서 홈 페이지를 개발하는 프로젝트를 수행한다. 이때 필요한 기능들을 기술해 본다.

  • 회원관리 기능 : 회원가입, 약관 및 개인정보수집 동의, 로그인, 로그아웃, 아이디/비밀번호 찾기, 회원탈퇴, 회원정보 관리(목록보기, 회원정보보기, 변경), 아이디 중복 검사, 아이디/비밀번호 입력제약 Audit 기능, 개인정보 암호화 저장

  • 게시판 기능 : 페이징 처리, 댓글 순서 관리, 목록보기, 게시글 쓰기, 게시글 수정, 게시글 삭제, 게시글 검색, 스마트 에디터 사용, 첨부파일 저장, 비밀글 기능, 게시판 종류 및 다른점 처리 (방문록, Q&A, 공지사항, 게시판, 자료실, FAQ), 게시판 권한관리(읽기, 쓰기, 답변, 첨부 허용여부 관리), 다중 게시판 만들기(게시판이 여러 개일 때 관리방법), 게시판 관리(목록보기, 추가, 변경, 삭제)

  • 메뉴 체계 및 링크 연동 : 헤더에 있는 nav 및 사이드바에 있는 lnav

    • nav : 몇 단으로 할 것인가? 마우스를 갖다 대면 전체 메뉴를 보여 줄 것인가? 아니면 1단 메뉴에 속한 2단 세부 메뉴를 보여줄 것인가?

    • lnav : nav와 연동되어 표시되는 방법은? 몇 단으로 구성할 것인가?

    • 관리자 측면에서는 메뉴의 관리를 할 것인가? 즉, 메뉴에 대한 추가, 삭제, 변경 및 목록보기, 보기를 허용할 것인지? 아니면 고정적으로 운영할 것인지 이다.

  • SNS 연동 기능 : 페이스북 로그인, 카카오톡 로그인, 트위터 로그인, 게시글 공유(카톡, 페이스북, 트위터), 좋아요, 별점

  • 반응형 웹 만들기 : 휴대폰에서 브라우저에서 접속할 때 동작

  • 실행환경 서버 관리(윈도우 서버, 리눅스 서버) : 네트워크, 하드웨어(CPU, 메모리, 하드디스크, 백업장치), 도메인관리, 호스팅 관리 , 실행환경 구축

  • 전자정부표준프레임 : 공통기능(모바일 포함), 하이브리드앱

  • HTML5 기능 : 비디오, 오디오, 캔버스, 드래그 & 드롭, 지오로케이션, 웹 스토리지, Indexed DB, 파일, 커뮤니케이션, 웹워커, 히스토리, 오프라인

  • Open API 연동 : Rest API, SOAP API 연동 (공공데이터 연동)

  • Open API 기능 제공 : REST API 제공

  • 주기적인 Job 수행 방법


Posted by 세상을 살아가는 사람
,