SpringSecurity的應用

官方文檔

一、核心能力

1.1?身份認證 (Authentication) - “你是誰?”

  • 多種認證方式:支持幾乎所有主流認證方案,如表單登錄(Username/Password)、HTTP Basic、HTTP Digest、OAuth 2.0、OIDC (OpenID Connect)、SAML 2.0、LDAP、JAAS、Pre-Authentication(如CAS)等。

    • 表單登錄:最常用的方式,提供默認的登錄頁。

    • HTTP Basic 認證:常用于 REST API。

    • OAuth 2.0 / OpenID Connect:支持第三方登錄(如使用 Google, GitHub, Facebook 登錄)。

    • LDAP:支持企業級目錄服務認證。

    • JAAS:Java 認證和授權服務。

    • 自定義認證:你可以集成任何你想用的認證方式。

  • 靈活的密碼編碼:內置支持多種密碼加密器(如BCrypt、SCrypt、Pbkdf2、Argon2),并強烈推薦使用BCrypt,防止密碼明文存儲。

  • “記住我”功能:通過持久化或基于令牌的機制實現長期登錄(通過 cookie 實現長期會話)。

  • 多因素認證 (MFA):可以集成TOTP(如Google Authenticator)等二次驗證手段。

  • 與現有系統集成:可以輕松地與已有的數據庫表結構、用戶服務進行對接。

