【SpringSecurity】詳細核心類與過濾器流程講解和封裝通用組件實戰

Spring Security 全面介紹

1. 什么是 Spring Security?

Spring Security 是一個功能強大且高度可定制的認證和訪問控制框架,是保護基于 Spring 的應用程序的標準工具。它是一個專注于為 Java 應用程序提供認證和授權的框架,實際上它是 Spring 生態系統中負責安全方面的重要成員。

核心特性

  • 全面的安全性:支持認證、授權和防護常見攻擊
  • 與 Spring 生態系統深度集成
  • 適應多種環境:Web 應用、RESTful API、微服務
  • 高度可擴展性:幾乎所有組件都可以定制或替換
  • 多種認證機制:表單認證、Basic 認證、OAuth2、LDAP、SAML 等

2. 基本概念

認證 (Authentication)

認證回答了"你是誰?"的問題,是確認用戶身份的過程。

核心工作流程:

  1. 收集認證憑據(如用戶名和密碼)
  2. 驗證憑據的有效性
  3. 對有效憑據發放安全令牌

授權 (Authorization)

授權回答了"你能做什么?"的問題,是確定用戶是否有權執行特定操作的過程。

核心工作流程:

  1. 確定資源要求的權限
  2. 檢查已認證用戶是否擁有所需權限
  3. 授予或拒絕訪問權限

主體 (Principal)

表示當前通過認證的用戶,通常包含用戶標識(如用戶名)和授予的權限。

權限 (Authorities/Roles)

表示授予用戶的特定權限,通常分為:

  • 角色 (Roles):代表用戶組,如 ROLE_ADMIN
  • 權限 (Authorities):代表具體權限,如 READ_DATA

3. 核心架構

Spring Security 架構基于過濾器鏈模式,請求通過一系列專門的過濾器,各自負責安全流程的不同方面。

關鍵組件

Security Filters

過濾器鏈,按特定順序執行的安全過濾器:

  • SecurityContextPersistenceFilter:管理 SecurityContext
  • UsernamePasswordAuthenticationFilter:處理表單登錄
  • BasicAuthenticationFilter:處理 HTTP Basic 認證
  • ExceptionTranslationFilter:處理安全異常
  • FilterSecurityInterceptor:處理授權決策
Authentication Manager

認證管理器,主要實現是 ProviderManager,它維護一個 AuthenticationProvider 列表,依次嘗試處理認證請求。

Authentication Providers

認證提供者,負責特定類型的認證,如:

  • DaoAuthenticationProvider:基于用戶名密碼的認證
  • JwtAuthenticationProvider:JWT 令牌驗證
  • LdapAuthenticationProvider:LDAP 認證
UserDetailsService

負責加載用戶數據,是自定義用戶存儲的主要擴展點。

Security Context

安全上下文,存儲當前認證信息,通過 SecurityContextHolder 提供訪問。

4. 常見認證機制

表單登錄認證

最常見的認證方式,用戶通過 HTML 表單提交憑據:

http.formLogin().loginPage("/custom-login").defaultSuccessUrl("/dashboard").failureUrl("/login?error=true").permitAll();

HTTP Basic 認證

簡單的認證機制,通過 HTTP 頭發送憑據:

http.httpBasic().realmName("My API");

記住我功能

允許用戶在會話過期后保持登錄狀態:

http.rememberMe().key("uniqueAndSecret").tokenValiditySeconds(86400);

OAuth 2.0 / OpenID Connect

用于實現單點登錄和 API 授權:

http.oauth2Login().loginPage("/oauth_login").clientRegistrationRepository(clientRegistrationRepository).authorizedClientService(authorizedClientService);

5. 授權模型

基于 URL 的授權

控制對 Web URL 的訪問:

http.authorizeHttpRequests().requestMatchers("/public/**").permitAll().requestMatchers("/admin/**").hasRole("ADMIN").requestMatchers("/api/**").hasAuthority("API_ACCESS").anyRequest().authenticated();

方法級安全

在服務層保護方法調用:

@PreAuthorize("hasRole('ADMIN') or #username == authentication.principal.username")
public UserDetails loadUserByUsername(String username) {// 實現邏輯
}

基于表達式的訪問控制

使用 SpEL 表達式定義復雜授權規則:

http.authorizeHttpRequests().requestMatchers("/orders/**").access("hasRole('USER') and @webSecurity.checkUserId(authentication, #id)");

6. 會話管理

會話創建策略

控制會話的創建方式:

http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

可選策略:

  • ALWAYS:總是創建會話
  • NEVER:不主動創建會話
  • IF_REQUIRED:需要時創建(默認)
  • STATELESS:不創建或使用會話(適用于 REST API)

并發會話控制

限制用戶同時活躍的會話數:

http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);

會話固定保護

防止會話固定攻擊:

http.sessionManagement().sessionFixation().migrateSession();

7. 保護常見攻擊

