在現代應用開發中,權限控制是一個至關重要的部分。復雜的業務場景往往要求靈活且細粒度的權限控制,而 Spring Expression Language (SpEL) 為我們提供了強大的表達式支持,使得權限控制的實現變得更加簡便和直觀。本文將詳細講解如何在 Spring Boot 3.3 中使用 SpEL 實現復雜權限控制,并結合代碼示例進行深入探討。
運行效果:
圖片
圖片
圖片
圖片
若想獲取項目完整代碼以及其他文章的項目源碼,且在代碼編寫時遇到問題需要咨詢交流,歡迎加入下方的知識星球。
首先,我們需要設置項目的基本環境,包括 Maven 依賴和 Spring Boot 的配置文件。
<?xml versinotallow="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.icoderoad</groupId> <artifactId>spelpermissions</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spelpermissions</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Thymeleaf 模板引擎 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
server: port: 8080spring: thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html security: user: name: admin password: admin
SpEL 提供了靈活的表達式支持,可以直接在注解中使用,結合 Spring Security 的 @PreAuthorize 注解,可以非常方便地實現基于業務邏輯的權限控制。
定義權限模型
首先,我們定義一個簡單的權限模型,假設我們有用戶(User)和資源(Resource),并且只有擁有特定權限的用戶可以訪問某些資源。
package com.icoderoad.spelpermissions.entity;import java.util.List;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private String username; private List<String> roles; }package com.icoderoad.spelpermissions.entity;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@AllArgsConstructor@NoArgsConstructorpublic class Resource { private String resourceName; private String requiredRole; }
接下來,我們在服務層編寫權限校驗邏輯,并使用 SpEL 在控制層進行權限控制。
package com.icoderoad.spelpermissions.service;import java.util.ArrayList;import java.util.List;import org.springframework.stereotype.Service;import com.icoderoad.spelpermissions.entity.Resource;import com.icoderoad.spelpermissions.entity.User;@Servicepublic class ResourceService { // 模擬資源列表(在實際應用中,這些資源可能會從數據庫中獲取) private final List<Resource> resources = new ArrayList<>(); public ResourceService() { // 初始化一些示例資源 resources.add(new Resource("Resource1", "ROLE_USER")); resources.add(new Resource("Resource2", "ROLE_ADMIN")); resources.add(new Resource("Resource3", "ROLE_USER")); resources.add(new Resource("Resource4", "ROLE_ADMIN")); } // 獲取所有資源 public List<Resource> getAllResources() { return resources; } // 獲取單個資源 public Resource getResource(String resourceName) { return resources.stream() .filter(resource -> resource.getResourceName().equals(resourceName)) .findFirst() .orElse(null); } // 檢查用戶是否有權限訪問某個資源 public boolean hasPermission(User user, Resource resource) { return user.getRoles().contains(resource.getRequiredRole()); }}
為了獲取用戶的詳細信息,我們需要實現 UserService 類,負責處理用戶登錄、查找等業務邏輯。
package com.icoderoad.spelpermissions.service;import java.util.Arrays;import org.springframework.stereotype.Service;import com.icoderoad.spelpermissions.entity.User;@Servicepublic class UserService { // 模擬從數據庫獲取用戶信息 public User findUserByUsername(String username) { // 假設我們有兩個用戶:admin 和 user if ("admin".equals(username)) { return new User(username, Arrays.asList("ROLE_ADMIN", "ROLE_USER")); } else if ("user".equals(username)) { return new User(username, Arrays.asList("ROLE_USER")); } else { return null; // 未找到用戶 } }}
創建一個自定義的 AccessDeniedHandler 類,用于處理訪問拒絕的情況。
package com.icoderoad.spelpermissions.handler;import java.io.IOException;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;import jakarta.servlet.RequestDispatcher;import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;@Componentpublic class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { // 設置響應狀態碼 response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 轉發到 accessDenied.html 頁面 RequestDispatcher dispatcher = request.getRequestDispatcher("/accessDenied"); dispatcher.forward(request, response); }}
為了處理用戶登錄和權限驗證,我們需要配置 Spring Security。
package com.icoderoad.spelpermissions.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.EnableWebSecurity;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.provisioning.InMemoryUserDetailsManager;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.access.AccessDeniedHandler;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true) // 啟用方法級權限控制public class SecurityConfig { @Autowired private AccessDeniedHandler customAccessDeniedHandler; // 自動注入現有的 CustomAccessDeniedHandler @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests() .requestMatchers("/res/**").authenticated() // 保護資源頁面 .anyRequest().permitAll() // 允許其他所有請求 .and() .formLogin() .loginPage("/res/login") // 指定登錄頁面 .defaultSuccessUrl("/res") // 登錄成功后跳轉的頁面 .permitAll() .and() .logout() .permitAll() .and() .exceptionHandling() .accessDeniedHandler(customAccessDeniedHandler); // 配置自定義的 AccessDeniedHandler return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); UserDetails admin = User.withDefaultPasswordEncoder() .username("admin") .password("password") .roles("ADMIN", "USER") .build(); return new InMemoryUserDetailsManager(user, admin); } }
這個配置類的主要目的是設置基本的安全配置,包括路徑保護、表單登錄、登出處理以及自定義的權限拒絕處理。它使用了 Spring Security 提供的標準功能,并通過 @EnableGlobalMethodSecurity 啟用了方法級別的權限控制。
我們首先實現用戶登錄、獲取資源列表和獲取用戶權限的控制器類。
package com.icoderoad.spelpermissions.controller;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.core.annotation.AuthenticationPrincipal;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import com.icoderoad.spelpermissions.entity.Resource;import com.icoderoad.spelpermissions.entity.User;import com.icoderoad.spelpermissions.service.ResourceService;import com.icoderoad.spelpermissions.service.UserService;@Controller@RequestMapping("/res")public class ResourceController { @Autowired private ResourceService resourceService; @Autowired private UserService userService; // 獲取資源列表并渲染到頁面 @GetMapping public String listResources(Model model, @AuthenticationPrincipal UserDetails userDetails) { // 獲取當前登錄用戶 User user = userService.findUserByUsername(userDetails.getUsername()); // 查詢所有資源 List<Resource> resources = resourceService.getAllResources(); model.addAttribute("resources", resources); model.addAttribute("username", user.getUsername()); return "resources"; // 返回 Thymeleaf 模板頁面 } // 獲取單個資源,并使用 @PreAuthorize 注解進行權限驗證 @GetMapping("/{resourceName}") @PreAuthorize("@resourceService.hasPermission(authentication, #resourceName, 'VIEW')") public String getResource(@PathVariable String resourceName, Model model) { Resource resource = resourceService.getResource(resourceName); model.addAttribute("resource", resource); return "resourceDetail"; // 返回資源詳情頁面 } // 用戶登錄頁面 @GetMapping("/login") public String login() { return "login"; // 返回登錄頁面 }}
@PreAuthorize("@resourceService.hasPermission(authentication, #resourceName, 'VIEW')") 是 Spring Security 提供的一個注解,用于方法級別的權限控制。它利用了 Spring Expression Language (SpEL) 來執行復雜的權限檢查。以下是這個注解的詳細說明:
package com.icoderoad.spelpermissions.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;@Controllerpublic class IndexController { @GetMapping("/") public String index() { return "login"; } @GetMapping("/accessDenied") public String accessDenied() { return "accessDenied"; // 返回 Thymeleaf 模板名 } }
我們為用戶登錄創建一個簡單的登錄頁面。
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>登錄</title> <link rel="stylesheet" ></head><body><div class="container"> <h2>用戶登錄</h2> <form th:action="@{/login}" method="post"> <div class="form-group"> <label for="username">用戶名</label> <input type="text" class="form-control" id="username" name="username" required> </div> <div class="form-group"> <label for="password">密碼</label> <input type="password" class="form-control" id="password" name="password" required> </div> <button type="submit" class="btn btn-primary">登錄</button> </form></div></body></html>
當用戶成功登錄后,會被重定向到資源列表頁面。
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>資源管理</title> <link rel="stylesheet" ></head><body><div class="container"> <h1>資源列表</h1> <p>歡迎,<span th:text="${username}"></span>!</p> <ul class="list-group"> <li class="list-group-item" th:each="resource : ${resources}"> <span th:text="${resource.resourceName}"></span> <a th:href="@{/res/{resourceName}(resourceName=${resource.resourceName})}" class="btn btn-primary"> 查看 </a> </li> </ul></div></body></html>
當用戶點擊某個資源后,如果用戶有權限訪問,則會展示該資源的詳細信息。
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>資源詳情</title> <link rel="stylesheet" ></head><body><div class="container"> <h1>資源詳情</h1> <p>資源名稱: <span th:text="${resource.resourceName}"></span></p> <p>需要權限: <span th:text="${resource.requiredRole}"></span></p> <!-- 返回資源列表按鈕 --> <a href="/res" class="btn btn-secondary">返回資源列表</a></div></body></html>
如果用戶沒有訪問權限,將顯示一個權限拒絕頁面。
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>訪問被拒絕</title> <link rel="stylesheet" ></head><body><div class="container"> <h1>訪問被拒絕</h1> <p>抱歉,您沒有權限訪問此資源。</p> <!-- 返回資源列表按鈕 --> <a href="/res" class="btn btn-secondary">返回資源列表</a></div></body></html>
使用 Thymeleaf 結合 Bootstrap 實現一個簡單的前端頁面,用于展示資源列表和用戶權限。
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>Resource Management</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"></head><body><div class="container"> <h1>資源列表</h1> <ul class="list-group"> <li class="list-group-item" th:each="resource : ${resources}"> <span th:text="${resource.resourceName}"></span> <button class="btn btn-primary" th:onclick="'location.href=/'/api/resources/' + ${resource.resourceName} + '/'" > 查看 </button> </li> </ul></div><script th:src="@{/js/bootstrap.min.js}"></script></body></html>
在這個示例中,用戶可以點擊按鈕查看特定資源的詳細信息。如果用戶沒有相應的權限,則會返回一個錯誤頁面。
通過本文的介紹,我們學習了如何使用 Spring Boot 3.3 和 SpEL 實現復雜的權限控制。通過 SpEL 強大的表達式支持,我們可以在業務邏輯中靈活地定義和執行權限校驗,使得復雜的權限控制變得更加簡潔和直觀。同時,我們也探討了如何在前后端集成實現權限管理的功能。
使用 SpEL 實現權限控制是一種非常強大的方式,它不僅支持基本的權限判斷,還可以通過自定義表達式滿足各種復雜的業務需求。在實際應用中,可以根據不同的場景靈活應用這一技術,以提高系統的安全性和靈活性。
本文鏈接:http://www.tebozhan.com/showinfo-26-112753-0.html使用 SpringBoot3.3 + SpEL 讓復雜權限控制變得很簡單!
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: Go并發編程詳解鎖、WaitGroup、Channel
下一篇: 類加載機制的源碼解讀