1.2?授權 (Authorization) - “你能做什么?”

  • 請求級別授權:基于URL模式,控制用戶對某個API或頁面的訪問權限(例如?/admin/**?需要?ROLE_ADMIN?角色)。

  • 方法級別授權:通過注解(如?@PreAuthorize,?@PostAuthorize,?@Secured)在Service層或Controller層的方法上進行精細化的權限控制。

  • 訪問控制列表 (ACL):支持對領域對象(Domain Object)?進行非常細粒度的權限控制(例如,用戶A可以“讀”文檔1,但不能“刪除”它)。這是一個相對復雜的功能,適用于特定場景。

  • 動態權限:權限規則可以從數據庫或其他動態源加載,實現高度靈活的權限管理。

1.3?防護常見攻擊

  • CSRF (跨站請求偽造):默認開啟防護,尤其對非冪等的POST、PUT等請求進行令牌驗證。

  • Session Fixation (會話固定):默認防護,認證成功后會自動創建新的Session。防止 Session 固定攻擊、控制并發會話數(單一用戶最多同時在線數)、Session 超時處理等。

  • 點擊劫持:可以通過設置HTTP頭?X-Frame-Options?來防護。

  • CORS (跨域資源共享):提供便捷的配置方式。

  • 安全頭:自動設置一系列安全相關的HTTP頭,如?Content-Security-Policy,?X-Content-Type-Options,?X-Frame-Options,?Strict-Transport-Security?等來增強瀏覽器端的安全性。

1.4?與其他技術無縫集成

  • Spring生態系統:與Spring Boot、Spring MVC、Spring WebFlux、Spring Data?深度整合,開箱即用,配置簡便。Spring Boot 更是通過自動配置讓集成變得極其簡單。

  • Servlet API:基于Servlet Filter實現,適用于任何Servlet容器(Tomcat, Jetty等)。

  • 微服務架構:是構建微服務安全(如資源服務器、OAuth2客戶端)的事實標準

1.5 能力邊界

  • ? 在其邊界內(做得好)

    • 應用級別的身份認證和授權。

    • 會話管理。

    • 防護基于Web的常見攻擊(CSRF, XSS的頭防護等)。

  • ? 超出其邊界(不擅長或不做)

    • 網絡層安全:如防火墻規則、VPN、DDoS防護、SSL/TLS終止(通常由網關/負載均衡器負責)。

    • 操作系統/容器安全:如Linux內核安全加固、Docker鏡像漏洞掃描。

    • 數據安全:如數據庫加密、數據傳輸過程中的加密(應由TLS負責)。

    • 業務邏輯漏洞:無法自動防止業務層面的漏洞(例如,水平越權:用戶A通過修改ID訪問了用戶B的數據,需要在授權邏輯中手動編寫檢查代碼)。

    • 安全審計與日志:雖然可以與審計集成,但專業的日志分析和審計通常由ELK、Splunk等專用系統完成。

    • WAF (Web應用防火墻) 功能:雖然能防護一些攻擊,但無法替代專業的WAF來防護復雜的SQL注入、XSS等攻擊(WAF基于規則和模式匹配,在更底層工作)。

二、核心架構與原理

Spring Security 的核心設計理念非常清晰:在 Servlet 過濾器(Filter)層面,為每一個進入應用的 HTTP 請求提供一系列的身份認證(Authentication)和授權(Authorization)檢查

它本質上是一個過濾器鏈,請求必須逐一通過這條鏈上的每個過濾器,才能最終訪問到你的 Controller 中的資源。如果任何一個過濾器檢查失敗,請求就會被重定向、拋出異常或直接返回錯誤信息。

2.1 HTTP完整的請求過程

  1. 請求到達: HTTP 請求進入應用。

  2. 遍歷過濾器鏈: 請求依次經過 Spring Security 的各個過濾器。

  3. 建立安全上下文:?SecurityContextPersistenceFilter?從 Session 中恢復用戶的?SecurityContext(如果已登錄)或創建一個空的。

  4. 處理登錄/認證:

    1. 如果是登錄請求(如?/login?POST),UsernamePasswordAuthenticationFilter?會攔截它,提取用戶名密碼,發起認證流程。

    2. 認證成功,一個包含用戶信息和權限的、已認證的?Authentication?對象會被放入?SecurityContext,并通常保存到 Session 中。

  5. 處理匿名用戶: 如果用戶未認證,AnonymousAuthenticationFilter?會放入一個匿名 Token。

  6. 異常轉換:?ExceptionTranslationFilter?準備捕獲后續的異常。

  7. 授權決策: 請求到達最終的?FilterSecurityInterceptor

    1. 它提取當前請求對應的權限規則 (ConfigAttribute)。

    2. 它從?SecurityContextHolder?中獲取已認證的?Authentication?對象。

    3. 它調用?AccessDecisionManager?進行投票決策。

  8. 決策結果:

    1. 允許訪問: 調用?FilterChain.doFilter(),請求最終到達你的 Controller,返回響應。

    2. 拒絕訪問: 拋出?AccessDeniedException

  9. 異常處理:?ExceptionTranslationFilter?捕獲到異常:

    1. 如果是?AuthenticationException?(認證失敗,用戶未知),啟動認證流程:清除?SecurityContext,調用?AuthenticationEntryPoint(例如:重定向到登錄頁或返回 WWW-Authenticate 頭)。

    2. 如果是?AccessDeniedException?(授權失敗,權限不足),拒絕訪問:調用?AccessDeniedHandler(例如:返回 403 錯誤頁面)。

  10. 清理上下文: 請求處理完畢,SecurityContextPersistenceFilter?將?SecurityContext?保存回 Session(如果需要),并清空?ThreadLocal

2.2 核心組成

2.2.1?過濾器鏈 (Filter Chain) - 心臟

這是 Spring Security 最核心的概念。整個安全機制都構建在 Servlet 規范定義的?Filter?之上。當一個 HTTP 請求到來時,它會經過一個由多個安全過濾器組成的鏈條。

核心過濾器(按典型順序):

  1. ChannelProcessingFilter: 決定是否需要重定向到 HTTPS 或 HTTP。

  2. SecurityContextPersistenceFilter:?至關重要。在請求開始時,從配置的?SecurityContextRepository(默認是?HttpSessionSecurityContextRepository)中讀取?SecurityContext(安全上下文,包含用戶認證信息),并將其設置到?SecurityContextHolder?中;在請求結束后,清空?SecurityContextHolder,并可能將?SecurityContext?保存回會話。

  3. CorsFilter: 處理跨域請求 (CORS)。

  4. CsrfFilter: 提供跨站請求偽造 (CSRF) 保護。

  5. LogoutFilter: 匹配退出登錄的 URL(如?/logout),處理用戶退出邏輯,清除認證信息。

  6. UsernamePasswordAuthenticationFilter:?核心認證過濾器。嘗試處理表單登錄請求。它從 POST 請求中提取用戶名和密碼,創建一個?UsernamePasswordAuthenticationToken(一個?Authentication?接口的實現)并進行認證。

  7. DefaultLoginPageGeneratingFilter: 如果沒有配置登錄頁面,這個過濾器會生成一個默認的登錄頁。

  8. DefaultLogoutPageGeneratingFilter: 生成默認的退出頁面。

  9. BasicAuthenticationFilter: 處理 HTTP Basic 認證頭。

  10. RequestCacheAwareFilter: 用于在用戶認證成功后,恢復因登錄而中斷的原始請求。

  11. SecurityContextHolderAwareRequestFilter: 包裝原始的?HttpServletRequest,提供一些 Spring Security 特有的方法,如?getRemoteUser(),?isUserInRole()?等。

  12. AnonymousAuthenticationFilter:?至關重要。如果此時?SecurityContextHolder?中還沒有認證信息(即用戶未登錄),它會創建一個匿名的?Authentication?對象(AnonymousAuthenticationToken)并放入其中。這確保了安全上下文中永遠有一個?Authentication?對象,避免了空指針異常,統一了“已認證”和“未認證”的處理邏輯。

  13. SessionManagementFilter: 處理會話相關的策略,如同一個用戶的會話數量控制(防止同一賬號多次登錄)。

  14. ExceptionTranslationFilter:?至關重要。它是整個過濾器鏈的“看門人”,負責捕獲后續過濾器(特別是?FilterSecurityInterceptor)拋出的異常,并將其轉換為相應的行為(如重定向到登錄頁、返回 403 錯誤等)。它本身不進行認證或授權。

  15. FilterSecurityInterceptor:?最終大門。這是授權發生的地方。它從?SecurityContextHolder?中獲取已認證的?Authentication?對象,然后根據配置的權限規則(訪問屬性配置,如?hasRole(‘ADMIN’)),決定是允許請求繼續(調用?FilterChain.doFilter())還是拒絕訪問(拋出?AccessDeniedException)。

工作流程簡化視圖:
HTTP Request -> Filter1 -> Filter2 -> ... -> FilterSecurityInterceptor -> DispatcherServlet -> Your Controller

2.2.2?認證 (Authentication) 核心組件

  • Authentication?接口: 代表一個認證請求或一個已認證的主體(用戶)。它包含:

    • principal: 主體標識,通常是用戶名、UserDetails 對象或用戶ID。

    • credentials: 憑證,通常是密碼。認證成功后通常會擦除。

    • authorities: 權限集合,即?GrantedAuthority?對象列表。

  • SecurityContext?接口: 持有?Authentication?對象。SecurityContextHolder.getContext().getAuthentication()?是獲取當前用戶信息的標準方式。

  • SecurityContextHolder: 存儲?SecurityContext?的策略容器。默認使用?ThreadLocal?策略,這意味著每個線程都有自己的?SecurityContext,從而保證了用戶請求之間的隔離。

  • AuthenticationManager: 認證的入口/大門。它只有一個方法:authenticate(Authentication authentication)。你通常不會直接使用它。

  • ProviderManager:?AuthenticationManager?最常用的實現。它本身不處理認證,而是委托給一個?AuthenticationProvider?列表。它會遍歷這個列表,直到有一個?Provider?能夠處理當前的?Authentication?類型。

  • AuthenticationProvider: 執行具體認證邏輯的組件。例如:

    • DaoAuthenticationProvider: 最常用的 Provider,從數據庫(DAO)中獲取用戶信息進行認證。它需要依賴一個?UserDetailsService

    • JwtAuthenticationProvider: 用于處理 JWT Token 認證。

    • LdapAuthenticationProvider: 用于 LDAP 認證。

  • UserDetailsService: 核心接口,只有一個方法?loadUserByUsername(String username)。它負責從存儲系統(數據庫、內存等)中根據用戶名加載用戶信息,并返回一個?UserDetails?對象。這是你需要自定義實現的最常見接口。

  • UserDetails: 接口,代表從系統存儲中加載出來的用戶信息,包括用戶名、密碼、權限、賬戶是否過期等。框架提供的實現是?User

認證數據流:
UsernamePasswordAuthenticationFilter?-> 創建?UsernamePasswordAuthenticationToken?(未認證) -> 調用?ProviderManager.authenticate()?-> 委托給?DaoAuthenticationProvider?-> 調用?UserDetailsService.loadUserByUsername()?-> 獲取?UserDetails?-> 比較密碼 -> 認證成功 -> 返回一個已認證的?Authentication?對象 -> 被過濾器設置到?SecurityContextHolder?中。

2.2.3?授權 (Authorization) 核心組件

  • AccessDecisionManager: 授權的決策管理器。它通過輪詢一組?AccessDecisionVoter?并進行投票,最終根據投票策略決定是否允許訪問。

  • AccessDecisionVoter: 投票器。它檢查當前用戶的?Authentication?和受保護對象所需的配置屬性(ConfigAttribute,如?ROLE_ADMIN),然后投贊成、反對或棄權票。

  • ConfigAttribute: 保存著訪問受保護資源(如一個URL)所需的權限信息。通常來自你的配置:.antMatchers("/admin/**").hasRole("ADMIN")?中的?hasRole("ADMIN")?就是一個?ConfigAttribute

  • FilterSecurityInterceptor: 如上所述,它是授權發生的觸發器。它調用?AccessDecisionManager?進行決策。

授權數據流:
請求到達?FilterSecurityInterceptor?-> 獲取受保護資源的?ConfigAttribute?-> 調用?AccessDecisionManager.decide()?-> 輪詢所有?AccessDecisionVoter.vote()?-> 根據投票策略(如“一票否決”、“多數同意”)做出最終決定 -> 允許訪問或拋出?AccessDeniedException?-> 被上層的?ExceptionTranslationFilter?捕獲處理。

三、基本使用示例

需求:SpringBoot整合Spring Security頁面登陸,要求用戶信息存入數據庫,且密碼加密存儲,登錄成功后返回JWT令牌用于后續請求認證;要求體現不同用戶授予不同權限;要求必要的安全配置。

安全特性:

  • 密碼使用BCrypt加密存儲

  • 基于角色的訪問控制

  • JWT令牌認證,無狀態會話。完整的安全JWT流程

    • 登錄:用戶憑據驗證 → 生成簽名JWT

    • 傳輸:通過HTTPS傳輸 → 防止竊聽

    • 存儲:客戶端安全存儲 → 防止XSS

    • 使用:每個請求攜帶 → 認證用戶

    • 驗證:服務器驗證簽名和有效期 → 防止篡改

    • 注銷:客戶端刪除令牌 → 服務器可黑名單

  • CSRF保護禁用(因使用JWT)

  • 會話管理設置為無狀態

項目結構:

src/
├── main/
│   ├── java/com/example/demo/
│   │   ├── config/
│   │   │   ├── SecurityConfig.java
│   │   │   ├── JwtAuthenticationFilter.java
│   │   │   └── JwtUtil.java
│   │   ├── controller/
│   │   │   ├── AuthController.java
│   │   │   └── TestController.java
│   │   ├── entity/
│   │   │   ├── User.java
│   │   │   └── Role.java
│   │   ├── mapper/
│   │   │   └── UserMapper.java
│   │   ├── service/
│   │   │   ├── UserService.java
│   │   │   └── CustomUserDetailsService.java
│   │   └── DemoApplication.java
│   └── resources/
│       ├── application.properties
│       ├── schema.sql
│       └── mapper/UserMapper.xml

3.1?依賴配置 (pom.xml)

<?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>2.7.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>1.0.0</version><properties><java.version>11</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><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><version>0.11.5</version><scope>runtime</scope></dependency></dependencies>
</project>

3.2?應用配置 (application.properties)

# 服務器端口
server.port=8080# 數據庫配置
spring.datasource.url=jdbc:mysql://localhost:3306/security_demo?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# MyBatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.demo.entity# JWT密鑰
jwt.secret=mySecretKey
jwt.expiration=86400

密鑰配置:

  • 密鑰長度至少與哈希算法安全性要求一致(HS512建議至少64字節)

  • 生產環境應從安全配置源獲取密鑰(環境變量、密鑰管理服務)

  • 定期輪換密鑰

# 使用足夠長且復雜的密鑰
jwt.secret=mySuperLongAndComplexSecretKeyThatIsHardToGuess123!

雖然代碼中不直接體現,但部署時必須使用HTTPS,防止中間人攻擊,加密整個通信通道

# 生產環境應強制使用HTTPS
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.key-store-type=PKCS12

3.3?數據庫初始化 (schema.sql)

CREATE DATABASE IF NOT EXISTS security_demo;
USE security_demo;CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(100) NOT NULL,enabled BOOLEAN NOT NULL DEFAULT TRUE
);CREATE TABLE IF NOT EXISTS roles (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50) NOT NULL UNIQUE
);CREATE TABLE IF NOT EXISTS user_roles (user_id INT NOT NULL,role_id INT NOT NULL,PRIMARY KEY (user_id, role_id),FOREIGN KEY (user_id) REFERENCES users(id),FOREIGN KEY (role_id) REFERENCES roles(id)
);-- 插入角色數據
INSERT IGNORE INTO roles (name) VALUES ('ROLE_USER');
INSERT IGNORE INTO roles (name) VALUES ('ROLE_ADMIN');-- 插入用戶數據(密碼使用BCrypt加密,原始密碼均為"password")
INSERT IGNORE INTO users (username, password, enabled) VALUES 
('user', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 1),
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 1);-- 分配角色
INSERT IGNORE INTO user_roles (user_id, role_id) VALUES 
(1, 1), -- user has ROLE_USER
(2, 2); -- admin has ROLE_ADMIN

3.4 實體類

// User.java
package com.example.demo.entity;import java.util.List;public class User {private Long id;private String username;private String password;private Boolean enabled;private List<Role> roles;// 構造方法、getter和setterpublic User() {}public User(String username, String password) {this.username = username;this.password = password;}// 省略getter和setter
}// Role.java
package com.example.demo.entity;public class Role {private Long id;private String name;// 構造方法、getter和setterpublic Role() {}public Role(String name) {this.name = name;}// 省略getter和setter
}

3.5?MyBatis Mapper接口和XML

// UserMapper.java
package com.example.demo.mapper;import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {User findByUsername(String username);User findById(Long id);
}
<!-- UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper"><resultMap id="userResultMap" type="User"><id property="id" column="id" /><result property="username" column="username" /><result property="password" column="password" /><result property="enabled" column="enabled" /><collection property="roles" ofType="Role"><id property="id" column="role_id" /><result property="name" column="role_name" /></collection></resultMap><select id="findByUsername" resultMap="userResultMap">SELECT u.*, r.id as role_id, r.name as role_nameFROM users uLEFT JOIN user_roles ur ON u.id = ur.user_idLEFT JOIN roles r ON ur.role_id = r.idWHERE u.username = #{username}</select><select id="findById" resultMap="userResultMap">SELECT u.*, r.id as role_id, r.name as role_nameFROM users uLEFT JOIN user_roles ur ON u.id = ur.user_idLEFT JOIN roles r ON ur.role_id = r.idWHERE u.id = #{id}</select>
</mapper>

3.6 服務層

// CustomUserDetailsService.java
package com.example.demo.service;import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.stream.Collectors;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("用戶不存在: " + username);}List<GrantedAuthority> authorities = user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);}
}// UserService.java
package com.example.demo.service;import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public User findByUsername(String username) {return userMapper.findByUsername(username);}
}

3.7?JWT工具類

package com.example.demo.config;import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.util.Date;/*** JWT工具類 - 負責JWT令牌的生成、解析和驗證* * 安全特性說明:* 1. 使用HMAC-SHA512算法進行簽名,確保令牌完整性* 2. 設置合理的過期時間,減少令牌泄露風險* 3. 從配置文件中讀取密鑰,便于管理和輪換* 4. 提供完整的異常處理,防止無效令牌導致系統異常*/
@Component
public class JwtUtil {// 從配置文件中注入JWT密鑰,生產環境應使用復雜且足夠長的密鑰@Value("${jwt.secret}")private String secret;// 從配置文件中注入JWT過期時間(秒)@Value("${jwt.expiration}")private long expiration;/*** 生成JWT令牌* * 安全考慮:* 1. 只包含必要信息(用戶名),不包含敏感數據* 2. 設置簽發時間和過期時間,控制令牌有效期* 3. 使用強加密算法(HS512)進行簽名* * @param authentication Spring Security認證對象* @return JWT令牌字符串*/public String generateToken(Authentication authentication) {// 從認證對象中獲取用戶信息UserDetails userDetails = (UserDetails) authentication.getPrincipal();Date now = new Date();// 計算過期時間:當前時間 + 配置的過期時間(轉換為毫秒)Date expiryDate = new Date(now.getTime() + expiration * 1000);// 構建JWT令牌return Jwts.builder().setSubject(userDetails.getUsername()) // 設置主題(用戶名).setIssuedAt(now)                     // 設置簽發時間.setExpiration(expiryDate)            // 設置過期時間.signWith(SignatureAlgorithm.HS512, secret) // 使用HS512算法和密鑰簽名.compact();                           // 生成緊湊的JWT字符串}/*** 從JWT令牌中提取用戶名* * 安全考慮:* 1. 驗證簽名確保令牌未被篡改* 2. 解析前不信任任何令牌內容* * @param token JWT令牌* @return 用戶名*/public String getUsernameFromToken(String token) {// 解析JWT令牌,驗證簽名并獲取聲明(Claims)Claims claims = Jwts.parser().setSigningKey(secret)                // 設置簽名密鑰.parseClaimsJws(token)                // 解析JWS(已簽名的JWT).getBody();                           // 獲取有效負載(Payload)// 返回主題(用戶名)return claims.getSubject();}/*** 驗證JWT令牌的有效性* * 安全考慮:* 1. 驗證簽名是否正確,防止偽造令牌* 2. 檢查令牌是否過期* 3. 捕獲所有可能異常,防止無效令牌導致系統異常* * @param token JWT令牌* @return 令牌是否有效*/public boolean validateToken(String token) {try {// 嘗試解析令牌,如果成功則說明令牌有效Jwts.parser().setSigningKey(secret).parseClaimsJws(token);return true;} catch (SignatureException ex) {// 簽名不匹配 - 令牌可能被篡改// 記錄日志但不拋出異常,避免信息泄露} catch (MalformedJwtException ex) {// 令牌格式錯誤 - 不是有效的JWT} catch (ExpiredJwtException ex) {// 令牌已過期 - 需要重新登錄獲取新令牌} catch (UnsupportedJwtException ex) {// 不支持的JWT令牌 - 可能使用了錯誤的算法} catch (IllegalArgumentException ex) {// JWT claims string is empty - 令牌為空}// 任何異常都意味著令牌無效return false;}
}

3.8 JWT認證過濾

package com.example.demo.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** JWT認證過濾器 - 處理每個請求的JWT認證* * 安全特性說明:* 1. 在每個請求前執行,確保所有請求都經過認證檢查* 2. 從Authorization頭中提取Bearer令牌* 3. 驗證令牌有效性并設置安全上下文* 4. 即使認證失敗也繼續過濾器鏈,確保公共接口可訪問*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserDetailsService userDetailsService;/*** 過濾器核心方法 - 處理每個HTTP請求* * 安全流程:* 1. 從請求中提取JWT令牌* 2. 驗證令牌有效性* 3. 如果有效,從令牌中提取用戶名并加載用戶詳情* 4. 設置安全上下文,供后續授權檢查使用* * @param request HTTP請求* @param response HTTP響應* @param filterChain 過濾器鏈*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {try {// 從HTTP請求中獲取JWT令牌String jwt = getJwtFromRequest(request);// 驗證令牌是否存在且有效if (StringUtils.hasText(jwt) && jwtUtil.validateToken(jwt)) {// 從有效令牌中提取用戶名String username = jwtUtil.getUsernameFromToken(jwt);// 從數據庫加載用戶詳細信息(包括權限)UserDetails userDetails = userDetailsService.loadUserByUsername(username);// 創建認證令牌,包含用戶詳情和權限UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 添加請求詳情(如IP地址、會話ID等)authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 將認證信息設置到安全上下文中,供后續授權檢查使用SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (Exception ex) {// 捕獲所有異常,避免因認證問題導致請求失敗// 記錄錯誤日志但繼續處理請求(某些接口可能允許匿名訪問)logger.error("Could not set user authentication in security context", ex);}// 繼續過濾器鏈處理(無論認證成功與否)filterChain.doFilter(request, response);}/*** 從HTTP請求中提取JWT令牌* * 安全考慮:* 1. 只接受Bearer類型的認證頭* 2. 移除"Bearer "前綴,獲取純令牌* * @param request HTTP請求* @return JWT令牌或null(如果不存在)*/private String getJwtFromRequest(HttpServletRequest request) {// 從Authorization頭獲取Bearer令牌String bearerToken = request.getHeader("Authorization");// 檢查令牌是否存在且格式正確(以"Bearer "開頭)if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {// 返回去掉"Bearer "前綴的純令牌return bearerToken.substring(7);}// 沒有找到有效令牌return null;}
}

3.9?Spring Security配置

package com.example.demo.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** Spring Security配置類 - 定義應用程序的安全策略* * 安全特性說明:* 1. 使用無狀態會話管理,適合RESTful API* 2. 配置密碼編碼器,確保密碼安全存儲* 3. 定義URL訪問規則,實現基于角色的訪問控制* 4. 集成JWT認證過濾器,替代默認的表單登錄*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomUserDetailsService userDetailsService;@Autowiredprivate JwtUtil jwtUtil;/*** 密碼編碼器Bean - 用于密碼加密和驗證* * 安全考慮:* 1. 使用BCrypt強哈希算法,自動處理鹽值* 2. 適合密碼存儲,抵抗彩虹表攻擊* * @return BCrypt密碼編碼器實例*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 認證管理器Bean - 暴露給其他組件使用* * 用途:* 1. 在AuthController中用于手動認證用戶* 2. 可以被其他需要認證服務的組件使用*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 配置認證管理器 - 設置自定義用戶詳情服務和密碼編碼器* * 安全流程:* 1. 使用自定義UserDetailsService從數據庫加載用戶信息* 2. 使用BCrypt密碼編碼器驗證密碼*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}/*** 配置HTTP安全策略 - 核心安全配置方法* * 安全策略:* 1. 禁用CORS和CSRF(因使用無狀態JWT認證)* 2. 使用無狀態會話管理* 3. 配置URL訪問規則(基于角色)* 4. 添加JWT認證過濾器*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 啟用CORS并禁用CSRF(因使用JWT而非Cookie).cors().and().csrf().disable()// 會話管理設置為無狀態(不創建和使用HTTP會話).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 配置請求授權規則.authorizeRequests().antMatchers("/api/auth/**").permitAll()      // 認證接口允許匿名訪問.antMatchers("/api/user/**").hasRole("USER")  // 用戶接口需要USER角色.antMatchers("/api/admin/**").hasRole("ADMIN") // 管理員接口需要ADMIN角色.anyRequest().authenticated()                 // 其他所有請求需要認證.and();// 添加JWT認證過濾器到UsernamePasswordAuthenticationFilter之前http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}/*** 創建JWT認證過濾器Bean* * 說明:* 1. 過濾器在每個請求前執行* 2. 負責提取和驗證JWT令牌* 3. 設置安全上下文中的認證信息* * @return JWT認證過濾器實例*/@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter();}
}

