Spring Security 是一個強大且可擴展的框架,用於保護 Java 應用程式,尤其是基於 Spring 的應用。它提供了身份驗證(驗證用戶身份)、授權(管理用戶許可權)和防護機制(如 CSRF 保護和防止會話劫持)等功能。 Spring Security 允許開發者通過靈活的配置實現安全控制 ...
Spring Security 是一個強大且可擴展的框架,用於保護 Java 應用程式,尤其是基於 Spring 的應用。它提供了身份驗證(驗證用戶身份)、授權(管理用戶許可權)和防護機制(如 CSRF 保護和防止會話劫持)等功能。
Spring Security 允許開發者通過靈活的配置實現安全控制,確保應用程式的數據和資源安全。通過與其他 Spring 生態系統的無縫集成,Spring Security 成為構建安全應用的理想選擇。
核心概念
- 身份驗證 (Authentication): 驗證用戶的身份(例如,用戶名/密碼)。
- 授權 (Authorization): 確定用戶是否有許可權訪問特定資源。
- 安全上下文 (Security Context): 存儲已認證用戶的詳細信息,應用程式中可以訪問。
1、準備工作
1.1 引入依賴
當我們引入 security
依賴後,訪問需要授權的 url 時,會重定向到 login
頁面(security 自己創建的),login
頁面需要賬號密碼,賬號預設是 user
, 密碼是隨機的字元串,在spring項目的輸出信息中
-
spring-boot-starter-security
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.3.4</version> </dependency>
-
jjwt-api
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.6</version> </dependency>
-
jjwt-impl
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency>
-
jjwt-jackson
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency>
一般我們會創建一個 SecurityConfig
類,來管理我們所有與 security
相關的配置。(我們講的是 security 5.7 版本之後的配置方法,之前的方法跟現在不太一樣)
@Configuration
@EnableWebSecurity // 該註解啟用 Spring Security 的 web 安全功能。
public class SecurityConfig {
}
下麵的都要寫到 SecurityConfig
類中
1.2 用戶認證的配置
基於記憶體的用戶認證
通過 createUser
, manager 把用戶配置的賬號密碼添加到spring的記憶體中, InMemoryUserDetailsManager
類中有一個 loadUserByUsername
的方法通過賬號(username)從記憶體中獲取我們配置的賬號密碼,之後調用其他方法來判斷前端用戶輸入的密碼和記憶體中的密碼是否匹配。
@Bean
public UserDetailsService userDetailsService() {
// 創建基於記憶體的用戶信息管理器
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
// 創建UserDetails對象,用於管理用戶名、用戶密碼、用戶角色、用戶許可權等內容
User.withDefaultPasswordEncoder().username("user").password("user123").roles("USER").build()
);
// 如果自己配置的有賬號密碼, 那麼上面講的 user 和 隨機字元串 的預設密碼就不能用了
return manager;
}
當我們點進 InMemoryUserDetailsManager
中 可以發現它實現了 UserDetailsManager
和 UserDetailsPasswordService
介面,其中 UserDetailsManager
介面繼承的 UserDetailsService
介面中就有 loadUserByUsername
方法
基於資料庫的用戶認證
上面講到,spring security 是通過 loadUserByUsername
方法來獲取 User
並用這個 User
來判斷用戶輸入的密碼是否正確。所以我們只需要繼承 UserDetailsService
介面並重寫 loadUserByUsername
方法即可
下麵的樣例我用的 mybatis-plus 來查詢資料庫中的 user
, 然後通過當前查詢到的 user
返回特定的 UserDetails
對象
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.eq("username", username); // 這裡不止可以用username,你可以自定義,主要根據你自己寫的查詢邏輯
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new UserDetailsImpl(user); // UserDetailsImpl 是我們實現的類
}
}
UserDetailsImpl
是實現了 UserDetails
介面的類。UserDetails
介面是 Spring Security 身份驗證機制的基礎,通過實現該介面,開發者可以定義自己的用戶模型,並提供用戶相關的信息,以便進行身份驗證和許可權檢查。
@Data
@AllArgsConstructor
@NoArgsConstructor // 這三個註解可以幫我們自動生成 get、set、有參、無參構造函數
public class UserDetailsImpl implements UserDetails {
private User user; // 通過有參構造函數填充賦值的
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() { // 檢查賬戶是否 沒過期。
return true;
}
@Override
public boolean isAccountNonLocked() { // 檢查賬戶是否 沒有被鎖定。
return true;
}
@Override
public boolean isCredentialsNonExpired() { //檢查憑據(密碼)是否 沒過期。
return true;
}
@Override
public boolean isEnabled() { // 檢查賬戶是否啟用。
return true;
}
// 這個方法是 @Data註解 會自動幫我們生成,用來獲取 loadUserByUsername 中最後我們返回的創建UserDetailsImpl對象時傳入的User。
// 如果你的欄位包含 username和password 的話可以用強制類型轉換, 把 UserDetailsImpl 轉換成 User。如果不能強制類型轉換的話就需要用到這個方法了
public User getUser() {
return user;
}
}
1.3 基本的配置
下麵這個是 security
的預設配置。我們可以修改並把它加到spring容器中,完成我們特定的需求。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 開啟授權保護
.authorizeHttpRequests(authorize -> authorize
// 不需要認證的地址有哪些
.requestMatchers("/blog/**", "/public/**", "/about").permitAll() // ** 通配符
// 對所有請求開啟授權保護
.anyRequest().
// 已認證的請求會被自動授權
authenticated()
)
// 使用預設的登陸登出頁面進行授權登陸
.formLogin(Customizer.withDefaults())
// 啟用“記住我”功能的。允許用戶在關閉瀏覽器後,仍然保持登錄狀態,直到他們主動註銷或超出設定的過期時間。
.rememberMe(Customizer.withDefaults());
// 關閉 csrf CSRF(跨站請求偽造)是一種網路攻擊,攻擊者通過欺騙已登錄用戶,誘使他們在不知情的情況下向受信任的網站發送請求。
http.csrf(csrf -> csrf.disable());
return http.build();
}
關於上面放行路徑寫法的一些細節問題:
如果在 application.properities
中配置的有 server.servlet.context-path=/api
首碼的話,在放行路徑中不需要寫 /api
。
如果 @RequestMapping(value = "/test/")
中寫的是 /test/
, 那麼放行路徑必須也寫成 /test/
, (/test
)是不行的,反之亦然。
如果 @RequestMapping(value = "/test")
鏈接 /test
後面要加查詢字元的話(/test?type=0
),不要寫成 @RequestMapping(value = "/test/")
上面都是一些細節問題,但是遇到 bug 的時候不容易發現。(筆者初學時找了一個小時,.. .. .)
下麵這個是我常用的配置,配置了jwt,加密的類,過濾器啟用 jwt,而不是session
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(CsrfConfigurer::disable) // 基於token,不需要csrf
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 基於token,不需要session
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/login/", "/getPicCheckCode").permitAll() // 放行api
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
2、加密
Spring Security 提供了多種加密和安全機制來保護用戶的敏感信息,尤其是在用戶身份驗證和密碼管理方面。(我們只講預設的 BCryptPasswordEncoder
)
1. 密碼加密的重要性
在應用程式中,用戶密碼是最敏感的數據之一。為了防止密碼泄露,即使資料庫被攻擊者獲取,密碼也應以加密形式存儲。加密可以保護用戶的隱私,併在一定程度上增加安全性。
2. Spring Security 的加密機制
2.1 PasswordEncoder 介面
Spring Security 提供了 PasswordEncoder
介面,用於定義密碼的加密和驗證方法。主要有以下幾種實現:
- BCryptPasswordEncoder:基於 BCrypt 演算法,具有適應性和強大的加密強度。它可以根據需求自動調整加密的複雜性。
- NoOpPasswordEncoder:不執行加密,適用於開發和測試環境,不建議在生產環境中使用。
- Pbkdf2PasswordEncoder、Argon2PasswordEncoder 等:這些都是基於不同演算法的實現,具有不同的安全特性。
2.2 BCrypt 演算法
BCrypt 是一種安全的密碼哈希演算法,具有以下特點:
- 鹽值(Salt):每次加密時都會生成一個隨機鹽值,確保相同的密碼在每次加密時生成不同的哈希值。
- 適應性:通過增加計算複雜性(如工作因數),可以提高密碼的加密強度。
3. 使用 PasswordEncoder
3.1 配置 PasswordEncoder
可以在 Spring Security 的配置類中定義 PasswordEncoder
bean,例如:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
// 也可用有參構造,取值範圍是 4 到 31,預設值為 10。數值越大,加密計算越複雜
return new BCryptPasswordEncoder();
}
}
3.2 加密密碼
在註冊用戶或更新密碼時,可以使用 PasswordEncoder
來加密密碼:
@Autowired
private PasswordEncoder passwordEncoder;
public void registerUser(String username, String rawPassword) {
String encryptedPassword = passwordEncoder.encode(rawPassword);
// 保存用戶信息到資料庫,包括加密後的密碼
}
3.3 驗證密碼
在用戶登錄時,可以使用 matches
方法驗證輸入的密碼與存儲的加密密碼是否匹配:
public boolean login(String username, String rawPassword) {
// 從資料庫中獲取用戶信息,包括加密後的密碼
String storedEncryptedPassword = // 從資料庫獲取;
return passwordEncoder.matches(rawPassword, storedEncryptedPassword);
}
3、前後端分離
1. 用戶認證流程
1.1 用戶登錄
-
前端:
- 用戶在登錄界面輸入用戶名和密碼。
- 前端將這些憑證以 JSON 格式發送到後端的登錄 API(例如
POST /api/login
)。
-
後端:
- Spring Security 接收請求,使用
AuthenticationManager
進行身份驗證。 - 如果認證成功,後端生成一個 JWT(JSON Web Token)或其他認證令牌,並將其返回給前端。
- Spring Security 接收請求,使用
2. 使用 JWT 進行用戶認證
2.1 前端存儲 JWT
- 前端收到 JWT 後,可以將其存儲在
localStorage
或sessionStorage
中,以便後續請求使用。
2.2 發送受保護請求
- 在發送需要認證的請求時,前端將 JWT 添加到請求頭中:
fetch('/api/protected-endpoint', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
2.3 後端解析 JWT
- Spring Security 通過過濾器來解析和驗證 JWT。可以自定義一個
OncePerRequestFilter
以攔截請求,提取 JWT,並驗證其有效性。
3. 退出登錄
由於 JWT 是無狀態的,後端不需要記錄會話狀態。用戶可以通過前端操作(例如,刪除存儲的 JWT)來“退出登錄”。可以實現一個註銷介面,用於前端執行相關邏輯。
4. 保護敏感信息
- 確保 HTTPS:在前後端通信中使用 HTTPS,確保傳輸中的數據安全。
- 令牌過期:設置 JWT 的有效期,過期後需要用戶重新登錄。
- 刷新令牌:可以實現刷新令牌的機制,以提高用戶體驗。
4. 實現 jwt
4.1 OncePerRequestFilter
-
作用:
OncePerRequestFilter
是 Spring Security 提供的一個抽象類,確保在每個請求中只執行一次特定的過濾邏輯。它是實現自定義過濾器的基礎,通常用於對請求進行預處理或後處理。(實現JWT
會用到這個介面) -
功能:提供了一種機制,以確保過濾器的邏輯在每個請求中只執行一次,非常適合需要對每個請求進行處理的場景。通過繼承該類,可以輕鬆實現自定義過濾器適合用於記錄日誌、身份驗證、許可權檢查等場景。
-
實現:繼承
OncePerRequestFilter
類,並重寫doFilterInternal
方法。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; public class CustomFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 自定義過濾邏輯,例如記錄請求日誌 System.out.println("Request URI: " + request.getRequestURI()); // 繼續執行過濾鏈 filterChain.doFilter(request, response); } }
- 註冊過濾器:可以在 Spring Security 配置類中註冊自定義的過濾器。
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class); // 其他配置... return http.build(); } }
4.2 生成 JWT
基於我們上面引入的三個 JWT
相關的依賴,寫JwtUtil
類
- 功能:
JwtUtil
類提供了生成和解析 JWT 的功能,是實現基於 JWT 的認證和授權的重要工具。 - 應用場景:可以在用戶登錄後生成 JWT,併在後續請求中攜帶 JWT 用於用戶身份驗證和許可權校驗。通過設置合適的過期時間,可以提高安全性。
@Component
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 有效期14天
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.id(uuid)
.subject(subject)
.issuer("sg")
.issuedAt(now)
.signWith(secretKey)
.expiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(jwt)
.getPayload();
}
}
1. 類註解與常量
@Component
:將這個類標記為 Spring 組件,允許 Spring 管理該類的生命周期,便於依賴註入。JWT_TTL
:JWT 的有效期,設置為 14 天(單位為毫秒)。JWT_KEY
:用於簽名的密鑰字元串,經過 Base64 編碼。它是生成和驗證 JWT 的關鍵。
2. UUID 生成
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
- 作用:生成一個唯一的 UUID,去掉了其中的連字元。這通常用作 JWT 的 ID(
jti
),確保每個 JWT 都是唯一的。
3. 創建 JWT
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
- 參數:
subject
是 JWT 的主題,通常是用戶的身份信息。 - 功能:調用
getJwtBuilder
方法生成一個 JWT 構建器,並將其壓縮為字元串返回。
4. JWT 構建器
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
//...
}
- 功能:生成一個 JWT 的構建器,配置 JWT 的各個屬性,包括:
setId(uuid)
:設置 JWT 的唯一標識。setSubject(subject)
:設置 JWT 的主題。setIssuer("sg")
:設置 JWT 的發行者,通常是你的應用或服務名稱。setIssuedAt(now)
:設置 JWT 的簽發時間。signWith(signatureAlgorithm, secretKey)
:使用指定的演算法和密鑰進行簽名。setExpiration(expDate)
:設置 JWT 的過期時間。
5. 生成密鑰
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
- 功能:將
JWT_KEY
進行 Base64 解碼,生成一個SecretKey
對象,用於 JWT 的簽名和驗證。使用 HMAC SHA-256 演算法進行簽名。
6. 解析 JWT
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody();
}
- 參數:
jwt
是要解析的 JWT 字元串。 - 功能:
- 使用之前生成的密鑰解析 JWT。
- 如果 JWT 有效,將返回 JWT 的聲明(
Claims
對象),其中包含了 JWT 的主題、發行者、過期時間等信息。 - 該方法可能會拋出異常,表示 JWT 無效或解析失敗。
4.3 應用 JWT
先加一個依賴 JetBrains Java Annotations
, 下麵的代碼會用到其中的一個註解 @NotNull
。
這個 JwtAuthenticationTokenFilter
類是一個實現了 OncePerRequestFilter
介面自定義的 Spring Security 過濾器。
- 功能:
JwtAuthenticationTokenFilter
通過 JWT 對用戶進行身份驗證,確保請求中攜帶的 JWT 是有效的,並根據 JWT 提供的用戶信息設置認證狀態。 - 應用場景:通常用於保護需要身份驗證的 API 介面,確保只有經過認證的用戶才能訪問資源。該過濾器通常放置在 Spring Security 過濾鏈的合適位置,以確保在處理請求之前進行身份驗證。
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用戶名未登錄");
}
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
// 如果是有效的jwt,那麼設置該用戶為認證後的用戶
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
1. 類註解與繼承
@Component
:標記該類為 Spring 組件,使其能夠被 Spring 掃描並註冊到應用上下文中。- 繼承
OncePerRequestFilter
:確保每個請求只調用一次該過濾器,適合進行請求預處理。
2. 依賴註入
@Autowired
private UserMapper userMapper;
UserMapper
:一個數據訪問對象(DAO),用於與資料庫交互,以便根據用戶 ID 查詢用戶信息。通過 Spring 的依賴註入機制自動註入。
3 獲取 JWT
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
- 從請求頭中獲取
Authorization
欄位的值,檢查是否包含 JWT。 - 如果沒有 JWT 或格式不正確,直接調用
filterChain.doFilter(request, response)
繼續執行下一個過濾器,返回。
4 解析 JWT
token = token.substring(7); // 去掉 "Bearer " 首碼
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
- 從 token 中去掉 "Bearer " 首碼,只保留 JWT 部分。
- 調用
JwtUtil.parseJWT(token)
解析 JWT,提取出用戶 ID (userid
)。如果解析失敗,會拋出異常。
5 查詢用戶信息
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用戶名未登錄");
}
- 根據解析出的用戶 ID 從資料庫中查詢用戶信息。如果用戶不存在,拋出異常。
6 設置安全上下文
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- 創建一個自定義的
UserDetailsImpl
對象,將查詢到的用戶信息封裝。 - 創建一個
UsernamePasswordAuthenticationToken
對象,表示用戶的認證信息,並將其設置到 Spring Security 的SecurityContextHolder
中,以便後續請求能夠訪問到用戶的認證信息。
4. 繼續過濾鏈
filterChain.doFilter(request, response);
- 調用
filterChain.doFilter(request, response)
,繼續執行後續的過濾器和最終的請求處理。
4.4 註冊自定義的 JwtAuthenticationTokenFilter
過濾器
把我們的過濾器應用到 spring security
中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
5、常用操作
5.1 配置 AuthenticationManager
將 AuthenticationManager
對象添加到spring容器中.(添加到我們創建的 SecurityConfig
配置類中)
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
5.2 驗證當前用戶
@Autowired
private AuthenticationManager authenticationManager;
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
// 從資料庫中對比查找,如果找到了會返回一個帶有認證的封裝後的用戶,否則會報錯,自動處理。(這裡我們假設我們配置的security是基於資料庫查找的)
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 獲取認證後的用戶
User user = (User ) authenticate.getPrincipal(); // 如果強制類型轉換報錯的話,可以用我們實現的 `UserDetailsImpl` 類中的 getUser 方法了
String jwt = JwtUtil.createJWT(user.getId().toString());
5.3 獲取當前用戶的認證信息
以下是獲取當前用戶認證信息的具體步驟:
// SecurityContextHolder 是一個存儲安全上下文的工具類,提供了一個全局訪問點,用於獲取當前請求的安全上下文。
// SecurityContext 是當前線程的安全上下文,包含了當前用戶的認證信息(即 Authentication 對象)。
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
User user = (User ) authenticate.getPrincipal();
// 另一種獲取 User 的方法
UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
User user = userDetails.getUser();
5.4 對比 authenticationManager
和 SecurityContext
獲取 Authentication
-
SecurityContextHolder.getContext().getAuthentication()
:- 這個方法獲取當前線程的安全上下文中的
Authentication
對象。 - 通常在用戶已經被認證後使用,用於獲取當前用戶的信息(如身份、許可權等)。
- 這個方法獲取當前線程的安全上下文中的
-
authenticationManager.authenticate(authenticationToken)
:- 這個方法是用於實際執行身份驗證的過程。
- 它接收一個憑證(如用戶名和密碼)並返回一個經過驗證的
Authentication
對象,或者拋出異常。 - 在用戶登錄或驗證時使用,屬於身份驗證的實際邏輯。
5.5 Authentication 介面
Authentication
介面包含以下重要方法:
getPrincipal()
:返回當前用戶的身份,通常是用戶的詳細信息(如用戶名)。getCredentials()
:返回用戶的憑證(如密碼),但通常不直接使用此方法。getAuthorities()
:返回用戶的許可權列表(角色或許可權)。
6、授權
Spring Security 的授權機制用於控制用戶對應用程式資源的訪問許可權。授權通常是基於用戶角色或許可權的,以下是對 Spring Security 授權的詳細講解。
1. 授權的基本概念
- 授權(Authorization):指的是決定用戶是否有許可權訪問特定資源的過程。在用戶認證成功後,系統會根據預定義的規則來判斷用戶是否可以訪問某些資源。
2. 授權的主要組成部分
2.1 許可權與角色
- 許可權(Permission):通常指對特定資源的操作能力,如“讀取”、“寫入”或“刪除”。
- 角色(Role):一組許可權的集合。例如,管理員角色可能具有所有許可權,而普通用戶角色可能只有讀取許可權。
2.2 GrantedAuthority 介面
- Spring Security 使用
GrantedAuthority
介面表示用戶的許可權。每個用戶的許可權可以通過實現該介面的對象來表示。
3. 授權方式
Spring Security 支持多種授權方式,主要包括:
3.1 基於角色的授權
- 定義角色:通常在用戶註冊或用戶管理中定義。
- 配置角色授權:可以在 Spring Security 配置類中設置基於角色的訪問控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 只有 ADMIN 角色可以訪問
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 和 ADMIN 角色可以訪問
.anyRequest().authenticated(); // 其他請求需要認證
}
3.2 基於許可權的授權
- 定義許可權:與角色類似,定義更細粒度的許可權。
- 配置許可權授權:在配置類中設置基於許可權的訪問控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE") // 僅具有 EDIT_PRIVILEGE 許可權的用戶可以訪問
.anyRequest().authenticated();
}
4. 自定義授權邏輯
如果需要更複雜的授權邏輯,可以實現自定義的 AccessDecisionVoter
或 AccessDecisionManager
。
4.1 AccessDecisionVoter
- 負責評估用戶的訪問請求,返回授權決策。
4.2 AccessDecisionManager
- 管理多個
AccessDecisionVoter
的調用,確定用戶是否有權訪問請求的資源。
7、其他自定義行為
以下介面和類用於處理不同的安全事件,提供了自定義處理的能力:
1. AuthenticationSuccessHandler
- 作用:用於處理用戶成功認證後的邏輯。
- 功能:可以自定義成功登錄後的跳轉行為,比如重定向到特定頁面、返回 JSON 響應等。
- 實現:實現
AuthenticationSuccessHandler
介面,並重寫onAuthenticationSuccess
方法。
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
}
// 讓我們實現的類生效
http.formLogin(form ->
form.successHandler(new MyAuthenticationSuccessHandler()));
2. AuthenticationFailureHandler
- 作用:用於處理用戶認證失敗的邏輯。
- 功能:可以自定義失敗登錄後的行為,比如返回錯誤信息、重定向到登錄頁面並顯示錯誤提示等。
- 實現:實現
AuthenticationFailureHandler
介面,並重寫onAuthenticationFailure
方法。
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
}
}
// 讓我們實現的類生效
http.formLogin(form ->
form.failureHandler(new MyAuthenticationFailureHandler()));
3. LogoutSuccessHandler
- 作用:用於處理用戶成功登出的邏輯。
- 功能:可以自定義註銷成功後的行為,比如重定向到登錄頁面、顯示註銷成功的消息等。
- 實現:實現
LogoutSuccessHandler
介面,並重寫onLogoutSuccess
方法。
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
}
// 讓我們實現的類生效
http.logout(logout -> {
logout.logoutSuccessHandler(new MyLogoutSuccessHandler());
});
4. AuthenticationEntryPoint
- 作用:用於處理未認證用戶訪問受保護資源時的邏輯。
- 功能:可以自定義未認證用戶的響應,比如返回 401 狀態碼、重定向到登錄頁面等。
- 實現:實現
AuthenticationEntryPoint
介面,並重寫commence
方法。
// 重寫 AuthenticationEntryPoint 介面
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
}
}
// 讓我們重寫的類生效
http.exceptionHandling(exception -> {
exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());
});
5. SessionInformationExpiredStrategy
- 作用:用於處理用戶會話過期的邏輯。
- 功能:可以自定義會話超時後的響應,比如重定向到登錄頁面或返回 JSON 響應等。
- 實現:實現
SessionInformationExpiredStrategy
介面,並重寫onExpiredSession
方法。
// 重寫 SessionInformationExpiredStrategy 介面
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
}
}
// 讓我們重寫的類生效
http.sessionManagement(session -> {
session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy());
});
6. AccessDeniedHandler
-
作用:用於處理用戶訪問被拒絕的情況。當用戶嘗試訪問沒有許可權的資源時,Spring Security 會調用實現了
AccessDeniedHandler
介面的處理器。 -
功能:可以自定義拒絕訪問後的響應行為,比如重定向到特定的錯誤頁面、返回錯誤信息或 JSON 響應。
-
實現:實現
AccessDeniedHandler
介面,並重寫handle
方法。
// 重寫 AccessDeniedHandler 介面
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
}
}
// 在安全配置中註冊自定義的 AccessDeniedHandler
http.exceptionHandling(exception -> {
exception.accessDeniedHandler(new MyAccessDeniedHandler());
});
});
文章到這裡就結束了~