《Spring Security 簡易速速上手小冊》第4章 授權與角色管理(2024 最新版)

在這里插入圖片描述

文章目錄

  • 4.1 理解授權
    • 4.1.1 基礎知識詳解
      • 授權的核心
      • 授權策略
      • 方法級安全
      • 動態權限檢查
    • 4.1.2 主要案例:基于角色的頁面訪問控制
      • 案例 Demo
    • 4.1.3 拓展案例 1:自定義投票策略
      • 案例 Demo
      • 測試自定義投票策略
    • 4.1.4 拓展案例 2:使用方法級安全進行細粒度控制
      • 案例 Demo
      • 動態權限驗證
  • 4.2 角色與權限的配置
    • 4.2.1 基礎知識詳解
    • 4.2.2 主要案例:配置內存中的用戶、角色和權限
      • 案例 Demo
      • 動態角色和權限的數據庫配置
      • 基于表達式的訪問控制
    • 4.2.3 拓展案例 1:動態角色和權限的數據庫配置
      • 案例 Demo
    • 4.2.4 拓展案例 2:使用方法級安全進行角色和權限控制
      • 案例 Demo
      • 使用自定義方法進行安全檢查
  • 4.3 方法級安全性
    • 4.3.1 基礎知識詳解
      • 方法級安全的關鍵概念
      • 方法級安全的配置
      • 使用表達式進行權限控制
    • 4.3.2 主要案例:使用 `@PreAuthorize` 控制方法訪問
      • 案例 Demo
      • 基于參數的動態權限驗證
      • 結合自定義方法進行復雜權限驗證
    • 4.3.3 拓展案例 1:使用自定義權限驗證
      • 案例 Demo
      • 結合數據庫進行權限驗證
    • 4.3.4 拓展案例 2:基于返回值的后置授權
      • 案例 Demo
      • 自定義后置處理邏輯

4.1 理解授權

歡迎進入授權的迷人世界,這里我們將學習如何在 Spring Security 中精細控制誰可以做什么。想象一下,我們的應用是一個大型的音樂節,授權就是確保每個人都在正確的舞臺前搖擺!

4.1.1 基礎知識詳解

授權的核心

授權,簡而言之,就是確定一個已認證的用戶(即已經通過登錄過程的用戶)是否擁有執行某項操作的權限。這涉及到兩個層面的判斷:角色(Role)和權限(Authority)。

  • 角色 (Role): 角色通常表示用戶的分組,每個角色擁有一組權限。例如,"管理員"角色可能有權限修改所有用戶的數據,而"普通用戶"角色可能只能修改自己的數據。
  • 權限 (Authority): 權限是更細粒度的訪問控制,它定義了用戶可以執行的具體操作,如"讀取文件"、"寫入數據"等。

授權策略

Spring Security 提供了多種授權策略,允許開發者根據具體需求來定制訪問控制規則:

  • 基于 URL 的授權: 通過配置特定的 URL 模式與所需的角色或權限相關聯,來控制對這些 URL 的訪問。
  • 方法級的授權: 使用注解(如 @PreAuthorize@Secured)直接在業務方法上定義訪問控制規則,為細粒度的控制提供了便利。

方法級安全

Spring Security 的方法級安全功能是通過 AOP(面向切面編程)實現的,它允許你在不修改業務邏輯代碼的情況下,添加額外的安全檢查。這種方式非常靈活,可以應用于任何 Spring 管理的 Bean 的方法上。

  • 啟用方法級安全: 通過在配置類上使用 @EnableGlobalMethodSecurity 注解并設置相應的屬性(如 prePostEnabledsecuredEnabled)來啟用。
  • 安全注解: @PreAuthorize@PostAuthorize@Secured@RolesAllowed 等注解提供了豐富的選項,用于定義方法的訪問控制規則。

動態權限檢查

在某些情況下,靜態定義的角色或權限可能不足以滿足復雜的業務需求。Spring Security 支持動態權限檢查,允許在運行時根據具體情況決定是否授權。

  • 表達式驅動的訪問控制: @PreAuthorize@PostAuthorize 注解支持 SpEL(Spring 表達式語言),使得可以在表達式中引用方法參數、調用方法等,實現動態的權限判斷。

通過精細地配置和使用 Spring Security 的授權機制,開發者可以為應用構建起一道堅固的安全防線,確保只有擁有適當權限的用戶才能訪問敏感資源或執行特定操作。這不僅提高了應用的安全性,也為維護良好的用戶體驗提供了支持。

4.1.2 主要案例:基于角色的頁面訪問控制

在這個案例中,我們將通過一個實際的示例來演示如何在 Spring Security 中實現基于角色的頁面訪問控制。這將確保只有具備特定角色的用戶能訪問相應的頁面或執行特定的操作,從而提高應用的安全性。

案例 Demo

假設我們的應用有三個主要區域:首頁(公開訪問)、用戶儀表板(僅限登錄用戶訪問)、管理員控制臺(僅限管理員訪問)。

步驟 1: 定義角色

在這個場景中,我們定義兩個角色:ROLE_USERROLE_ADMIN

步驟 2: 配置 Spring Security