3.10?控制器

// AuthController.java
package com.example.demo.controller;import com.example.demo.config.JwtUtil;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/api/auth")
public class AuthController {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserService userService;@PostMapping("/login")public ResponseEntity<?> login(@RequestBody Map<String, String> loginRequest) {String username = loginRequest.get("username");String password = loginRequest.get("password");Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));SecurityContextHolder.getContext().setAuthentication(authentication);String jwt = jwtUtil.generateToken(authentication);User user = userService.findByUsername(username);Map<String, Object> response = new HashMap<>();response.put("token", jwt);response.put("user", user);return ResponseEntity.ok(response);}
}// TestController.java
package com.example.demo.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api")
public class TestController {@GetMapping("/user/test")@PreAuthorize("hasRole('USER')")public String userAccess() {return "用戶內容";}@GetMapping("/admin/test")@PreAuthorize("hasRole('ADMIN')")public String adminAccess() {return "管理員內容";}
}

3.11 主應用類

// DemoApplication.java
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

3.12 測試

登錄獲取令牌:

POST http://localhost:8080/api/auth/login
Content-Type: application/json{"username": "user","password": "password"
}

訪問用戶API:

GET http://localhost:8080/api/user/test
Authorization: Bearer <your_token>

訪問管理員API:

GET http://localhost:8080/api/admin/test
Authorization: Bearer <your_token>

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

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