CSRF 保護

默認啟用,保護表單提交免受跨站請求偽造:

http.csrf()  // 默認啟用.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

跨域資源共享 (CORS)

啟用跨域支持:

http.cors().configurationSource(corsConfigurationSource());

安全頭部配置

添加安全相關的 HTTP 頭:

http.headers().frameOptions().deny().xssProtection().block().contentSecurityPolicy("script-src 'self'");

8. 實際應用示例

基礎 Web 應用配置

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/", "/home", "/css/**", "/js/**").permitAll().requestMatchers("/user/**").hasRole("USER").requestMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").permitAll().defaultSuccessUrl("/dashboard")).logout(logout -> logout.permitAll().logoutSuccessUrl("/login?logout"));return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

自定義用戶存儲

@Service
public class CustomUserDetailsService implements UserDetailsService {private final UserRepository userRepository;public CustomUserDetailsService(UserRepository userRepository) {this.userRepository = userRepository;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.isEnabled(),true, true, true,mapRolesToAuthorities(user.getRoles()));}private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Set<Role> roles) {return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())).collect(Collectors.toSet());}
}

REST API 安全配置

@Configuration
@EnableWebSecurity
public class ApiSecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(auth -> auth.requestMatchers("/api/public/**").permitAll().anyRequest().authenticated()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).httpBasic(Customizer.withDefaults());return http.build();}
}

9. 高級特性

方法安全

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {// 配置代碼
}@Service
public class UserService {@PreAuthorize("hasRole('ADMIN')")public List<User> findAllUsers() {// 實現邏輯}@PostAuthorize("returnObject.username == authentication.name")public User getUser(String id) {// 實現邏輯}
}

多租戶安全

@Bean
public TenantContextHolder tenantContextHolder() {return new TenantContextHolder();
}@Bean
public UserDetailsService userDetailsService(DataSource dataSource, TenantContextHolder holder) {return username -> {String tenant = holder.getTenant();// 基于租戶獲取用戶};
}

事件監聽

@Component
public class AuthenticationEventListener {private static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);@EventListenerpublic void onSuccess(AuthenticationSuccessEvent event) {logger.info("User logged in: {}", event.getAuthentication().getName());}@EventListenerpublic void onFailure(AuthenticationFailureBadCredentialsEvent event) {logger.warn("Login failed for user: {}", event.getAuthentication().getName());}
}

10. 最佳實踐

1. 使用強密碼哈希

@Bean
public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(12);  // 更高的強度
}

2. 保持依賴更新

定期更新 Spring Security 版本,以獲取安全修復和新功能。

3. 使用多種防御機制

不要僅依賴一種安全機制,應組合使用認證、授權、CSRF 保護等。

4. 最小權限原則

默認拒絕訪問,只明確允許必要的權限:

.anyRequest().denyAll()  // 而不是 .authenticated()

5. 安全日志記錄

記錄所有重要的安全事件,但避免記錄敏感信息:

@EventListener
public void handleBadCredentials(AuthenticationFailureBadCredentialsEvent event) {logger.warn("Failed login attempt from IP: {}", request.getRemoteAddr());// 不要記錄密碼!
}

6. 適當的錯誤處理

不要泄露敏感信息:

.failureHandler((request, response, exception) -> {response.sendRedirect("/login?error=true");  // 通用錯誤,不指明原因
})

11. 生態系統集成

Spring Boot 集成

Spring Boot 自動配置大部分 Spring Security 功能:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

OAuth2 客戶端集成

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

資源服務器

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

12. 性能和可擴展性考慮

  • 使用適當的會話策略(無狀態 vs. 有狀態)
  • 緩存用戶詳情和權限檢查
  • 在負載均衡環境中注意會話復制/共享
  • 考慮分布式會話存儲(Redis, Hazelcast)

Spring Security 攔截器鏈工作步驟

作為一名資深軟件工程師,我將詳細分析 Spring Security 的攔截器鏈(Filter Chain)工作流程。Spring Security 使用一系列過濾器來實現其安全功能,這些過濾器按特定順序組織成攔截器鏈。

攔截器鏈概述

Spring Security 的攔截器鏈是由 FilterChainProxy 管理的一系列 SecurityFilterChain 對象,每個 SecurityFilterChain 包含多個安全過濾器。

標準過濾器執行順序

以下是典型的 Spring Security 過濾器鏈執行順序(從先到后):

  1. WebAsyncManagerIntegrationFilter

    • 將 SecurityContext 集成到 Spring 異步處理中
    • 確保異步請求中可以訪問 SecurityContext
  2. SecurityContextPersistenceFilter

    • 在請求開始時從 SecurityContextRepository(通常是 HTTP Session)中恢復 SecurityContext
    • 在請求結束時將 SecurityContext 保存回 SecurityContextRepository
    • 使安全上下文在整個請求中可用
  3. HeaderWriterFilter

    • 向響應添加安全相關 HTTP 頭
    • 如 X-XSS-Protection, X-Frame-Options, X-Content-Type-Options 等
  4. CsrfFilter

    • 提供 CSRF(跨站請求偽造)保護
    • 驗證 POST/PUT/DELETE 等請求中的 CSRF token
  5. LogoutFilter

    • 處理 /logout 路徑的請求
    • 執行用戶注銷邏輯,清除認證信息和會話
  6. UsernamePasswordAuthenticationFilter

    • 處理表單登錄嘗試(通常是 /login POST 請求)
    • 提取用戶名和密碼并創建認證令牌
    • 委托給 AuthenticationManager 進行實際驗證
  7. DefaultLoginPageGeneratingFilter

    • 如果沒有自定義登錄頁,生成默認登錄頁面
    • 處理 /login GET 請求
  8. DefaultLogoutPageGeneratingFilter

    • 生成默認注銷頁面
    • 處理 /logout GET 請求
  9. BasicAuthenticationFilter

    • 處理 HTTP Basic 認證頭
    • 提取憑據并嘗試認證
  10. RequestCacheAwareFilter

    • 處理請求緩存
    • 如果用戶在登錄前訪問受保護資源,登錄后可以重定向到原始 URL
  11. SecurityContextHolderAwareRequestFilter

    • 包裝 HttpServletRequest,添加安全相關方法
    • 實現 Servlet API 安全方法
  12. AnonymousAuthenticationFilter

    • 如果當前沒有認證信息,創建匿名用戶認證
    • 確保 SecurityContext 總是有 Authentication 對象
  13. SessionManagementFilter

    • 檢測會話相關問題
    • 處理會話固定保護、并發會話控制等
  14. ExceptionTranslationFilter

    • 捕獲安全異常并轉換為適當的 HTTP 響應
    • 處理 AccessDeniedException 和 AuthenticationException
  15. FilterSecurityInterceptor

    • 最后一道防線,保護 HTTP 資源
    • 使用 AccessDecisionManager 確定是否允許當前請求訪問資源
    • 在這里應用具體的訪問控制決策

工作流程詳解

  1. 請求到達

    • 請求首先進入 FilterChainProxy
    • FilterChainProxy 找到匹配當前請求的第一個 SecurityFilterChain
  2. 上下文準備

    • SecurityContextPersistenceFilter 檢索或創建 SecurityContext
    • SecurityContext 存儲在 SecurityContextHolder
  3. 認證流程

    • 如果請求包含認證信息(如登錄請求),相應的認證過濾器處理認證
    • 認證成功后,Authentication 對象被放入 SecurityContext
  4. 授權檢查

    • FilterSecurityInterceptor 檢查用戶是否有權限訪問請求的資源
    • 使用 SecurityMetadataSource 獲取資源所需權限
    • 使用 AccessDecisionManager 決定是否授予訪問權限
  5. 異常處理

    • 如果發生安全異常,ExceptionTranslationFilter 進行處理
    • 認證異常觸發認證流程(通常重定向到登錄頁)
    • 授權異常產生 403 禁止訪問響應
  6. 請求完成

    • 所有過濾器處理完畢后,請求傳遞給實際的應用程序處理器
    • 請求處理完成后,SecurityContextPersistenceFilter 保存 SecurityContext
    • SecurityContextHolder 被清理

自定義配置示例

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/public/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();}
}

這個配置會設置適當的過濾器并配置它們的行為。

調試技巧

要查看實際應用的過濾器鏈,可以設置日志級別:

logging.level.org.springframework.security.web.FilterChainProxy=DEBUG

這將輸出每個請求應用的確切過濾器鏈。

Spring Security 的攔截器鏈設計體現了職責分離原則,每個過濾器專注于特定安全功能,共同構成了一個強大而靈活的安全框架。

Spring Security 核心類詳解

作為資深軟件工程師,我將深入介紹 Spring Security 的核心類結構。這些核心類共同構成了 Spring Security 的基礎架構,理解它們有助于掌握整個框架的工作原理。

認證核心類

1. Authentication(接口)

public interface Authentication extends Principal, Serializable {Collection<? extends GrantedAuthority> getAuthorities();Object getCredentials();Object getDetails();Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean isAuthenticated);
}
  • 職責: 代表認證請求或已認證的主體
  • 關鍵方法:
    • getPrincipal(): 獲取主體身份(通常是 UserDetails)
    • getCredentials(): 獲取憑證(如密碼)
    • getAuthorities(): 獲取授予的權限
    • isAuthenticated(): 判斷是否已認證
  • 常見實現: UsernamePasswordAuthenticationToken, JwtAuthenticationToken

2. AuthenticationManager(接口)

public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
  • 職責: 處理認證請求,是認證架構的核心接口
  • 工作方式: 接收一個 Authentication 對象,驗證后返回完全填充的 Authentication
  • 主要實現: ProviderManager

3. ProviderManager

public class ProviderManager implements AuthenticationManager {private List<AuthenticationProvider> providers;private AuthenticationManager parent;public Authentication authenticate(Authentication authentication) throws AuthenticationException {// 遍歷所有 provider 嘗試認證}
}
  • 職責: AuthenticationManager 的主要實現
  • 工作流程: 維護 AuthenticationProvider 列表,依次嘗試認證

4. AuthenticationProvider(接口)

public interface AuthenticationProvider {Authentication authenticate(Authentication authentication) throws AuthenticationException;boolean supports(Class<?> authentication);
}
  • 職責: 執行特定類型的認證
  • 關鍵實現:
    • DaoAuthenticationProvider: 基于用戶名密碼的認證
    • JwtAuthenticationProvider: JWT令牌認證

5. UserDetailsService(接口)

public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
  • 職責: 從數據源加載用戶信息
  • 使用場景: 被 AuthenticationProvider 調用以獲取用戶數據

6. UserDetails(接口)

public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}
  • 職責: 提供核心用戶信息
  • 特點: 框架對用戶概念的抽象,與應用用戶模型解耦
  • 常用實現: User 和自定義實現

安全上下文管理

7. SecurityContext(接口)

public interface SecurityContext extends Serializable {Authentication getAuthentication();void setAuthentication(Authentication authentication);
}
  • 職責: 存儲當前線程的安全信息
  • 主要實現: SecurityContextImpl

8. SecurityContextHolder

public class SecurityContextHolder {private static SecurityContextHolderStrategy strategy;public static SecurityContext getContext() {return strategy.getContext();}public static void setContext(SecurityContext context) {strategy.setContext(context);}public static void clearContext() {strategy.clearContext();}
}
  • 職責: 提供對當前 SecurityContext 的訪問
  • 存儲策略: 支持 ThreadLocal, InheritableThreadLocal 和全局模式

9. SecurityContextRepository(接口)

public interface SecurityContextRepository {SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);boolean containsContext(HttpServletRequest request);
}
  • 職責: 在請求之間持久化 SecurityContext
  • 主要實現: HttpSessionSecurityContextRepository

授權核心類

10. AccessDecisionManager(接口)

public interface AccessDecisionManager {void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException;boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);
}
  • 職責: 做出訪問控制決策
  • 主要實現:
    • AffirmativeBased: 只要有一個投票者同意即通過
    • ConsensusBased: 基于多數原則
    • UnanimousBased: 要求全體一致同意

11. AccessDecisionVoter(接口)

public interface AccessDecisionVoter<S> {int ACCESS_GRANTED = 1;int ACCESS_ABSTAIN = 0;int ACCESS_DENIED = -1;boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}
  • 職責: 對訪問請求進行投票
  • 主要實現: RoleVoter, WebExpressionVoter

12. FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {private FilterInvocationSecurityMetadataSource securityMetadataSource;public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {// 攔截請求并應用安全檢查}
}
  • 職責: HTTP 資源的安全攔截器
  • 工作流程: 從 SecurityMetadataSource 獲取配置屬性,調用 AccessDecisionManager 做決策

過濾器鏈管理

13. FilterChainProxy

public class FilterChainProxy extends GenericFilterBean {private List<SecurityFilterChain> filterChains;public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {// 找到匹配的過濾器鏈并執行}
}
  • 職責: Spring Security 過濾器的主入口點
  • 特點: 管理多個 SecurityFilterChain 實例

14. SecurityFilterChain(接口)

public interface SecurityFilterChain {boolean matches(HttpServletRequest request);List<Filter> getFilters();
}
  • 職責: 持有與特定請求匹配的過濾器集合
  • 主要實現: DefaultSecurityFilterChain

關鍵過濾器

15. UsernamePasswordAuthenticationFilter

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {// 處理表單登錄public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {// 提取用戶名密碼并創建認證令牌}
}
  • 職責: 處理表單登錄認證

16. ExceptionTranslationFilter

public class ExceptionTranslationFilter extends GenericFilterBean {private AccessDeniedHandler accessDeniedHandler;private AuthenticationEntryPoint authenticationEntryPoint;public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {// 捕獲安全異常并轉換為HTTP響應}
}
  • 職責: 轉換 Spring Security 異常為 HTTP 響應
  • 處理邏輯:
    • AuthenticationException → AuthenticationEntryPoint
    • AccessDeniedException → AccessDeniedHandler

17. SecurityContextPersistenceFilter

public class SecurityContextPersistenceFilter extends GenericFilterBean {private SecurityContextRepository repo;public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {// 在請求前加載上下文,請求后保存上下文}
}
  • 職責: 管理 SecurityContext 的生命周期

配置核心類

18. WebSecurityConfigurerAdapter (在Spring Security 5.7+已廢棄)

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {protected void configure(HttpSecurity http) throws Exception {// 配置HTTP安全}protected void configure(AuthenticationManagerBuilder auth) throws Exception {// 配置認證}
}
  • 職責: 提供安全配置的便捷基類
  • : 在新版中使用組件化配置代替

19. HttpSecurity

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> {// 提供流式API配置HTTP安全public HttpSecurity authorizeRequests() {...}public HttpSecurity formLogin() {...}// 更多配置方法...
}
  • 職責: 配置 HTTP 請求級別的安全特性
  • 特點: 提供流式 API 進行安全配置

20. SecurityBuilder & SecurityConfigurer

public interface SecurityBuilder<O> {O build() throws Exception;
}public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {void init(B builder) throws Exception;void configure(B builder) throws Exception;
}
  • 職責: 構建器模式的核心接口,支持模塊化安全配置

實際應用示例

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(auth -> auth.requestMatchers("/api/public/**").permitAll().requestMatchers("/api/admin/**").hasRole("ADMIN").anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").permitAll()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));return http.build();}@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build();return new InMemoryUserDetailsManager(user);}
}

理解這些核心類及其關系,能讓你更透徹掌握 Spring Security 的工作方式,并更有效地進行安全配置和定制化開發。無論是標準認證流程還是實現自定義安全機制,這些類都是框架的基礎構建塊。

通用鑒權模塊實戰

我們新建一個單獨的模塊security-contract,封裝Security的相關組件,這樣就能在我們自己的項目中引入。
在這里插入圖片描述

依賴管理

<?xml version="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 http://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.2.4</version><relativePath/></parent><groupId>com.xujie</groupId><artifactId>security-contract</artifactId><version>1.0.0</version><properties><maven.compiler.source>23</maven.compiler.source><maven.compiler.target>23</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 新版本的 JJWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson --><version>0.11.5</version><scope>runtime</scope></dependency><!-- pom.xml --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency></dependencies>
</project>

相關類代碼

IgnoreUrlsConfig 白名單配置類

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "secure")
public class IgnoreUrlsConfig {private List<String> ignored = new ArrayList<>();}

UserSecurityConfiguration 用戶鑒權核心配置類


public class UserSecurityConfiguration {private final StringRedisTemplate redisTemplate;private final MyAuthenticationTokenFilter tokenFilter;private List<String> ingoreList;private final SecurityContextFilter securityContextFilter;public UserSecurityConfiguration(StringRedisTemplate stringRedisTemplate, List<String> ingoreList, SecurityContextFilter securityContextFilter) {this.redisTemplate = stringRedisTemplate;this.tokenFilter = new MyAuthenticationTokenFilter(stringRedisTemplate);this.ingoreList = ingoreList;this.securityContextFilter = securityContextFilter;}@Beanpublic SecurityFilterChain jwtFilterChain(HttpSecurity http) throws Exception {http.cors(cors -> cors.configurationSource(corsConfigurationSource())) // 開啟跨域.csrf(AbstractHttpConfigurer::disable)  // 對API禁用CSRF.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorize -> authorize.requestMatchers(ingoreList.toArray(String[]::new)).permitAll().requestMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN").anyRequest().authenticated()).exceptionHandling(exceptions -> exceptions.authenticationEntryPoint((request, response, ex) -> {response.setCharacterEncoding("UTF-8");response.setStatus(401);response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);ErrorInfo errorInfo = new ErrorInfo(HttpStatus.UNAUTHORIZED.value(), "未登錄或登錄已過期,請重新登錄!");ObjectMapper mapper = new ObjectMapper();mapper.writeValue(response.getWriter(), errorInfo);response.getWriter().flush();}).accessDeniedHandler((request, response, ex) -> {response.setContentType("application/json");response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.getWriter().write("{\"error\":\"Forbidden\",\"message\":\"" + ex.getMessage() + "\"}");})).addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class).addFilterBefore(securityContextFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOrigins(List.of("http://localhost:*")); // 允許的前端域configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); // 允許的HTTP方法configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With")); // 允許的請求頭configuration.setExposedHeaders(List.of("Authorization")); // 允許前端訪問的響應頭configuration.setAllowCredentials(true); // 允許發送憑證configuration.setMaxAge(3600L); // 預檢請求緩存時間(秒)UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration); // 對所有路徑應用此配置return source;}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}public record ErrorInfo(int status, String message) {}
}

AuthenticationConstant 常量類

public class AuthenticationConstant {public static final String HEAD_AUTH = "Authorization";public static final String HEAD_USER_ID = "userId";public static final String REDIS_KEY = "Auth:%s";}

MyAuthenticationTokenFilter Token驗證攔截器


@Slf4j
public class MyAuthenticationTokenFilter extends OncePerRequestFilter {private final StringRedisTemplate redisTemplate;public MyAuthenticationTokenFilter(StringRedisTemplate stringRedisTemplate) {this.redisTemplate = stringRedisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader(AuthenticationConstant.HEAD_AUTH);// 判斷Token是否為空if (token == null) {SecurityContextHolder.getContext().setAuthentication(null);filterChain.doFilter(request, response);}Long userId = JWTUtil.getUserIdFromTokenUnsafe(token);// TODO 暫時不開啟Redis 驗證
//        String key = String.format(AuthenticationConstant.REDIS_KEY, userId);
//        if (!redisTemplate.hasKey(key)) {
//            SecurityContextHolder.getContext().setAuthentication(null);
//            filterChain.doFilter(request, response);
//        }String userSign = "user123password";if (userSign != null && !userSign.isEmpty()) {try {JWTUtil.verifyToken(token, userSign);request.setAttribute(AuthenticationConstant.HEAD_USER_ID, userId);} catch (Exception e) {SecurityContextHolder.getContext().setAuthentication(null);} finally {filterChain.doFilter(request, response);}}}
}

SecurityContextFilter 上下文填充攔截器


public abstract class SecurityContextFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {try {Long userId = (Long) request.getAttribute(AuthenticationConstant.HEAD_USER_ID);if (userId == null) {SecurityContextHolder.getContext().setAuthentication(null);filterChain.doFilter(request, response);return;}Collection<GrantedAuthority> userAuth = getAuths(getUserAuth(userId), getUserRoles(userId));UsernamePasswordAuthenticationToken securityContextFilter = new UsernamePasswordAuthenticationToken(userId, null, userAuth);SecurityContextHolder.getContext().setAuthentication(securityContextFilter);} finally {filterChain.doFilter(request, response);}}protected abstract List<String> getUserRoles(Long userId);protected abstract List<String> getUserAuth(Long userId);private Collection<GrantedAuthority> getAuths(List<String> auths, List<String> userRoles) {Collection<GrantedAuthority> authorities = new ArrayList<>();if (auths == null) {return authorities;}for (String auth : auths) {authorities.add(new SimpleGrantedAuthority(auth));}for (String userRole : userRoles) {authorities.add(new SimpleGrantedAuthority("ROLE_" + userRole));}return authorities;}
}

JWTUtil 工具類


public class JWTUtil {/*** 從用戶密碼生成安全的JWT密鑰** @param userPassword 用戶密碼* @return 安全的JWT密鑰*/public static SecretKey generateSecureKeyFromPassword(String userPassword) throws NoSuchAlgorithmException {// 1. 組合用戶密碼String combined = userPassword;// 2. 使用SHA-256對組合字符串進行哈希處理MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] hashedBytes = digest.digest(combined.getBytes(StandardCharsets.UTF_8));// 3. 確保密鑰長度足夠(SHA-256已經產生32字節/256位的輸出)return Keys.hmacShaKeyFor(hashedBytes);}/*** 創建JWT令牌*/public static String createToken(String username, String userPassword) throws NoSuchAlgorithmException {SecretKey key = generateSecureKeyFromPassword(userPassword);return Jwts.builder().setSubject(username).setId(UUID.randomUUID().toString()).signWith(key).compact();}/*** 創建JWT令牌*/public static String createToken(String username, String userPassword, Map<String, String> claims) throws NoSuchAlgorithmException {SecretKey key = generateSecureKeyFromPassword(userPassword);return Jwts.builder().setClaims(claims).setSubject(username).setId(UUID.randomUUID().toString()).signWith(key).compact();}/*** 驗證并解析令牌*/public static void verifyToken(String token, String sign) throws NoSuchAlgorithmException {try {SecretKey key = generateSecureKeyFromPassword(sign);Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();} catch (Exception e) {throw new IllegalArgumentException("Token is not valid");}}/*** 不驗證簽名,直接解析JWT獲取對應的Claim* 警告:此方法不驗證令牌的真實性,僅用于讀取*/private static String parseTokenWithoutVerification(String token, String claimKey) {// 將token拆分成頭部、負載和簽名String[] parts = token.split("\\.");if (parts.length != 3) {throw new IllegalArgumentException("Invalid token format");}DecodedJWT decode = JWT.decode(token);Map<String, Claim> claims = decode.getClaims();Claim claim = claims.get(claimKey);if (claim == null) {throw new IllegalArgumentException("Invalid claim");}return claim.asString();}/*** 不驗證簽名,直接獲取用戶ID*/public static Long getUserIdFromTokenUnsafe(String token) {String userId = parseTokenWithoutVerification(token, "userId");return Long.valueOf(userId);}public static void main(String[] args) {try {// 模擬用戶密碼String userPassword = "user123password";String username = "john_doe";// 創建令牌Map<String, String> claimsMap = new HashMap<>();claimsMap.put("userId", "1001");String jwt = createToken(username, userPassword, claimsMap);System.out.println("生成的JWT: " + jwt);// 獲取Token的subjectLong userId = JWTUtil.getUserIdFromTokenUnsafe(jwt);System.out.println("UserId: " + userId);// 驗證令牌verifyToken(jwt, userPassword);System.out.println("驗證成功");// 驗證使用錯誤密碼try {verifyToken(jwt, "wrong_password");System.out.println("這行不應該執行");} catch (Exception e) {System.out.println("使用錯誤密碼驗證失敗(預期行為): " + e.getMessage());}} catch (Exception e) {e.printStackTrace();}}
}