創建一個名為 WebSecurityConfig 的安全配置類,繼承 WebSecurityConfigurerAdapter 并重寫相應的方法來配置安全策略。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable() // 示例中禁用CSRF保護,實際應用中應根據需要啟用.authorizeRequests().antMatchers("/").permitAll() // 首頁允許所有人訪問.antMatchers("/user/**").hasRole("USER") // 用戶儀表板僅限擁有 ROLE_USER 的用戶訪問.antMatchers("/admin/**").hasRole("ADMIN") // 管理員控制臺僅限擁有 ROLE_ADMIN 的用戶訪問.anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll() // 提供自定義登錄頁面.and().logout().permitAll(); // 允許所有用戶登出}@Bean@Overridepublic UserDetailsService userDetailsService() {// 在內存中配置一些用戶InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(User.withUsername("user").password(passwordEncoder().encode("password")).roles("USER").build());manager.createUser(User.withUsername("admin").password(passwordEncoder().encode("password")).roles("ADMIN").build());return manager;}@Beanpublic PasswordEncoder passwordEncoder() {// 使用 BCryptPasswordEncoder 對密碼進行編碼return new BCryptPasswordEncoder();}
}

步驟 3: 測試訪問控制

啟動應用,并嘗試訪問不同區域:

  • 訪問 / 應該對所有人開放。
  • 訪問 /user/dashboard 時,如果未登錄或不具備 ROLE_USER 角色,應重定向到登錄頁面。
  • 訪問 /admin/control 時,僅當用戶擁有 ROLE_ADMIN 角色時才能訪問,否則應拒絕訪問。

4.1.3 拓展案例 1:自定義投票策略

在更復雜的安全需求中,簡單的角色檢查可能不足以滿足需求,這時可以通過自定義投票策略來進行細粒度的控制。Spring Security 提供了一個靈活的訪問決策管理器(AccessDecisionManager),它可以根據多個投票器(AccessDecisionVoter)的投票結果來決定是否授予訪問權限。

案例 Demo

假設我們有一個需求,只允許在特定時間段內訪問某些資源。我們可以通過實現一個自定義的投票器來實現這一需求。

步驟 1: 創建自定義投票器

首先,我們創建一個自定義投票器 TimeBasedAccessDecisionVoter,它將根據當前時間和預定義的時間段來決定是否投票贊成訪問。

import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;import java.time.LocalTime;
import java.util.Collection;public class TimeBasedAccessDecisionVoter implements AccessDecisionVoter<FilterInvocation> {private final LocalTime allowedStartTime;private final LocalTime allowedEndTime;public TimeBasedAccessDecisionVoter(String startTime, String endTime) {this.allowedStartTime = LocalTime.parse(startTime);this.allowedEndTime = LocalTime.parse(endTime);}@Overridepublic int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {LocalTime now = LocalTime.now();if (now.isAfter(allowedStartTime) && now.isBefore(allowedEndTime)) {return ACCESS_GRANTED;} else {return ACCESS_DENIED;}}@Overridepublic boolean supports(ConfigAttribute attribute) {return true;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}
}

步驟 2: 配置訪問決策管理器

接下來,在安全配置類中配置自定義的訪問決策管理器,將我們的自定義投票器添加到其中。

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.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.ConsensusBased;
import org.springframework.security.access.vote.UnanimousBased;
import java.util.List;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().accessDecisionManager(accessDecisionManager());}public AccessDecisionManager accessDecisionManager() {List<AccessDecisionVoter<?>> decisionVoters = List.of(new TimeBasedAccessDecisionVoter("09:00", "17:00"));return new AffirmativeBased(decisionVoters); // 使用肯定性決策策略}
}

這里,我們使用 AffirmativeBased 訪問決策管理器,并添加了我們的 TimeBasedAccessDecisionVoter 作為投票器。這意味著,如果自定義投票器投票贊成,則允許訪問;否則,拒絕訪問。

測試自定義投票策略

啟動應用并嘗試在不同時間訪問受保護的資源。你會發現,只有在定義的時間段內(例如上午 9:00 至下午 5:00),請求才會被允許;其他時間則會被拒絕。

通過實現這個案例,你就學會了如何使用 Spring Security 的高級特性來滿足特定的安全需求,提供了一種靈活而強大的方法來根據應用的具體需求定制訪問控制策略。

4.1.4 拓展案例 2:使用方法級安全進行細粒度控制

方法級安全是 Spring Security 提供的一個強大功能,它允許開發者在單個方法上應用安全注解,從而實現細粒度的訪問控制。這種方式非常適用于那些需要根據不同業務邏輯對訪問權限進行精細管理的應用。

案例 Demo

假設我們正在開發一個博客系統,其中包含一個文章服務,我們希望只有文章的作者或者管理員才能編輯文章。

步驟 1: 啟用方法級安全

首先,在你的安全配置類上添加 @EnableGlobalMethodSecurity 注解,并設置 prePostEnabledtrue,以啟用方法級安全。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {// 安全配置內容
}

步驟 2: 創建文章服務

接下來,創建一個文章服務 ArticleService,并在編輯文章的方法上使用 @PreAuthorize 注解來定義訪問控制規則。