相關文章

跨境云手機與傳統手機的不同之處

傳統手機主要滿足個人日常生活中的通訊、娛樂、辦公等基礎需求&#xff0c;比如用于日常打電話聯系親朋好友&#xff0c;閑暇時刷短視頻、玩本地安裝的游戲&#xff0c;或者簡單處理一些文檔、郵件等辦公事務。跨境云手機主要是側重于跨境業務場景&#xff0c;對于從事跨境電商…

MemGPT: Towards LLMs as Operating Systems

1 MemGPT: Towards LLMs as Operating Systems 論文地址&#xff1a;MemGPT: Towards LLMs as Operating Systems 代碼地址&#xff1a;https://github.com/letta-ai/letta 1.1 MemGPT MemGPT&#xff08;MemoryGPT&#xff09;借鑒傳統操作系統的分層內存管理思想&#xff08;…

MICAPS:氣象信息綜合分析與處理系統概述

1.概述 說明:Meteorological Information Comprehensive Analysis and Process System 中文意思:氣象信息綜合分析處理系統。它是中國氣象局開發的一套氣象數據分析、處理和可視化系統,用于氣象資料的收集、整理、分析和發布。 2.MICAPS 的用途 說明: 數據收集:接收來自…

MySQL-day2_02

MySQL-day2&#xff08;四&#xff09;排序&#xff08;五&#xff09;聚合函數一、count 總記錄數二、max 最大值三、min 最小值四、sum 求和五、avg 平均值&#xff08;六&#xff09;數據分組一、分組二、分組后的數據篩選&#xff08;七&#xff09;數據分頁顯示一、獲取部…