引入自己模塊使用

WebSecurityConfig 配置類

@Configuration
public class WebSecurityConfig extends UserSecurityConfiguration {public WebSecurityConfig(StringRedisTemplate stringRedisTemplate, IgnoreUrlsConfig ignoreUrlsConfig, MySecurityContextFilter mySecurityContextFilter) {super(stringRedisTemplate, ignoreUrlsConfig.getIgnored(), mySecurityContextFilter);}
}

MySecurityContextFilter 實現用戶角色和權限接口

@Component
public class MySecurityContextFilter extends SecurityContextFilter {@Overrideprotected List<String> getUserRoles(Long userId) {return List.of("testRole");}@Overrideprotected List<String> getUserAuth(Long userId) {return List.of("testAuth");}
}

配置白名單

secure:ignored:- /test

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

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

相關文章

淺談Qt事件子系統——以可拖動的通用Widget為例子

淺談Qt事件子系統——以可拖動的通用Widget為例子 這一篇文章是一個通過實現可拖動的通用Widget為引子簡單介紹一下我們的事件對象子系統的事情 代碼和所有的文檔 1&#xff1a;Qt側的API介紹和說明 ? 這個是每一個小項目的慣例&#xff0c;我會介紹大部分Qt程序中使用到的…

[入門]NUC13配置Ubuntu20.04詳細步驟