@Service
public class ArticleService {@PreAuthorize("hasRole('ADMIN') or #article.author.username == authentication.principal.username")public void editArticle(Article article) {// 編輯文章的邏輯}
}

在這個例子中,#article.author.username 引用了方法參數 article 中作者用戶名的屬性,而 authentication.principal.username 引用了當前認證用戶的用戶名。這條規則的含義是:“只有當當前用戶是管理員,或者文章的作者時,才允許編輯文章。”

動態權限驗證

假設系統中還有一個需求,即用戶可以對文章進行評論,但我們希望在用戶被禁言時禁止其評論。

步驟 1: 實現用戶服務

假設我們有一個 UserService,它提供了方法來檢查用戶是否被禁言。

@Service
public class UserService {public boolean isUserBanned(String username) {// 檢查用戶是否被禁言的邏輯return true; // 假設返回結果}
}

步驟 2: 使用 SpEL 進行動態驗證

在評論服務的添加評論方法上使用 @PreAuthorize,結合 SpEL 表達式和自定義方法來實現動態權限驗證。

@Service
public class CommentService {@Autowiredprivate UserService userService;@PreAuthorize("!@userService.isUserBanned(authentication.principal.username)")public void addComment(Comment comment) {// 添加評論的邏輯}
}

這里,@userService.isUserBanned(authentication.principal.username) 調用了 UserServiceisUserBanned 方法來檢查當前認證用戶是否被禁言,從而動態決定是否允許用戶添加評論。

通過在方法上應用安全注解,你可以根據業務邏輯的需要實現復雜的訪問控制策略。這種靈活性是 Spring Security 方法級安全特別強大的地方,使得開發者可以輕松應對各種復雜的安全需求。

4.2 角色與權限的配置

在 Spring Security 中,角色和權限的概念是實現細粒度訪問控制的關鍵。通過合理配置角色和權限,你可以精確地控制誰可以訪問應用中的哪些資源。

4.2.1 基礎知識詳解

  1. 角色 (Roles):

    • 角色通常用來表示用戶的分組,它是一種高級別的權限集合,可以簡化權限管理。
    • 在 Spring Security 中,角色通常以 ROLE_ 前綴命名,例如 ROLE_ADMINROLE_USER 等。
    • 角色使得可以通過一次檢查來控制對多個資源的訪問權限。
  2. 權限 (Authorities):

    • 權限代表對特定行為的訪問控制,它是更細粒度的訪問權限。
    • 權限不一定以 ROLE_ 前綴命名,可以是任何字符串,如 READ_PRIVILEGESWRITE_PRIVILEGES 等。
    • 權限允許對用戶可以執行的操作進行精確控制。
  3. 角色與權限的關系:

    • 一個角色可以包含多個權限,而一個用戶可以擁有多個角色。
    • 角色和權限共同工作,提供了一種靈活而強大的方式來定義安全策略,確保只有合適的用戶能訪問應用中的資源。
  4. 配置方式:

    • 內存中的配置:適用于簡單應用或開發測試階段。通過 AuthenticationManagerBuilder 直接在安全配置中指定角色和用戶。
    • 數據庫配置:適用于需要動態管理用戶、角色和權限的生產環境。通常結合 UserDetailsServiceJdbcUserDetailsManager 或自定義實現來完成。
  5. 方法級安全:

    • Spring Security 支持在方法級別進行安全控制,允許在業務邏輯方法上直接聲明訪問控制規則。
    • 使用 @PreAuthorize@PostAuthorize@Secured@RolesAllowed 等注解,可以基于表達式或直接基于角色進行方法訪問控制。
    • 方法級安全提供了比URL級安全更細粒度的控制,特別適用于復雜業務邏輯的安全需求。

通過掌握角色和權限的基礎知識以及它們在 Spring Security 中的應用方式,開發者可以為應用構建起一套既強大又靈活的安全策略,有效地保護應用資源,確保只有授權用戶才能訪問敏感信息或執行關鍵操作。

4.2.2 主要案例:配置內存中的用戶、角色和權限

在這個案例中,我們將通過一個實際的示例來演示如何在 Spring Security 中使用內存配置來定義用戶、角色和權限。這種方式非常適合于簡單的應用或是在開發和測試階段快速搭建安全框架。

案例 Demo

假設我們正在開發一個小型的博客系統,其中包含三類用戶:管理員(ADMIN)、作者(AUTHOR)和訪客(VISITOR)。每種用戶類型都有不同的權限集合。

步驟 1: 創建安全配置類

首先,創建一個名為 WebSecurityConfig 的安全配置類,繼承 WebSecurityConfigurerAdapter 并重寫 configure(AuthenticationManagerBuilder auth) 方法來配置用戶、角色和權限。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance()).withUser("admin").password("adminPass").roles("ADMIN").and().withUser("author").password("authorPass").authorities("ROLE_AUTHOR", "CREATE_POST", "EDIT_POST").and().withUser("visitor").password("visitorPass").roles("VISITOR");}
}

在這個配置中,我們使用了 NoOpPasswordEncoder 為了簡化示例,實際應用中推薦使用 BCryptPasswordEncoder

步驟 2: 定義訪問控制規則

繼續在 WebSecurityConfig 中,重寫 configure(HttpSecurity http) 方法來定義各種URL路徑的訪問控制規則。

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll() // 首頁允許所有人訪問.antMatchers("/admin/**").hasRole("ADMIN") // 管理員界面僅管理員可訪問.antMatchers("/blog/new", "/blog/edit/*").hasAuthority("CREATE_POST") // 博客創建和編輯僅作者可訪問.anyRequest().authenticated() // 其他所有請求都需要認證.and().formLogin() // 啟用默認登錄頁面.and().logout(); // 啟用默認登出功能
}