HarmonyOS應用開發:深入ArkUI聲明式開發范式與最佳實踐

HarmonyOS應用開發&#xff1a;深入ArkUI聲明式開發范式與最佳實踐 引言 隨著HarmonyOS 4.0的發布及API 12的推出&#xff0c;華為的分布式操作系統進入了全新的發展階段。ArkUI作為HarmonyOS應用開發的核心框架&#xff0c;其聲明式開發范式&#xff08;Declarative Paradigm&…

Claude-Flow AI協同開發:鉤子系統與 GitHub 集成

5.1 思維認知框架&#xff1a;從“開發助手”到“DevOps 智能體” 在此之前&#xff0c;我們將 Claude-Flow 視為一個強大的 “開發助手 (Development Assistant)” &#xff0c;它在編碼、測試、重構等環節為我們提供支持。現在&#xff0c;我們需要再次進行思維升級&#xff…

DigitalOcean Kubernetes 現已支持 Gateway API 托管服務

在 DigitalOcean Kubernetes 集群中管理流量&#xff0c;一直以來主要依賴 Ingress。雖然能滿足基本需求&#xff0c;但在靈活性、角色分離和高級路由方面仍存在局限。今天&#xff0c;我們很高興迎來新的改變。 我們正式宣布&#xff0c;Kubernetes Gateway API 托管服務現已…