文章目錄 1. 安裝Ubuntu20.041.1 制作系統啟動盤1.1.1 下載鏡像文件1.1.2 配置啟動盤 1.2 安裝內存條、硬盤1.3 安裝系統 2. 網卡驅動配置2.1 關閉安全啟動2.2 安裝intel官方網卡驅動backport2.2.1 第四步可能會出現問題 2.3 ubuntu官方的驅動2.4 重啟 3. 軟件安裝3.1 錄屏軟件…

(七)Reactor響應式編程框架

一、簡介 Reactor 是運行在 JVM 上的編程框架&#xff0c;最大特點是完全非阻塞&#xff0c;能高效控制 “背壓”&#xff0c;簡單來說就是處理數據傳輸時速度不匹配的問題 。它能和 Java 8 里的一些功能直接搭配使用&#xff0c;像處理異步結果的 CompletableFuture、處理數據…

從邊緣到核心:群聯云防護如何重新定義安全加速邊界?

一、安全能力的全方位碾壓 1. 協議層深度防護 四層防御&#xff1a; 動態過濾畸形TCP/UDP包&#xff08;如SYN Flood&#xff09;&#xff0c;傳統CDN僅限速率控制。技術示例&#xff1a;基于AI的協議指紋分析&#xff0c;攔截異常連接模式。 七層防御&#xff1a; 精準識別業…