動態角色和權限的數據庫配置

在生產環境中,通常需要從數據庫動態加載用戶、角色和權限信息,以便于管理。你可以通過實現 UserDetailsService 接口并使用 JPA 或 MyBatis 等ORM框架從數據庫加載用戶信息。

基于表達式的訪問控制

Spring Security 支持基于表達式的訪問控制(SpEL),這為定義復雜的訪問控制邏輯提供了強大的靈活性。

http.authorizeRequests().antMatchers("/blog/**").access("hasRole('ROLE_AUTHOR') or hasRole('ROLE_ADMIN')").anyRequest().authenticated();

通過實現這個案例,你可以看到如何在 Spring Security 中配置內存中的用戶、角色和權限。這種方法雖然簡單,但能夠為你的應用提供一個快速而安全的認證和授權機制。在實際生產環境中,你可能會需要使用數據庫來存儲這些信息,以支持動態的權限管理和更細粒度的訪問控制。

4.2.3 拓展案例 1:動態角色和權限的數據庫配置

在實際生產環境中,角色和權限的配置通常需要更加靈活和動態,以便于管理和擴展。通過數據庫配置用戶、角色和權限信息,可以實現這一目標。以下是如何在 Spring Security 中實現動態角色和權限配置的示例。

案例 Demo

假設我們有一個博客系統,其中用戶信息、角色和權限都存儲在數據庫中。

步驟 1: 創建數據庫模型

首先,我們需要在數據庫中創建用戶(User)、角色(Role)和權限(Permission)的模型。以下是一個簡化的模型示例:

