diff --git a/README.md b/README.md index 8e645ed..bc5a7d1 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,14 @@ This document presents an example of dynamic permission menu loading based on jC **Technology Stack** -- **Backend:** Spring Boot 3.x + jCasbin + Spring Data JPA +- **Backend:** Spring Boot 2.x + jCasbin + Spring Data JPA - **Frontend:** Bootstrap + Thymeleaf **Startup Instructions** -1. Build the menu structure you need in the `casbin/policy.csv` file. Specifically, `g2` represents the relationship between menus. For example: `g2, submenu name, parent menu name`. -2. Once the configuration is complete, run the `main` method in `Application.java` located under `org/casbin/`. Access `http://localhost:8080/casbin/menu` for testing. -3. The access control model file for jCasbin is located at `casbin/model.conf`, and the policy file is at `casbin/policy.csv`. Modify them as needed based on your requirements. +1. Build the menu structure you need in the [`casbin/policy.csv`](https://github.com/jcasbin/jcasbin-menu-permission/blob/master/src/main/resources/casbin/policy.csv) file. Specifically, `g2` represents the relationship between menus. For example: `g2, submenu name, parent menu name`. +2. Once the configuration is complete, run the `main` method in [`Application.java`](https://github.com/jcasbin/jcasbin-menu-permission/blob/master/src/main/java/org/casbin/Application.java) located under `org/casbin/`. Access `http://localhost:8080/casbin/menu` for testing. +3. The access control model file for jCasbin is located at [`casbin/model.conf`](https://github.com/jcasbin/jcasbin-menu-permission/blob/master/src/main/resources/casbin/model.conf), and the policy file is at [`casbin/policy.csv`](https://github.com/jcasbin/jcasbin-menu-permission/blob/master/src/main/resources/casbin/policy.csv). Modify them as needed based on your requirements. ## Simple Examples @@ -24,14 +24,14 @@ This document presents an example of dynamic permission menu loading based on jC -**System user** +**Root user** -system_example +system_example **Admin user** -admin_example +admin_example **Normal user** -user_example \ No newline at end of file +user_example \ No newline at end of file diff --git a/examples/admin_example.png b/examples/admin_example.png index 619e48c..3d78cbd 100644 Binary files a/examples/admin_example.png and b/examples/admin_example.png differ diff --git a/examples/example.mp4 b/examples/example.mp4 index 1c4a6a8..04a839c 100644 Binary files a/examples/example.mp4 and b/examples/example.mp4 differ diff --git a/examples/root_example.png b/examples/root_example.png new file mode 100644 index 0000000..73edee8 Binary files /dev/null and b/examples/root_example.png differ diff --git a/examples/system_example.png b/examples/system_example.png deleted file mode 100644 index 24e98a4..0000000 Binary files a/examples/system_example.png and /dev/null differ diff --git a/examples/user_example.png b/examples/user_example.png index 505ac76..5b0bd30 100644 Binary files a/examples/user_example.png and b/examples/user_example.png differ diff --git a/src/main/java/org/casbin/config/WebMvcConfig.java b/src/main/java/org/casbin/config/WebMvcConfig.java index c14768b..0ee7bd3 100644 --- a/src/main/java/org/casbin/config/WebMvcConfig.java +++ b/src/main/java/org/casbin/config/WebMvcConfig.java @@ -51,15 +51,12 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons // Extract the menu name, assuming it is the last part of the URI. String menuName = requestURI.substring(requestURI.lastIndexOf('/') + 1); - if(menuService.checkUserAccess(username, "ALL_ROOT")) return true; - if (username == null) { // The user is not logged in response.sendRedirect(request.getContextPath() + "/denied"); return false; } - - if (!menuService.checkUserAccess(username, menuName)) { + if (!menuService.checkMenuAccess(username, menuName)) { // Users do not have access to the menu. response.sendRedirect(request.getContextPath() + "/denied"); return false; diff --git a/src/main/java/org/casbin/controller/TestMenuController.java b/src/main/java/org/casbin/controller/TestMenuController.java index 3230633..1b82a3a 100644 --- a/src/main/java/org/casbin/controller/TestMenuController.java +++ b/src/main/java/org/casbin/controller/TestMenuController.java @@ -19,6 +19,10 @@ @Controller public class TestMenuController { + @GetMapping(value = "menu/UserMenu") + public String UserMenu(){ + return "UserMenu/UserMenu"; + } @GetMapping(value = "menu/UserSubMenu_allow") public String UserSubMenu_allow(){ return "UserMenu/UserSubMenu_allow"; @@ -29,6 +33,16 @@ public String UserSubMenu_deny(){ return "UserMenu/UserSubMenu_deny"; } + @GetMapping(value = "menu/UserSubSubMenu") + public String UserSubMenu_sub(){ + return "UserMenu/UserSubSubMenu"; + } + + @GetMapping(value = "menu/AdminMenu") + public String AdminMenu(){ + return "AdminMenu/AdminMenu"; + } + @GetMapping(value = "menu/AdminSubMenu_allow") public String AdminSubMenu_allow(){ return "AdminMenu/AdminSubMenu_allow"; @@ -39,9 +53,9 @@ public String AdminSubMenu_deny(){ return "AdminMenu/AdminSubMenu_deny"; } - @GetMapping(value = "menu/SystemMenu") + @GetMapping(value = "menu/OtherMenu") public String SystemMenu(){ - return "SystemMenu/SystemMenu"; + return "OtherMenu/OtherMenu"; } } diff --git a/src/main/java/org/casbin/entity/MenuEntity.java b/src/main/java/org/casbin/entity/MenuEntity.java index f468630..8c4e7f0 100644 --- a/src/main/java/org/casbin/entity/MenuEntity.java +++ b/src/main/java/org/casbin/entity/MenuEntity.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Menu Entity @@ -46,4 +47,19 @@ public void addSubMenu(MenuEntity subMenu) { this.subMenus.add(subMenu); subMenu.setParents(this); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MenuEntity that = (MenuEntity) o; + return Objects.equals(name, that.name) && + Objects.equals(url, that.url); + } + + @Override + public int hashCode() { + return Objects.hash(name, url, subMenus); + } + } \ No newline at end of file diff --git a/src/main/java/org/casbin/service/AuthenticationService.java b/src/main/java/org/casbin/service/AuthenticationService.java index c69bb0b..aa16180 100644 --- a/src/main/java/org/casbin/service/AuthenticationService.java +++ b/src/main/java/org/casbin/service/AuthenticationService.java @@ -20,7 +20,7 @@ public class AuthenticationService { public boolean authenticate(String username, String password) { // Three valid username and password combinations are set here. - return ("system".equals(username) && "system".equals(password)) || + return ("root".equals(username) && "root".equals(password)) || ("admin".equals(username) && "admin".equals(password)) || ("user".equals(username) && "user".equals(password)); } diff --git a/src/main/java/org/casbin/service/MenuService.java b/src/main/java/org/casbin/service/MenuService.java index 149a1ab..b9dbdf0 100644 --- a/src/main/java/org/casbin/service/MenuService.java +++ b/src/main/java/org/casbin/service/MenuService.java @@ -28,7 +28,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * The MenuService class handles menu-related business logic, including permission control and menu filtering, using the jCasbin permission library. @@ -53,12 +52,7 @@ public List findAccessibleMenus(String username) { this.menuMap = new HashMap<>(); } this.accessMap = new HashMap<>(); - // Check if there is ALL_ROOT permission - if (checkUserAccess(username, "ALL_ROOT")) { - return menuMap.values().stream() - .filter(this::isTopLevelMenu) - .collect(Collectors.toList()); - } + List accessibleMenus = new ArrayList<>(); for (MenuEntity menu : menuMap.values()) { checkAndSetMenuAccess(menu, username); @@ -102,9 +96,31 @@ private boolean isTopLevelMenu(MenuEntity menu) { return menu.getParents() == null; } - public boolean checkUserAccess(String username, String menuName) { + private boolean checkUserAccess(String username, String menuName) { // Integrate Casbin to check user access to specific menus return enforcer.enforce(username, menuName, "read"); } -} + public boolean checkMenuAccess(String username, String menuName) { + List accessibleMenus = findAccessibleMenus(username); + for (MenuEntity menu : accessibleMenus) { + if (menuMatches(menu, menuName)) { + return true; + } + } + return false; + } + + private boolean menuMatches(MenuEntity menu, String menuName) { + if (menu.getName().equals(menuName)) { + return true; + } + for (MenuEntity subMenu : menu.getSubMenus()) { + if (menuMatches(subMenu, menuName)) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/org/casbin/util/MenuUtil.java b/src/main/java/org/casbin/util/MenuUtil.java index cc65c1d..6ddf3ca 100644 --- a/src/main/java/org/casbin/util/MenuUtil.java +++ b/src/main/java/org/casbin/util/MenuUtil.java @@ -36,17 +36,22 @@ public static Map parseCsvFile(String filePath) throws IOExc String childName = values[1].trim(); String parentName = values[2].trim(); - menuMap.putIfAbsent(childName, new MenuEntity(childName)); - if (!parentName.isEmpty()) { + // Check whether the name of the submenu is "(NULL)" + if (!"(NULL)".equals(childName)) { + menuMap.putIfAbsent(childName, new MenuEntity(childName)); + if (!parentName.isEmpty()) { + menuMap.putIfAbsent(parentName, new MenuEntity(parentName)); + MenuEntity childMenu = menuMap.get(childName); + MenuEntity parentMenu = menuMap.get(parentName); + parentMenu.addSubMenu(childMenu); + } + } else if (!parentName.isEmpty()) { + // Add only the parent menu, no submenu. menuMap.putIfAbsent(parentName, new MenuEntity(parentName)); - MenuEntity childMenu = menuMap.get(childName); - MenuEntity parentMenu = menuMap.get(parentName); - parentMenu.addSubMenu(childMenu); } } } } return menuMap; } -} - +} \ No newline at end of file diff --git a/src/main/resources/casbin/policy.csv b/src/main/resources/casbin/policy.csv index d65a395..0718e0a 100644 --- a/src/main/resources/casbin/policy.csv +++ b/src/main/resources/casbin/policy.csv @@ -1,18 +1,19 @@ -p, ROLE_SYSTEM, ALL_ROOT, read, allow +p, ROLE_ROOT, OtherMenu, read, allow +p, ROLE_ROOT, AdminMenu, read, allow +p, ROLE_ROOT, UserMenu, read, deny p, ROLE_ADMIN, UserMenu, read, allow p, ROLE_ADMIN, AdminMenu, read, allow p, ROLE_ADMIN, AdminSubMenu_deny, read, deny p, ROLE_USER, UserSubMenu_allow, read, allow g, user, ROLE_USER -g, admin, ROLE_USER g, admin, ROLE_ADMIN -g, system, ROLE_SYSTEM +g, root, ROLE_ROOT +g, ROLE_ADMIN, ROLE_USER g2, UserSubMenu_allow, UserMenu g2, UserSubMenu_deny, UserMenu +g2, UserSubSubMenu, UserSubMenu_allow g2, AdminSubMenu_allow, AdminMenu g2, AdminSubMenu_deny, AdminMenu -g2, , SystemMenu - - +g2, (NULL), OtherMenu \ No newline at end of file diff --git a/src/main/resources/templates/AdminMenu/AdminMenu.html b/src/main/resources/templates/AdminMenu/AdminMenu.html new file mode 100644 index 0000000..c986868 --- /dev/null +++ b/src/main/resources/templates/AdminMenu/AdminMenu.html @@ -0,0 +1,13 @@ + + + + + AdminMenu + + +
+ AdminMenu +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/SystemMenu/SystemMenu.html b/src/main/resources/templates/OtherMenu/OtherMenu.html similarity index 90% rename from src/main/resources/templates/SystemMenu/SystemMenu.html rename to src/main/resources/templates/OtherMenu/OtherMenu.html index 1d3f769..cddee05 100644 --- a/src/main/resources/templates/SystemMenu/SystemMenu.html +++ b/src/main/resources/templates/OtherMenu/OtherMenu.html @@ -6,7 +6,7 @@
- SystemMenu + OtherMenu
diff --git a/src/main/resources/templates/UserMenu/UserMenu.html b/src/main/resources/templates/UserMenu/UserMenu.html new file mode 100644 index 0000000..f06186b --- /dev/null +++ b/src/main/resources/templates/UserMenu/UserMenu.html @@ -0,0 +1,13 @@ + + + + + UserMenu + + +
+ UserMenu +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/UserMenu/UserSubSubMenu.html b/src/main/resources/templates/UserMenu/UserSubSubMenu.html new file mode 100644 index 0000000..1b580eb --- /dev/null +++ b/src/main/resources/templates/UserMenu/UserSubSubMenu.html @@ -0,0 +1,13 @@ + + + + + UserSubSubMenu + + +
+ UserSubSubMenu +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/main/menu.html b/src/main/resources/templates/main/menu.html index b00be01..960f8b6 100644 --- a/src/main/resources/templates/main/menu.html +++ b/src/main/resources/templates/main/menu.html @@ -21,35 +21,60 @@ .card-body p { padding-left: 20px; } + .submenu-background { + background-color: #f8f9fa; + padding: 10px; + border-radius: 5px; + margin-top: 5px; + } + -
-