【Linux】Ubuntu 24.04 LTS 安裝 OpenJDK 8

目錄 通過 apt-get 直接安裝 JDK 1. 更新 apt 軟件源 2. 檢查 JDK 是否已安裝 3. 安裝OpenJDK 4. 檢查 JDK 是否成功安裝 5. 設置 JAVA_HOME 環境變量 找到需要設置的 Java 路徑 使用文本編輯器打開/etc/environment文件 添加 Java 安裝路徑 應用更改和驗證配置 通過…

Java 方法執行原理底層解析

java 文件經過javac編譯后&#xff0c;變成了存儲了一系列指令的.class文件。本文從指令層面分析Java 方法從解析、調用到執行的過程。 1 指令 一般格式&#xff1a;操作碼 [操作數1] [操作數2] ... 操作碼 1個字節的無符號整數&#xff08;范圍&#xff1a;0x00 ~ 0xFF&…

【數學建模】最大最小值模型詳解

數學建模中的最大最小值模型詳解 文章目錄 數學建模中的最大最小值模型詳解引言最大最小值模型的基本概念最大化問題最小化問題 常見的求解方法1. 微積分法2. 線性規劃3. 非線性規劃4. 動態規劃 實際應用案例案例1&#xff1a;生產規劃問題案例2&#xff1a;投資組合優化 最大最…

