spring security 超詳細使用教程(接入springboot、前後端分離)

来源:https://www.cnblogs.com/xxctx/p/18441536
-Advertisement-
Play Games

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 中 可以發現它實現了 UserDetailsManagerUserDetailsPasswordService 介面,其中 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:不執行加密,適用於開發和測試環境,不建議在生產環境中使用。
  • Pbkdf2PasswordEncoderArgon2PasswordEncoder 等:這些都是基於不同演算法的實現,具有不同的安全特性。

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)或其他認證令牌,並將其返回給前端。

2. 使用 JWT 進行用戶認證

2.1 前端存儲 JWT

  • 前端收到 JWT 後,可以將其存儲在 localStoragesessionStorage 中,以便後續請求使用。

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 對比 authenticationManagerSecurityContext 獲取 Authentication

  1. SecurityContextHolder.getContext().getAuthentication():

    • 這個方法獲取當前線程的安全上下文中的 Authentication 對象。
    • 通常在用戶已經被認證後使用,用於獲取當前用戶的信息(如身份、許可權等)。
  2. 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. 自定義授權邏輯

如果需要更複雜的授權邏輯,可以實現自定義的 AccessDecisionVoterAccessDecisionManager

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());
        });
});

文章到這裡就結束了~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 可擴展的事件驅動處理 1.1. 使用消息傳遞系統進行通信,你可以創建松耦合的架構 1.1.1. 消息生產者只是將消息存儲在隊列中,而不用關心消費者如何處理消息 1.1.2. 有一個或多個消費者,並且生產者和消費者的集合可以隨著時間的推移而改變 1.1.3. 有助於提高服務響應能力、通過緩存消除 ...
  • 軟體的生命周期自從應用程式的上線和發版之後服務於客戶。程式員進入公司的項目組之後所接觸到的系統項目有二次開發中和從零開始搭建的項目。項目有項目組的開發和驗收周期。軟體的設計模式遵循瀑布模型和敏捷開發。瀑布模型的軟體設計模式在項目的一開始的搭建組成階段需要招攬各種層次的項目組成員。 瀑布模型把項目分為 ...
  • 測試工作在Java工程項目中的作用不可或缺。測試驅動和模型驅動以及迭代開發。項目的測試工作分為黑盒測試和白盒測試。黑盒測試並不會讓你知道很多讓你不應該知道的細節。白盒測試透明,項目組的開發人員也是不能觸碰。程式設計的編寫開發人員主要工作是編寫項目的源代碼,完成需求說明書分配下來的項目排期計劃。開發分 ...
  • 退休模式需求分析 退休模式2+1無限鏈動模式詳細分析: 退休模式是商城平臺基於投資者用戶開發的一套區塊鏈市場投資返利方式。 投資者用戶的註冊額度:499元 投資者的推薦費用:100元 1指的是老闆投資者 2 指的是老闆所推薦的代理投資者 2+1 無限鏈動模式的資金籌集方式 平臺註冊的投資者老闆推薦代 ...
  • Java中的操作日誌模塊的開發和運行維護都是十分耗時耗力。操作日誌的收集涉及到公司的項目或者是上市產品的用戶體驗和反饋。後端和前端開發工程師的日常工作就是對運行維護工程師收集回來的項目和產品的反饋進行系統級別的分析以及需求下發迭代開發。操作日誌的列印方式分為線下列印和線上的日誌列印。線下的系統操作日 ...
  • 版本:rustc 1.81.0 (eeb90cda1 2024-09-04) 報錯情況如下圖: 摸索了後,總結一下關鍵解決方法: 從微軟體官網: https://visualstudio.microsoft.com/zh-hans/downloads/ 找到選項“用於 Visual Studio 的 ...
  • Java中的Date 為什麼用類表示日期,而不是像其他語言中那樣用一個內置(built-in)類型來表示?例如,Visual Basic 中有一個內置的 date 類型,程式員可以採用#12/31/1999格式指定日期。看起來這似乎很方便,程式員只需要使用內置的 date 類型而不用考慮類。但實際上 ...
  • 五,MyBatis-Plus 當中的 “ActiveRecord模式”和“SimpleQuery工具類”(詳細實操) @目錄五,MyBatis-Plus 當中的 “ActiveRecord模式”和“SimpleQuery工具類”(詳細實操)1. ActiveRecord 模式2. ActiveRec ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...