聚銘網絡入選數世咨詢《中國數字安全價值圖譜》“日志審計”推薦企業

近日&#xff0c;國內知名數字安全咨詢機構數世咨詢正式發布《中國數字安全價值圖譜》。聚銘網絡憑借領先的技術實力與出色的市場表現&#xff0c;成功入選“日志審計”領域重點推薦企業&#xff0c;彰顯了在該賽道的專業認可與品牌影響力。關于《中國數字安全價值圖譜》 在當下…

豆包、Kimi、通義千問、DeepSeek、Gamma、墨刀 AI”六款主流大模型(或 AI 平臺)生成 PPT 的完整流程

、先厘清 3 個概念&#xff0c;少走彎路大模型 ≠ PPT 軟件豆包、Kimi、通義千問、DeepSeek 本身只負責“出大綱/出文案”&#xff0c;真正的“一鍵配圖排版”要靠官方 PPT 助手或第三方平臺&#xff08;博思 AiPPT、迅捷 AiPPT、Gamma、墨刀 AI 等&#xff09;。兩條主流技術路…

Redis哈希(Hash):適合存儲對象的數據結構,優勢與坑點解析

Redis哈希&#xff08;Hash&#xff09;&#xff1a;適合存儲對象的數據結構&#xff0c;優勢與坑點解析 1. Redis哈希概述 1.1 什么是Redis哈希 Redis哈希&#xff08;Hash&#xff09;是一種映射類型&#xff08;Map&#xff09;&#xff0c;由多個字段值對&#xff08;fi…