C#的List和DIctionary實現原理(手搓泛型類以及增刪查改等功能)

這里寫自定義目錄標題 ListDIctionary List MyList類&#xff1a;這是一個泛型類&#xff0c;能夠存儲任意類型的元素。 _items數組&#xff1a;用于實際存儲元素。 _size變量&#xff1a;記錄當前列表中的元素數量。 構造函數&#xff1a;初始化數組容量為 4。 Count屬性&…

Linux系統管理與編程08:任務驅動綜合應用

蘭生幽谷&#xff0c;不為莫服而不芳&#xff1b; 君子行義&#xff0c;不為莫知而止休。 [環境] windows11、centos9.9.2207、zabbix6、MobaXterm、Internet環境 [要求] zabbix6.0安裝環境&#xff1a;Lamp&#xff08;linux httpd mysql8.0 php&#xff09; [步驟] 3 …

數據結構之基本隊列-順序結構實現-初始化-判斷隊列是否為空(front=rear)-出隊-入隊-隊尾滿了,調整隊列-獲取隊頭元素

數據結構之基本隊列-順序結構實現-初始化-判斷隊列是否為空(frontrear)-出隊-入隊-隊尾滿了&#xff0c;調整隊列-獲取隊頭元素——完整可運行代碼 #include <stdio.h>#define MAXSIZE 100 typedef int ElemType;typedef struct {ElemType data[MAXSIZE];int front;int…