CREATE TABLE `users` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY,`username` VARCHAR(50) NOT NULL,`password` VARCHAR(100) NOT NULL,`enabled` BOOLEAN NOT NULL
);CREATE TABLE `roles` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY,`name` VARCHAR(50) NOT NULL
);CREATE TABLE `user_roles` (`user_id` BIGINT NOT NULL,`role_id` BIGINT NOT NULL,PRIMARY KEY (`user_id`, `role_id`),FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`)
);CREATE TABLE `permissions` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY,`name` VARCHAR(50) NOT NULL
);CREATE TABLE `role_permissions` (`role_id` BIGINT NOT NULL,`permission_id` BIGINT NOT NULL,PRIMARY KEY (`role_id`, `permission_id`),FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`),FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`)
);

步驟 2: 實現 UserDetailsService

接下來,我們需要實現 UserDetailsService 接口,以從數據庫加載用戶信息及其角色和權限。

@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));Set<GrantedAuthority> authorities = new HashSet<>();user.getRoles().forEach(role -> {authorities.add(new SimpleGrantedAuthority(role.getName()));role.getPermissions().forEach(permission -> authorities.add(new SimpleGrantedAuthority(permission.getName())));});return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);}
}

步驟 3: 配置 WebSecurityConfig

在安全配置類中使用自定義的 UserDetailsService 和合適的密碼編碼器。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomUserDetailsService customUserDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 其他配置...
}

步驟 4: 測試動態角色和權限

啟動應用,并測試不同角色的用戶訪問受限資源,驗證動態角色和權限配置是否生效。

通過實現這個案例,你就可以在 Spring Security 中實現動態的角色和權限管理。這種方式為應用提供了更大的靈活性和擴展性,使得管理用戶訪問權限變得更加簡單和高效。

4.2.4 拓展案例 2:使用方法級安全進行角色和權限控制

當你需要在更細粒度的級別上控制訪問權限時,方法級安全是一個強大的工具。Spring Security 通過注解提供了這種控制能力,允許你直接在服務或控制器層的方法上定義訪問策略。

案例 Demo

假設我們的博客系統需要對不同的操作定義詳細的訪問控制策略,例如只允許文章的作者或管理員編輯文章,而訪客只能閱讀文章。

步驟 1: 啟用方法級安全

首先,確保你的安全配置類啟用了方法級安全。通過在配置類上添加 @EnableGlobalMethodSecurity 注解,并設置 prePostEnabledtrue

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {// 安全配置內容
}

步驟 2: 定義角色和權限

在這個示例中,我們假設已經通過某種方式(例如,動態角色和權限的數據庫配置案例)定義了用戶的角色和權限。

步驟 3: 應用方法級安全注解

在服務類中,我們可以使用 @PreAuthorize@PostAuthorize 注解來定義訪問控制策略。

@Service
public class BlogService {// 只有作者或管理員可以編輯文章@PreAuthorize("hasRole('ROLE_ADMIN') or (hasRole('ROLE_AUTHOR') and #article.author == authentication.name)")public void editArticle(Article article) {// 編輯文章的邏輯}// 所有人都可以閱讀文章@PreAuthorize("permitAll()")public Article readArticle(Long articleId) {// 讀取文章的邏輯return new Article();}
}

editArticle 方法上的 @PreAuthorize 注解確保只有文章的作者或者擁有管理員角色的用戶可以編輯文章。這里使用了 Spring 表達式語言(SpEL)來訪問方法參數 article 和當前認證用戶的名稱。

步驟 4: 測試方法級安全控制

啟動應用,并嘗試以不同角色的用戶執行上述定義的操作。你應該會看到只有符合條件的用戶才能成功執行 editArticle 方法,而 readArticle 方法對所有人開放。

使用自定義方法進行安全檢查

有時候,你可能需要根據復雜的業務邏輯進行安全檢查,這時可以定義自定義方法來輔助安全注解。

步驟 1: 創建一個輔助服務

@Service
public class SecurityService {public boolean isArticleOwner(Long articleId, String username) {// 檢查指定的用戶名是否為文章的所有者// 此處省略具體實現return true;}
}

步驟 2: 在方法級安全注解中調用自定義方法

@Service
public class BlogService {@Autowiredprivate SecurityService securityService;@PreAuthorize("@securityService.isArticleOwner(#articleId, authentication.name)")public void editArticle(Long articleId) {// 編輯文章的邏輯}
}

在這個例子中,我們通過調用 SecurityService 中的 isArticleOwner 方法來動態地檢查當前用戶是否有權限編輯文章。

通過使用方法級安全控制,你可以在 Spring Security 中實現復雜和細粒度的訪問控制策略,確保應用的安全性同時滿足業務需求。

4.3 方法級安全性

在 Spring Security 中,方法級安全性是一個強大的特性,允許開發者在單個方法上應用安全策略。這種方式提供了比URL級別更細粒度的控制,非常適合那些需要根據不同業務邏輯或數據敏感性來調整訪問權限的場景。

4.3.1 基礎知識詳解

方法級安全的關鍵概念

  1. @EnableGlobalMethodSecurity: 這個注解用于在配置類上啟用方法級安全設置。它允許開發者啟用和配置不同類型的方法級安全注解支持,包括 prePostEnabledsecuredEnabledjsr250Enabled 三個參數。

  2. @PreAuthorize 和 @PostAuthorize: 這兩個注解允許在方法執行之前或之后進行權限驗證。@PreAuthorize 可以根據表達式的評估結果來決定是否執行方法,而 @PostAuthorize 允許根據方法的執行結果來進行安全檢查。

  3. @Secured: 這個注解用于指定一個方法只能被擁有特定角色的用戶訪問。它是一種相對簡單直接的訪問控制形式,不支持SpEL表達式。

  4. @RolesAllowed: 類似于 @Secured,這個注解來源于JSR-250標準,用于指定執行方法所需的角色列表。

方法級安全的配置

啟用方法級安全需要在配置類中使用 @EnableGlobalMethodSecurity 注解,并根據需求設置其參數。

  • prePostEnabled: 設置為 true 以啟用 @PreAuthorize@PostAuthorize 注解。
  • securedEnabled: 設置為 true 以啟用 @Secured 注解。
  • jsr250Enabled: 設置為 true 以啟用 @RolesAllowed 注解。

使用表達式進行權限控制

Spring Security 的方法級安全支持使用Spring表達式語言(SpEL)進行復雜的權限控制。這種方式提供了極高的靈活性,允許開發者在注解中引用bean、方法參數、認證對象等,以實現精細化的訪問控制策略。

例如,使用 @PreAuthorize 來檢查認證用戶的角色或調用方法參數的屬性:

@PreAuthorize("hasRole('ROLE_ADMIN') or #model.createdBy == authentication.name")
public void performSensitiveOperation(Model model) {// 方法體...
}

這段代碼表示只有管理員角色的用戶或者模型的創建者本人才能執行該操作。

通過掌握這些基礎知識,開發者可以利用Spring Security在應用中實現高度定制化的安全策略,確保敏感操作只能由合適的角色或用戶執行。這不僅加強了應用的安全性,也提供了一種靈活且強大的方式來滿足復雜的業務需求。

4.3.2 主要案例:使用 @PreAuthorize 控制方法訪問

在這個案例中,我們將演示如何使用 @PreAuthorize 注解來實現方法級別的訪問控制。假設我們正在開發一個在線圖書館系統,其中包含一個功能允許用戶借閱圖書。我們希望只有擁有 ROLE_USER 角色并且賬戶狀態為激活狀態的用戶才能借閱圖書。

案例 Demo

步驟 1: 啟用方法級安全

首先,在安全配置類中啟用方法級安全,允許使用 @PreAuthorize 注解。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {// 安全配置內容
}

步驟 2: 創建用戶服務

接下來,假設我們有一個用戶服務,其中包含用戶的角色和狀態信息。為了簡化,我們在這里不展示用戶服務的實現細節。

步驟 3: 定義借閱圖書的方法

在圖書服務類中,我們定義一個方法 borrowBook,使用 @PreAuthorize 注解來限制只有角色為 ROLE_USER 且賬戶狀態為激活的用戶才能執行該方法。

@Service
public class BookService {@PreAuthorize("hasRole('ROLE_USER') and @userService.isAccountActive(authentication.principal.username)")public void borrowBook(Long bookId) {// 借閱圖書的邏輯...System.out.println("Book borrowed successfully.");}
}

在這個例子中,@userService.isAccountActive(authentication.principal.username) 調用了自定義的 UserService 中的 isAccountActive 方法來檢查當前認證用戶的賬戶是否激活。這里使用了 SpEL 表達式中的 @ 符號來引用 Spring 管理的 Bean。

步驟 4: 測試方法訪問控制

啟動應用,并以不同的用戶身份嘗試借閱圖書。你應該會發現只有符合條件(即角色為 ROLE_USER 且賬戶為激活狀態)的用戶才能成功執行 borrowBook 方法。

基于參數的動態權限驗證

在一些場景下,你可能需要根據方法參數來動態決定訪問權限。例如,只允許用戶編輯他們自己的文章。

@Service
public class ArticleService {@PreAuthorize("#article.author.username == authentication.principal.username")public void editArticle(Article article) {// 編輯文章的邏輯...}
}

結合自定義方法進行復雜權限驗證

有時候,權限驗證邏輯可能非常復雜,需要結合多個條件進行判斷。這時,可以定義自定義方法來輔助權限驗證。

@Service
public class SecurityService {public boolean checkAccess(Long articleId, String username) {// 復雜的權限驗證邏輯...return true; // 假設返回結果}
}@Service
public class ArticleService {@Autowiredprivate SecurityService securityService;@PreAuthorize("@securityService.checkAccess(#articleId, authentication.principal.username)")public void updateArticle(Long articleId, Article article) {// 更新文章的邏輯...}
}

通過這個案例和其拓展,你可以看到 @PreAuthorize 注解如何為 Spring Security 應用提供強大而靈活的方法級訪問控制能力,使得開發者能夠根據具體的業務需求定制復雜的安全策略。

4.3.3 拓展案例 1:使用自定義權限驗證

在復雜的應用場景中,可能需要根據業務邏輯進行詳細的權限驗證。Spring Security 允許開發者定義自定義權限驗證方法,這為實現復雜的安全需求提供了極大的靈活性。

案例 Demo

假設我們正在開發一個社交媒體平臺,我們需要確保用戶只能刪除自己的帖子。為此,我們將實現一個自定義權限驗證邏輯,以檢查當前認證用戶是否為帖子的所有者。

步驟 1: 創建帖子服務

首先,假設我們有一個 PostService 類,其中包含了帖子的各種操作,包括刪除帖子的方法。

@Service
public class PostService {// 假設有一個方法用于檢查帖子所有者public boolean isOwner(Long postId, String username) {// 這里應該包含實際的業務邏輯來驗證用戶是否為帖子的所有者// 為了簡化,這里直接返回truereturn true;}// 刪除帖子的方法public void deletePost(Long postId) {// 刪除帖子的邏輯...System.out.println("Post deleted successfully.");}
}

步驟 2: 應用自定義權限驗證

接下來,我們將在 PostServicedeletePost 方法上應用自定義權限驗證,以確保只有帖子的所有者才能刪除帖子。

@Service
public class PostService {@Autowiredprivate SecurityService securityService;@PreAuthorize("@securityService.isOwner(#postId, authentication.principal.username)")public void deletePost(Long postId) {// 刪除帖子的邏輯...}
}

在這個例子中,@PreAuthorize 注解使用了 SpEL 表達式來引用 SecurityService 中的 isOwner 方法,并傳入了帖子ID和當前認證用戶的用戶名作為參數,以驗證當前用戶是否有權刪除該帖子。

步驟 3: 測試自定義權限驗證

啟動應用,并以不同用戶身份嘗試刪除帖子。只有當用戶為帖子的所有者時,刪除操作才應該成功執行。

結合數據庫進行權限驗證

在更復雜的場景中,可能需要根據數據庫中的信息來進行權限驗證。例如,驗證用戶是否有權訪問某個特定的資源。

步驟 1: 創建權限驗證服務

假設我們有一個 PermissionService,它可以根據用戶和資源ID來檢查用戶是否具有訪問權限。

@Service
public class PermissionService {public boolean hasPermission(String username, Long resourceId) {// 實現根據數據庫信息檢查用戶是否有權訪問資源的邏輯// 為了簡化,這里直接返回truereturn true;}
}

步驟 2: 應用數據庫權限驗證

在需要進行權限驗證的方法上使用 @PreAuthorize 注解,并調用 PermissionService 的方法來實現基于數據庫的權限驗證。

@Service
public class ResourceService {@Autowiredprivate PermissionService permissionService;@PreAuthorize("@permissionService.hasPermission(authentication.principal.username, #resourceId)")public void accessResource(Long resourceId) {// 訪問資源的邏輯...}
}

通過實現這些案例,你可以看到如何利用 Spring Security 提供的方法級安全特性和 SpEL 表達式,結合自定義邏輯和數據庫信息來實現復雜的權限驗證需求,從而為應用提供強大而靈活的安全保護。

4.3.4 拓展案例 2:基于返回值的后置授權

后置授權是一種在方法執行后基于其返回值來做出授權決策的機制。這種方式在需要根據操作結果來決定是否授權訪問時非常有用,例如,只允許用戶查看或修改他們自己創建的資源。

案例 Demo

假設我們正在開發一個任務管理應用,其中包含一個功能允許用戶查看任務詳情。我們希望確保用戶只能查看他們自己創建的任務。

步驟 1: 創建任務實體和服務

首先,假設我們有一個 Task 實體,其中包含了任務的創建者信息。同時,我們有一個 TaskService 類,其中包含了獲取任務詳情的方法。

public class Task {private Long id;private String title;private String creator; // 創建者的用戶名// 構造器、getter和setter省略
}@Service
public class TaskService {public Task getTaskDetails(Long taskId) {// 獲取任務詳情的邏輯,這里簡化為直接返回一個示例任務return new Task(taskId, "Example Task", "user1");}
}

步驟 2: 應用后置授權

TaskServicegetTaskDetails 方法上使用 @PostAuthorize 注解來實現后置授權。這里我們根據返回的任務對象的創建者與當前認證用戶的用戶名進行比較,以確定是否允許訪問。

@Service
public class TaskService {@PostAuthorize("returnObject.creator == authentication.name")public Task getTaskDetails(Long taskId) {// 獲取任務詳情的邏輯return new Task(taskId, "Example Task", "user1");}
}

步驟 3: 測試后置授權

啟動應用,并以不同用戶身份嘗試獲取任務詳情。你會發現只有任務的創建者能夠成功獲取到任務詳情,其他用戶嘗試訪問時將會因為授權失敗而被拒絕。

自定義后置處理邏輯

在某些情況下,你可能需要執行更復雜的后置處理邏輯,這時可以結合 @PostAuthorize 注解和自定義方法來實現。

步驟 1: 創建自定義后置處理服務

假設我們有一個 SecurityService,它提供了自定義的后置處理邏輯。

@Service
public class SecurityService {public boolean customPostAuthorizeLogic(Task task) {// 實現自定義的后置處理邏輯,例如根據任務的某些屬性進行復雜的校驗// 為了簡化,這里直接返回truereturn true;}
}

步驟 2: 結合自定義后置處理邏輯應用后置授權

在需要進行后置授權的方法上,結合 @PostAuthorize 注解和自定義的后置處理邏輯。

@Service
public class TaskService {@Autowiredprivate SecurityService securityService;@PostAuthorize("@securityService.customPostAuthorizeLogic(returnObject)")public Task getTaskDetails(Long taskId) {// 獲取任務詳情的邏輯return new Task(taskId, "Example Task", "user1");}
}

通過使用基于返回值的后置授權和自定義后置處理邏輯,你可以實現復雜的安全需求,確保應用中的敏感操作和數據只對授權用戶開放,從而提高應用的安全性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/714673.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/714673.shtml
英文地址,請注明出處:http://en.pswp.cn/news/714673.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【flutter】加載指示器(loading indicator)阻止用戶在某個操作執行期間操作頁面

在Flutter中&#xff0c;通過顯示一個加載指示器&#xff08;loading indicator&#xff09;來阻止用戶在某個操作執行期間操作頁面。以下是一個簡單的示例代碼&#xff0c;演示了按鈕被點擊后執行某操作&#xff0c;在操作完成前顯示加載指示器&#xff0c;阻止用戶操作頁面&a…

c語言數據結構(5)——棧

歡迎來到博主的專欄——C語言數據結構 博主id&#xff1a;代碼小豪 文章目錄 棧棧的順序存儲結構棧的插入空棧的初始化棧的刪除判斷空棧讀取棧頂元素數據 實現順序棧的所有代碼棧的鏈式存儲結構鏈式棧的初始化鏈式棧的入棧操作鏈式棧的出棧操作 實現鏈式棧的所有代碼 棧 棧是…

學習網絡編程No.11【傳輸層協議之UDP】

引言&#xff1a; 北京時間&#xff1a;2023/11/20/9:17&#xff0c;昨天成功更文&#xff0c;上周實現了更文兩篇&#xff0c;所以這周再接再厲。當然做題任在繼續&#xff0c;而目前做題給我的感覺以套路和技巧偏多&#xff0c;還是那句話很多東西不經歷你就是不懂&#xff…

測試人員如何向開發人員準確清晰地描述問題?

測試人員向開發人員準確清晰地描述問題可以采取以下方法&#xff1a; 提供詳細的背景和上下文信息&#xff1a;描述問題發生的環境、前提條件和操作步驟&#xff0c;讓開發人員能夠了解問題出現的場景。明確問題的癥狀和表現&#xff1a;清楚地說明問題的具體表現&#xff0c;…

【Python】2. 基礎語法

常量和表達式 我們可以把 Python 當成一個計算器, 來進行一些算術運算. 注意: print 是一個 Python 內置的 函數, 這個稍后詳細介紹. 可以使用 - * / ( ) 等運算符進行算術運算. 先算乘除, 后算加減. 運算符和數字之間, 可以沒有空格, 也可以有多個空格. 但是一般習慣上寫一…

LDR6328芯片:智能家居時代的小家電充電革新者

在當今的智能家居時代&#xff0c;小家電的供電方式正變得越來越智能化和高效化。 利用PD&#xff08;Power Delivery&#xff09;芯片進行誘騙取電&#xff0c;為后端小家電提供穩定電壓的技術&#xff0c;正逐漸成為行業的新寵。在這一領域&#xff0c;LDR6328芯片以其出色的…

Qt下使用modbus-c庫實現PLC線圈/保持寄存器的讀寫

系列文章目錄 提示&#xff1a;這里是該系列文章的所有文章的目錄 第一章&#xff1a;Qt下使用ModbusTcp通信協議進行PLC線圈/保持寄存器的讀寫&#xff08;32位有符號數&#xff09; 第二章&#xff1a;Qt下使用modbus-c庫實現PLC線圈/保持寄存器的讀寫 文章目錄 系列文章目錄…

前端Vue3項目如何打包成Docker鏡像運行

將前端Vue3項目打包成Docker鏡像并運行包括幾個主要步驟&#xff1a;項目打包、編寫Dockerfile、構建鏡像和運行容器。下面是一個基本的流程&#xff1a; 1. 項目打包 首先&#xff0c;確保你的Vue3項目可以正常運行和打包。在項目根目錄下執行以下命令來打包你的Vue3項目&am…

nest.js使用nest-winston日志一

nest-winston文檔 nest-winston - npm 參考&#xff1a;nestjs中winston日志模塊使用 - 浮的blog - SegmentFault 思否 安裝 cnpm install --save nest-winston winstoncnpm install winston-daily-rotate-file 在main.ts中 import { NestFactory } from nestjs/core; im…

【5G 接口協議】GTP-U協議介紹

博主未授權任何人或組織機構轉載博主任何原創文章&#xff0c;感謝各位對原創的支持&#xff01; 博主鏈接 本人就職于國際知名終端廠商&#xff0c;負責modem芯片研發。 在5G早期負責終端數據業務層、核心網相關的開發工作&#xff0c;目前牽頭6G算力網絡技術標準研究。 博客…

mysql學習

查看glibc版本 ldd --version --mysql啟動失敗,嘗試啟動 1 查看錯誤日志,端口被占用,參數名寫錯,有不支持的參數 2 通過mysqld啟動 mysqld --default-filemy.cnf & 3 mysqld --no-defaults --basedir/user/local/mysql --datadir/data/mysql/3306/data/ --usermysql 4 str…

深入理解 Nginx 的負載均衡與反向代理

深入理解 Nginx 的負載均衡與反向代理 Nginx 是一個高性能的 HTTP 和反向代理服務器&#xff0c;也是一個 IMAP/POP3/SMTP 代理服務器。由于其出色的性能和靈活性&#xff0c;Nginx 已成為現代 web 架構中的重要組成部分&#xff0c;尤其是在處理高并發連接和大規模流量時。在…

找到數組的中間位置-1991-[簡單]

力扣 關鍵點 從題目中總結出公式 sum * 2 nums[i] total從左往右開始嘗試&#xff0c;尋找 i 位置滿足上面的公式&#xff0c;為什么從左開始&#xff0c;因為題目要求找到最左邊的一個用前綴和的概念來解&#xff0c;從左往右嘗試i位置的左邊所有數之和&#xff0c;右邊所有…

基礎小白快速入門Python------>模塊的作用和意義

模塊&#xff0c; 這個詞聽起來是如此的高大威猛&#xff0c;以至于萌新小白見了瑟瑟發抖&#xff0c;本草履蟲見了都直搖頭&#xff0c;好像聽上去很難的樣子&#xff0c;但是但是&#xff0c;年輕人&#xff0c;請聽本少年細細講述&#xff0c;他只是看起來很難&#xff0c;實…

GO-接口

1. 接口 在Go語言中接口&#xff08;interface&#xff09;是一種類型&#xff0c;一種抽象的類型。 interface是一組method的集合&#xff0c;接口做的事情就像是定義一個協議&#xff08;規則&#xff09;&#xff0c;只要一臺機器有洗衣服和甩干的功能&#xff0c;我就稱它…

【go語言開發】swagger安裝和使用

本文主要介紹go-swagger的安裝和使用&#xff0c;首先介紹如何安裝swagger&#xff0c;測試是否成功&#xff1b;然后列出常用的注釋和給出使用例子&#xff1b;最后生成接口文檔&#xff0c;并在瀏覽器上測試 文章目錄 安裝注釋說明常用注釋參考例子 文檔生成格式化文檔生成do…

C++從零開始的打怪升級之路(day39)

這是關于一個普通雙非本科大一學生的C的學習記錄貼 在此前&#xff0c;我學了一點點C語言還有簡單的數據結構&#xff0c;如果有小伙伴想和我一起學習的&#xff0c;可以私信我交流分享學習資料 那么開啟正題 今天分享的是關于模板的知識點 1.非類型模板參數 模板參數分為…

大模型生成,Open API調用

大模型是怎么生成結果的 通俗原理 其實&#xff0c;它只是根據上文&#xff0c;猜下一個詞&#xff08;的概率&#xff09;…… OpenAI 的接口名就叫【completion】&#xff0c;也證明了其只會【生成】的本質。 下面用程序演示【生成下一個字】。你可以自己修改 prompt 試試…

高并發下的 AtomicReference 性能陷阱

介紹 Java 提供了 AtomicInteger/AtomicLong 在并發編程里經常用到&#xff0c;它們封裝了對 int 和 long 的原子操作。 Java 還提供了 AtomicReference&#xff0c;用于對象引用做原子性的管理&#xff0c;比如 get、set、CAS。 一般情況下 AtomicInteger、AtomicLong 的性能…

mac新環境

1、maven 設置阿里云鏡像 打開Maven的settings.xml文件。找到<mirrors>標簽&#xff0c;如果沒有&#xff0c;可以手動添加。在<mirrors>標簽內部添加以下內容&#xff1a; <mirror> <id>nexus-aliyun</id> <mirrorOf>*</mirrorO…