feature/admin #5

Merged
gh.yeom merged 6 commits from feature/admin into main 2024-05-10 17:26:59 +09:00
21 changed files with 319 additions and 67 deletions
Showing only changes of commit 86d76b6d38 - Show all commits

View File

@ -21,7 +21,7 @@ dependencies {
// thymeleaf // thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0'
// jpa // jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

View File

@ -13,16 +13,19 @@ public class Administrator extends BaseEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(name = "login_id") @Column(name = "login_id", length = 100, nullable = false)
private String loginId; private String loginId;
@Column(name = "password") @Column(name = "password", length = 255, nullable = false)
private String password; private String password;
@Column(name = "email") @Column(name = "email", length = 100, nullable = false)
private String email; private String email;
@Column(name = "name") @Column(name = "name", length = 100, nullable = false)
private String name; private String name;
@OneToOne(mappedBy = "administrator", fetch = FetchType.LAZY)
private AdministratorRole administratorRole;
} }

View File

@ -0,0 +1,24 @@
package com.bpgroup.poc.admin.domain.admin;
import com.bpgroup.poc.admin.domain.BaseEntity;
import jakarta.persistence.*;
import lombok.Getter;
@Getter
@Entity
@Table(name = "administrator_role")
public class AdministratorRole extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "administrator_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Administrator administrator;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "role_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Role role;
}

View File

@ -0,0 +1,14 @@
package com.bpgroup.poc.admin.domain.admin;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum Menu {
ADMINISTRATOR_MANAGEMENT("/admin/management", "관리자 관리"),
ROLE_MANAGEMENT("/admin/role", "권한 관리"),
MENU_MANAGEMENT("/admin/menu", "메뉴 관리")
;
private final String uri;
private final String name;
}

View File

@ -0,0 +1,26 @@
package com.bpgroup.poc.admin.domain.admin;
import com.bpgroup.poc.admin.domain.BaseEntity;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "role")
public class Role extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", length = 100, nullable = false)
private String name;
@Column(name = "description")
private String description;
@OneToMany(mappedBy = "role", fetch = FetchType.LAZY)
private List<RoleMenu> roleMenus = new ArrayList<>();
}

View File

@ -0,0 +1,22 @@
package com.bpgroup.poc.admin.domain.admin;
import com.bpgroup.poc.admin.domain.BaseEntity;
import jakarta.persistence.*;
@Table
@Entity(name = "role_menu")
public class RoleMenu extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "role_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Role role;
@Enumerated(EnumType.STRING)
@Column(name = "menu", length = 100, nullable = false)
private Menu menu;
}

View File

@ -0,0 +1,6 @@
package com.bpgroup.poc.admin.domain.admin;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RoleRepository extends JpaRepository<Role, Long> {
}

View File