基于LabVIEW的Windows平臺高速閉環控制

在Windows系統下&#xff0c;通過LabVIEW實現高速閉環控制面臨兩大核心挑戰&#xff1a;非實時操作系統的調度延遲與硬件接口的傳輸速度限制。以USB-6351&#xff08;NI USB-6351 DAQ卡&#xff09;為例&#xff0c;其理論采樣率可達1.25 MS/s&#xff08;單通道&#xff09;&a…

Java面試黃金寶典8

1. 什么是 Spring MVC 定義 Spring MVC 是 Spring 框架里用于構建 Web 應用程序的模塊&#xff0c;它嚴格遵循 MVC&#xff08;Model - View - Controller&#xff09;設計模式。這種設計模式把應用程序清晰地劃分成三個主要部分&#xff1a; Model&#xff08;模型&#xff0…

【 <二> 丹方改良:Spring 時代的 JavaWeb】之 Spring Boot 中的 RESTful API 設計:從上手到骨折

<前文回顧> 點擊此處查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、開篇整活…

分享最近前端面試遇到的一些問題

前情提要&#xff08;分享個人情況&#xff0c;可以直接跳過&#xff09; 先說一下我的個人情況&#xff0c;我是2026屆的&#xff0c;目前是在找前端實習。 3月初&#xff0c;從3月3日開始在Boss上投簡歷。 分享我的個人故事&#xff0c;不想看可以直接滑到下面&#xff0c;…