Python的uv包管理工具使用

一、簡介 uv是一個繼Python版本管理、Python包管理、項目管理、虛擬環境管理于一體的工具&#xff0c;由于底層是用Rust編寫的&#xff0c;uv的執行速度非常快。 安裝 pip install uv鏡像源設置 uv默認安裝包是從pypi上下載的&#xff0c;速度比較慢。我們可以設置鏡像源&#…

JavaScript事件機制與性能優化:防抖 / 節流 / 事件委托 / Passive Event Listeners 全解析

目標&#xff1a;把“為什么慢、卡頓從哪來、該怎么寫”一次說清。本文先講事件傳播與主線程瓶頸&#xff0c;再給出四件法寶&#xff08;防抖、節流、事件委托、被動監聽&#xff09;&#xff0c;最后用一套可復制的工具函數 清單收尾。1&#xff09;先理解“為什么會卡”&am…

【Chrome】chrome 調試工具的network選項卡,如何同時過濾出doc js css

通過類型按鈕快速篩選&#xff08;更直觀&#xff09;在 Network 選項卡中&#xff0c;找到頂部的 資源類型按鈕欄&#xff08;通常在過濾器搜索框下方&#xff09;。按住 Ctrl 鍵&#xff08;Windows/Linux&#xff09;或 Command 鍵&#xff08;Mac&#xff09;&#xff0c;同…