@ -2,6 +2,7 @@ package com.bpgroup.poc.admin.security;
import com.bpgroup.poc.admin.common.FormatHelper; import com.bpgroup.poc.admin.common.FormatHelper;
import com.bpgroup.poc.admin.security.authentication.AuthenticationFailException; import com.bpgroup.poc.admin.security.authentication.AuthenticationFailException;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -15,6 +16,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.util.Objects; import java.util.Objects;
@Configuration @Configuration
@RequiredArgsConstructor
public class SecurityConfig { public class SecurityConfig {
private static final String LOGIN_PATH = "/login"; private static final String LOGIN_PATH = "/login";
@ -27,19 +29,20 @@ public class SecurityConfig {
CsrfTokenRequestAttributeHandler csrfTokenRequestAttributeHandler = new CsrfTokenRequestAttributeHandler(); CsrfTokenRequestAttributeHandler csrfTokenRequestAttributeHandler = new CsrfTokenRequestAttributeHandler();
csrfTokenRequestAttributeHandler.setCsrfRequestAttributeName("_csrf"); csrfTokenRequestAttributeHandler.setCsrfRequestAttributeName("_csrf");
http.csrf(t -> { http.csrf(c -> {
t.csrfTokenRequestHandler(csrfTokenRequestAttributeHandler) c.csrfTokenRequestHandler(csrfTokenRequestAttributeHandler)
.ignoringRequestMatchers("/common/modal/**") .ignoringRequestMatchers("/common/modal/**")
.ignoringRequestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH) // CSRF 무시 URL 설정 .ignoringRequestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH) // CSRF 무시 URL 설정
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); // CSRF 토큰을 쿠키에 저장 사용 가능 .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); // CSRF 토큰을 쿠키에 저장 사용 가능
}).addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // 로그인이 완료된 CSRF Filter 실행 }).addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // 로그인이 완료된 CSRF Filter 실행
// 인증 설정 // 인증 설정
http.authorizeHttpRequests(c -> c http.authorizeHttpRequests(c -> {
.requestMatchers("/css/**", "/images/**", "/js/**").permitAll() c.requestMatchers("/css/**", "/images/**", "/js/**").permitAll()
.requestMatchers("/common/modal/**").permitAll() .requestMatchers("/common/modal/**").permitAll()
.requestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH).permitAll() .requestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH).permitAll()
.anyRequest().authenticated()); .anyRequest().authenticated();
});
http.formLogin(c -> c http.formLogin(c -> c
.loginPage(LOGIN_PATH) .loginPage(LOGIN_PATH)

View File

@ -0,0 +1,35 @@
package com.bpgroup.poc.admin.web.advice;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
@ControllerAdvice
@RequiredArgsConstructor
public class PathInfoControllerAdvice {
@ModelAttribute("pathInfo")
public PathInfo pathInfo(HttpServletRequest request) {
return new PathInfo(request);
}
@Getter
public static class PathInfo {
private final String currentPath;
public PathInfo(HttpServletRequest request) {
this.currentPath = request.getServletPath();
}
public boolean isActive(String path) {
return currentPath.startsWith(path);
}
public String activeClass(String path) {
return isActive(path) ? "active" : "";
}
}
}

View File

@ -0,0 +1,16 @@
package com.bpgroup.poc.admin.web.main.admin.management;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/admin/management")
public class AdministratorManagementController {
@GetMapping
public String administratorManagementPage() {
return "main/admin/management/list";
}
}

View File

@ -0,0 +1,15 @@
package com.bpgroup.poc.admin.web.main.admin.role;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/admin/role")
public class RoleController {
@GetMapping
public String rolePage() {
return "main/admin/role/list";
}
}

View File

@ -1,2 +1,17 @@
INSERT INTO `administrator` (`login_id`, `password`, `email`, `name`, `create_date`, `update_date`) INSERT INTO `administrator` (`login_id`, `password`, `email`, `name`, `create_date`, `update_date`)
VALUES ('admin', '$2a$10$g6UOrQ/OS8o5r5CJk7C5juVFaItQ62U3EIn8zLPzkFplM3wVLvKZ2', 'admin@admin.com', '홍길동', CURDATE(), CURDATE()); VALUES ('admin', '$2a$10$g6UOrQ/OS8o5r5CJk7C5juVFaItQ62U3EIn8zLPzkFplM3wVLvKZ2', 'admin@admin.com', '홍길동', CURDATE(), CURDATE());
INSERT INTO `role` (`name`, `description`, `create_date`, `update_date`)
VALUES ('SUPER_ADMIN', '최고 관리자', CURDATE(), CURDATE()),
('ADMIN', '관리자', CURDATE(), CURDATE()),
('CLIENT', '고객', CURDATE(), CURDATE());
INSERT INTO `administrator_role` (`administrator_id`, `role_id`, `create_date`, `update_date`)
VALUES ('1', '1', CURDATE(), CURDATE());
INSERT INTO `role_menu` (`role_id`, `menu`, `create_date`, `update_date`)
VALUES ('1', 'ADMINISTRATOR_MANAGEMENT', CURDATE(), CURDATE()),
('1', 'ROLE_MANAGEMENT', CURDATE(), CURDATE()),
('1', 'MENU_MANAGEMENT', CURDATE(), CURDATE());

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>관리자 시스템</title>
<script type="text/javascript" th:src="@{/js/jquery/jquery-3.7.1.min.js}"></script>
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<link rel="stylesheet" th:href="@{/css/style.css}"/>
<link rel="stylesheet" th:href="@{/css/sub.css}"/>
<script type="text/javascript" th:src="@{/js/motion.js}"></script>
<script type="text/javascript" th:src="@{/js/pagehelper.js}"></script>
<script type="text/javascript" th:src="@{/js/reqhelper.js}"></script>
<script type="text/javascript" th:src="@{/js/eventrouter.js}"></script>
<th:block th:replace="~{fragment/csrf/csrf :: applyCsrf}"></th:block>
</head>
<body>
<header>
<th:block th:replace="~{layout/gnb :: gnb}"></th:block>
</header>
<div class="wrapper flexbox">
<th:block th:replace="~{layout/lnb :: lnb}"></th:block>
<div class="container flexitem">
<th:block layout:fragment="contents"/>
</div>
</div>
<th:block layout:fragment="script">
</th:block>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="lnb">
<nav class="menu_wrap flexitem">
<h2>메뉴</h2>
<button class="closeButton"><img th:src="@{/images/ico_close.svg}"></button>
<a class="nav_tit"
th:classappend="${pathInfo.activeClass('/admin')}">
관리자
</a>
<ul class="nav_section">
<li>
<a th:with="path='/admin/management'" th:href="${path}"
th:classappend="${pathInfo.activeClass(path)}">관리자 관리</a>
<a th:with="path='/admin/role'" th:href="${path}"
th:classappend="${pathInfo.activeClass(path)}">권한 관리</a>
</li>
</ul>
</nav>
</th:block>
</body>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{main/admin/management/root.html}">
<body>
<th:block layout:fragment="innerContents">
</th:block>
<th:block layout:fragment="innerScript">
<script th:inline="javascript">
</script>
</th:block>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!-- 메뉴 공통 영역-->
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/common.html}">
<body>
<th:block layout:fragment="contents">
<th:block layout:fragment="innerContents"></th:block>
</th:block>
</body>
<th:block layout:fragment="script">
<th:block layout:fragment="innerScript"></th:block>
</th:block>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{main/admin/role/root.html}">
<body>
<th:block layout:fragment="innerContents">
</th:block>
<th:block layout:fragment="innerScript">
<script th:inline="javascript">
</script>
</th:block>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!-- 메뉴 공통 영역-->
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/common.html}">
<body>
<th:block layout:fragment="contents">
<th:block layout:fragment="innerContents"></th:block>
</th:block>
</body>
<th:block layout:fragment="script">
<th:block layout:fragment="innerScript"></th:block>
</th:block>
</html>

View File

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<body>
<th:block th:fragment="lnb">
<nav class="menu_wrap flexitem">
<!--/*@thymesVar id="pathInfo" type="kr.co.bpsoft.settlement_system.backend.admin.web.advice.PathInfoControllerAdvice$PathInfo"*/-->
<h2>메뉴</h2>
<button class="closeButton"><img th:src="@{/images/ico_close.svg}"></button>
<th>
<a href="#"
class="nav_tit">
1 Depth
</a>
<ul class="nav_section">
<li>
<a>2 Depth</a>
</li>
</ul>
</th>
</nav>
</th:block>
</body>

View File

@ -1,37 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" <html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
<head> layout:decorate="~{layout/common.html}">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>관리자 시스템</title>
<script type="text/javascript" th:src="@{/js/jquery/jquery-3.7.1.min.js}"></script>
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<link rel="stylesheet" th:href="@{/css/style.css}"/>
<link rel="stylesheet" th:href="@{/css/sub.css}"/>
<script type="text/javascript" th:src="@{/js/motion.js}"></script>
<script type="text/javascript" th:src="@{/js/pagehelper.js}"></script>
<script type="text/javascript" th:src="@{/js/reqhelper.js}"></script>
<script type="text/javascript" th:src="@{/js/eventrouter.js}"></script>
<th:block th:replace="~{fragment/csrf/csrf :: applyCsrf}"></th:block>
</head>
<body> <body>
<header> <th:block layout:fragment="contents">
<th:block th:replace="~{main/gnb :: gnb}"></th:block> <th:block layout:fragment="innerContents">
</header>
<div class="wrapper flexbox">
<th:block th:replace="~{main/lnb :: lnb}"></th:block>
<div class="container flexitem">
<th:block layout:fragment="contents"/>
</div>
</div>
<th:block layout:fragment="script">
</th:block>
</th:block> </th:block>
</body> </body>
<th:block layout:fragment="script">
<script th:inline="javascript">
</script>
</th:block>
</html> </html>