rip 協議詳細介紹

以下是關于 RIP&#xff08;Routing Information Protocol&#xff0c;路由信息協議&#xff09; 的詳細介紹&#xff0c;涵蓋其工作原理、版本演進、配置方法、優缺點及實際應用場景。 1. RIP 協議概述 類型&#xff1a;動態路由協議&#xff0c;基于距離矢量算法&#xff08…

scrapy入門(深入)

Scrapy框架簡介 Scrapy是:由Python語言開發的一個快速、高層次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站點并從頁面中提取結構化的數據&#xff0c;只需要實現少量的代碼&#xff0c;就能夠快速的抓取。 新建項目 (scrapy startproject xxx)&#xff1a;新建一個新的…

KiLog2MaximumIncrement的由來和KiMaximumIncrementReciprocal的由來

第一部分&#xff1a;KiLog2MaximumIncrement的由來 i 1; j KeMaximumIncrement; while ((1UI64<<i) < KeMaximumIncrement) { i; } KiLog2MaximumIncrement i; 2^17131072 2^18262144 i18KiLog2MaximumIncrement 中…

數據結構-ArrayList

文章目錄 1. 線性表2. 順序表3. ArrayList4. ArrayList的問題以及思考4.2 增容的性能消耗問題4.3 空間浪費問題 1. 線性表 線性表&#xff08;Linear List&#xff09;是n個具有相同特性的數據元素的有限序列。線性表是一種在實際中廣泛使用的數據結構&#xff0c;常見線性表&…

FastGPT 社區版快速部署指南

產品簡介 FastGPT 是基于大語言模型的智能知識庫系統&#xff0c;提供以下核心能力&#xff1a; ? 開箱即用 - 內置數據預處理、多模型對接、權限管理 ? 可視化編排 - 通過 Flow 工作流實現復雜問答邏輯設計 ? 多場景適配 - 支持客服機器人/知識檢索/數據分析等場景 &…

【css酷炫效果】純CSS實現科技感網格背景

【css酷炫效果】純CSS實現科技感網格背景 緣創作背景html結構css樣式完整代碼基礎版進階版(3D光線掃描版) 效果圖 想直接拿走的老板&#xff0c;鏈接放在這里&#xff1a;上傳后更新 緣 創作隨緣&#xff0c;不定時更新。 創作背景 剛看到csdn出活動了&#xff0c;趕時間&a…