Elasticsearch (ES)相關

在ES中&#xff0c;已經有Term Index&#xff0c;那還會走倒排索引嗎 你這個問題問得很到位 &#x1f44d;。我們分清楚 Term Index 和 倒排索引 在 Elasticsearch (ES) 里的關系&#xff1a;1. 倒排索引&#xff08;Inverted Index&#xff09; 是 Lucene/ES 檢索的核心。文檔…

pre-commit run --all-files 報錯:http.client.RemoteDisconnected

報錯完整信息初步原因是這樣 報錯是 Python 的 http.client.RemoteDisconnected&#xff0c;意思是 在用 urllib 請求遠程 URL 時&#xff0c;遠程服務器直接斷開了連接&#xff0c;沒有返回任何響應。在你的堆棧里&#xff0c;它出現在 pre-commit 嘗試安裝 Golang 環境的時候…

【C++】STL·List

1. list的介紹及使用 1.1list介紹 List文檔介紹 1.2 list的使用 list中的接口比較多&#xff0c;此處類似&#xff0c;只需要掌握如何正確的使用&#xff0c;然后再去深入研究背后的原理&#xff0c;已 達到可擴展的能力。以下為list中一些常見的重要接口。 1.2.1 list的構造…

圖論2 圖的數據結構表示

目錄 一 圖的數據結構表示 1 鄰接矩陣&#xff08;Adjacency Matrix&#xff09; 2 鄰接表&#xff08;Adjacency List&#xff09; 3 邊列表&#xff08;Edge List&#xff09; 4 十字鏈表&#xff08;Orthogonal List / Cross-linked List, 十字鏈表&#xff09; 5 鄰接…

在Excel中刪除大量間隔空白行

在 Excel 中刪除大量間隔空白行&#xff0c;可使用定位空值功能來快速實現。以下是具體方法&#xff1a;首先&#xff0c;選中包含空白行的數據區域。可以通過點擊數據區域的左上角單元格&#xff0c;然后按住鼠標左鍵拖動到右下角最后一個單元格來實現。接著&#xff0c;按下快…

【C 學習】10-循環結構

“知道做不到就是不知道”一、條件循環1. while只要條件為真&#xff08;true&#xff09;&#xff0c;就會重復執行循環體內的代碼。while (條件) {// 循環體&#xff08;要重復執行的代碼&#xff09; }//示例 int i 1; while (i < 5) {printf("%d\n", i);i; …

音視頻的下一站:協議編排、低時延工程與國標移動化接入的系統實踐

一、引言&#xff1a;音視頻的基礎設施化 過去十年&#xff0c;音視頻的兩條主線清晰可辨&#xff1a; 娛樂驅動&#xff1a;直播、電商、短視頻把“實時觀看與互動”變成高頻日常。 行業擴展&#xff1a;教育、會議、安防、政務逐步把“可用、可管、可控”引入產業系統。 …