亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

spring security 超詳細(xì)使用教程及如何接入springboot、前后端分離

 更新時間:2025年05月20日 11:08:50   作者:小lii  
Spring Security 是一個強大且可擴展的框架,用于保護(hù) Java 應(yīng)用程序,尤其是基于 Spring 的應(yīng)用,這篇文章主要介紹了spring security 超詳細(xì)使用教程及如何接入springboot、前后端分離,需要的朋友可以參考下

Spring Security 是一個強大且可擴展的框架,用于保護(hù) Java 應(yīng)用程序,尤其是基于 Spring 的應(yīng)用。它提供了身份驗證(驗證用戶身份)、授權(quán)(管理用戶權(quán)限)和防護(hù)機制(如 CSRF 保護(hù)和防止會話劫持)等功能。

Spring Security 允許開發(fā)者通過靈活的配置實現(xiàn)安全控制,確保應(yīng)用程序的數(shù)據(jù)和資源安全。通過與其他 Spring 生態(tài)系統(tǒng)的無縫集成,Spring Security 成為構(gòu)建安全應(yīng)用的理想選擇。

核心概念

  • 身份驗證 (Authentication): 驗證用戶的身份(例如,用戶名/密碼)。
  • 授權(quán) (Authorization): 確定用戶是否有權(quán)限訪問特定資源。
  • 安全上下文 (Security Context): 存儲已認(rèn)證用戶的詳細(xì)信息,應(yīng)用程序中可以訪問。

1、準(zhǔn)備工作

1.1 引入依賴

當(dāng)我們引入 security 依賴后,訪問需要授權(quán)的 url 時,會重定向到 login 頁面(security 自己創(chuàng)建的),login 頁面需要賬號密碼,賬號默認(rèn)是 user, 密碼是隨機的字符串,在spring項目的輸出信息中

spring-boot-starter-security

<!-- 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-api

<!-- 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>

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>

一般我們會創(chuàng)建一個 SecurityConfig 類,來管理我們所有與 security 相關(guān)的配置。(我們講的是 security 5.7 版本之后的配置方法,之前的方法跟現(xiàn)在不太一樣)

@Configuration
@EnableWebSecurity		// 該注解啟用 Spring Security 的 web 安全功能。
public class SecurityConfig {
}

下面的都要寫到 SecurityConfig 類中

1.2 用戶認(rèn)證的配置

基于內(nèi)存的用戶認(rèn)證

通過 createUser , manager 把用戶配置的賬號密碼添加到spring的內(nèi)存中, InMemoryUserDetailsManager 類中有一個 loadUserByUsername 的方法通過賬號(username)從內(nèi)存中獲取我們配置的賬號密碼,之后調(diào)用其他方法來判斷前端用戶輸入的密碼和內(nèi)存中的密碼是否匹配。

@Bean
public UserDetailsService userDetailsService() {
	// 創(chuàng)建基于內(nèi)存的用戶信息管理器
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(
	    // 創(chuàng)建UserDetails對象,用于管理用戶名、用戶密碼、用戶角色、用戶權(quán)限等內(nèi)容
	    User.withDefaultPasswordEncoder().username("user").password("user123").roles("USER").build()
    ); 		
    // 如果自己配置的有賬號密碼, 那么上面講的 user 和 隨機字符串 的默認(rèn)密碼就不能用了
    return manager;
}

當(dāng)我們點進(jìn) InMemoryUserDetailsManager 中 可以發(fā)現(xiàn)它實現(xiàn)了 UserDetailsManagerUserDetailsPasswordService 接口,其中 UserDetailsManager 接口繼承的 UserDetailsService 接口中就有 loadUserByUsername 方法

基于數(shù)據(jù)庫的用戶認(rèn)證

上面講到,spring security 是通過 loadUserByUsername 方法來獲取 User 并用這個 User 來判斷用戶輸入的密碼是否正確。所以我們只需要繼承 UserDetailsService 接口并重寫 loadUserByUsername 方法即可

下面的樣例我用的 mybatis-plus 來查詢數(shù)據(jù)庫中的 user, 然后通過當(dāng)前查詢到的 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,你可以自定義,主要根據(jù)你自己寫的查詢邏輯
        User user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        return new UserDetailsImpl(user);	// UserDetailsImpl 是我們實現(xiàn)的類
    }
}

UserDetailsImpl 是實現(xiàn)了 UserDetails 接口的類。UserDetails 接口是 Spring Security 身份驗證機制的基礎(chǔ),通過實現(xiàn)該接口,開發(fā)者可以定義自己的用戶模型,并提供用戶相關(guān)的信息,以便進(jìn)行身份驗證和權(quán)限檢查。

@Data
@AllArgsConstructor
@NoArgsConstructor	// 這三個注解可以幫我們自動生成 get、set、有參、無參構(gòu)造函數(shù)
public class UserDetailsImpl implements UserDetails {
    private User user;	// 通過有參構(gòu)造函數(shù)填充賦值的
    @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() {  //檢查憑據(jù)(密碼)是否 沒過期。
        return true;
    }
    @Override
    public boolean isEnabled() {    // 檢查賬戶是否啟用。
        return true;
    }
    // 這個方法是 @Data注解 會自動幫我們生成,用來獲取 loadUserByUsername 中最后我們返回的創(chuàng)建UserDetailsImpl對象時傳入的User。
    // 如果你的字段包含 username和password 的話可以用強制類型轉(zhuǎn)換, 把 UserDetailsImpl 轉(zhuǎn)換成 User。如果不能強制類型轉(zhuǎn)換的話就需要用到這個方法了
	public User getUser() {	
	        return user;	
	    }
	}

1.3 基本的配置

下面這個是 security 的默認(rèn)配置。我們可以修改并把它加到spring容器中,完成我們特定的需求。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            // 開啟授權(quán)保護(hù)
            .authorizeHttpRequests(authorize -> authorize
                    // 不需要認(rèn)證的地址有哪些
                    .requestMatchers("/blog/**", "/public/**", "/about").permitAll()	// ** 通配符
                    // 對所有請求開啟授權(quán)保護(hù)
                    .anyRequest().
                    // 已認(rèn)證的請求會被自動授權(quán)
                    authenticated()
            )
            // 使用默認(rèn)的登陸登出頁面進(jìn)行授權(quán)登陸
            .formLogin(Customizer.withDefaults())
            // 啟用“記住我”功能的。允許用戶在關(guān)閉瀏覽器后,仍然保持登錄狀態(tài),直到他們主動注銷或超出設(shè)定的過期時間。
            .rememberMe(Customizer.withDefaults());
    // 關(guān)閉 csrf CSRF(跨站請求偽造)是一種網(wǎng)絡(luò)攻擊,攻擊者通過欺騙已登錄用戶,誘使他們在不知情的情況下向受信任的網(wǎng)站發(fā)送請求。
    http.csrf(csrf -> csrf.disable());
    return http.build();
}

關(guān)于上面放行路徑寫法的一些細(xì)節(jié)問題:
如果在 application.properities 中配置的有 server.servlet.context-path=/api 前綴的話,在放行路徑中不需要寫 /api。
如果 @RequestMapping(value = "/test/") 中寫的是 /test/, 那么放行路徑必須也寫成 /test/, (/test)是不行的,反之亦然。
如果 @RequestMapping(value = "/test") 鏈接 /test 后面要加查詢字符的話(/test?type=0),不要寫成 @RequestMapping(value = "/test/")

上面都是一些細(xì)節(jié)問題,但是遇到 bug 的時候不容易發(fā)現(xiàn)。(筆者初學(xué)時找了一個小時,… … .)

1.4 常用配置

下面這個是我常用的配置,配置了jwt,加密的類,過濾器啟用 jwt,而不是session。(jwt的類會在下面講到)

@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 提供了多種加密和安全機制來保護(hù)用戶的敏感信息,尤其是在用戶身份驗證和密碼管理方面。(我們只講默認(rèn)的 BCryptPasswordEncoder

1. 密碼加密的重要性

在應(yīng)用程序中,用戶密碼是最敏感的數(shù)據(jù)之一。為了防止密碼泄露,即使數(shù)據(jù)庫被攻擊者獲取,密碼也應(yīng)以加密形式存儲。加密可以保護(hù)用戶的隱私,并在一定程度上增加安全性。

2. Spring Security 的加密機制

2.1 PasswordEncoder 接口

Spring Security 提供了 PasswordEncoder 接口,用于定義密碼的加密和驗證方法。主要有以下幾種實現(xiàn):

  • BCryptPasswordEncoder:基于 BCrypt 算法,具有適應(yīng)性和強大的加密強度。它可以根據(jù)需求自動調(diào)整加密的復(fù)雜性。
  • NoOpPasswordEncoder:不執(zhí)行加密,適用于開發(fā)和測試環(huán)境,不建議在生產(chǎn)環(huán)境中使用。
  • Pbkdf2PasswordEncoder、Argon2PasswordEncoder 等:這些都是基于不同算法的實現(xiàn),具有不同的安全特性。

2.2 BCrypt 算法

BCrypt 是一種安全的密碼哈希算法,具有以下特點:

  • 鹽值(Salt):每次加密時都會生成一個隨機鹽值,確保相同的密碼在每次加密時生成不同的哈希值。
  • 適應(yīng)性:通過增加計算復(fù)雜性(如工作因子),可以提高密碼的加密強度。

3. 使用 PasswordEncoder

3.1 配置 PasswordEncoder

可以在 Spring Security 的配置類中定義 PasswordEncoder bean,例如:

@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 也可用有參構(gòu)造,取值范圍是 4 到 31,默認(rèn)值為 10。數(shù)值越大,加密計算越復(fù)雜
        return new BCryptPasswordEncoder();	
    }
}

3.2 加密密碼

在注冊用戶或更新密碼時,可以使用 PasswordEncoder 來加密密碼:

@Autowired
private PasswordEncoder passwordEncoder;
public void registerUser(String username, String rawPassword) {
    String encryptedPassword = passwordEncoder.encode(rawPassword);
    // 保存用戶信息到數(shù)據(jù)庫,包括加密后的密碼
}

3.3 驗證密碼

在用戶登錄時,可以使用 matches 方法驗證輸入的密碼與存儲的加密密碼是否匹配:

public boolean login(String username, String rawPassword) {
    // 從數(shù)據(jù)庫中獲取用戶信息,包括加密后的密碼
    String storedEncryptedPassword = // 從數(shù)據(jù)庫獲取;
    return passwordEncoder.matches(rawPassword, storedEncryptedPassword);
}

3、前后端分離

1. 用戶認(rèn)證流程

1.1 用戶登錄

前端

  • 用戶在登錄界面輸入用戶名和密碼。
  • 前端將這些憑證以 JSON 格式發(fā)送到后端的登錄 API(例如 POST /api/login)。

后端

  • Spring Security 接收請求,使用 AuthenticationManager 進(jìn)行身份驗證。
  • 如果認(rèn)證成功,后端生成一個 JWT(JSON Web Token)或其他認(rèn)證令牌,并將其返回給前端。

2. 使用 JWT 進(jìn)行用戶認(rèn)證

2.1 前端存儲 JWT

  • 前端收到 JWT 后,可以將其存儲在 localStorage 或 sessionStorage 中,以便后續(xù)請求使用。

2.2 發(fā)送受保護(hù)請求

在發(fā)送需要認(rèn)證的請求時,前端將 JWT 添加到請求頭中:

fetch('/api/protected-endpoint', {
    method: 'GET',
    headers: {
        'Authorization': `Bearer ${token}`
    }
});

2.3 后端解析

JWT Spring Security 通過過濾器來解析和驗證 JWT??梢宰远x一個 OncePerRequestFilter 以攔截請求,提取 JWT,并驗證其有效性。

3. 退出登錄

由于 JWT 是無狀態(tài)的,后端不需要記錄會話狀態(tài)。用戶可以通過前端操作(例如,刪除存儲的 JWT)來“退出登錄”??梢詫崿F(xiàn)一個注銷接口,用于前端執(zhí)行相關(guān)邏輯。

4. 保護(hù)敏感信息

  • 確保 HTTPS:在前后端通信中使用 HTTPS,確保傳輸中的數(shù)據(jù)安全。
  • 令牌過期:設(shè)置 JWT 的有效期,過期后需要用戶重新登錄。
  • 刷新令牌:可以實現(xiàn)刷新令牌的機制,以提高用戶體驗。

4. 實現(xiàn) jwt

4.1 OncePerRequestFilter

  • 作用OncePerRequestFilter 是 Spring Security 提供的一個抽象類,確保在每個請求中只執(zhí)行一次特定的過濾邏輯。它是實現(xiàn)自定義過濾器的基礎(chǔ),通常用于對請求進(jìn)行預(yù)處理或后處理。(實現(xiàn) JWT 會用到這個接口)
  • 功能:提供了一種機制,以確保過濾器的邏輯在每個請求中只執(zhí)行一次,非常適合需要對每個請求進(jìn)行處理的場景。通過繼承該類,可以輕松實現(xiàn)自定義過濾器適合用于記錄日志、身份驗證、權(quán)限檢查等場景。
  • 實現(xiàn):繼承 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());
        // 繼續(xù)執(zhí)行過濾鏈
        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 相關(guān)的依賴,寫JwtUtil

  • 功能JwtUtil 類提供了生成和解析 JWT 的功能,是實現(xiàn)基于 JWT 的認(rèn)證和授權(quán)的重要工具。
  • 應(yīng)用場景:可以在用戶登錄后生成 JWT,并在后續(xù)請求中攜帶 JWT 用于用戶身份驗證和權(quán)限校驗。通過設(shè)置合適的過期時間,可以提高安全性。
@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:將這個類標(biāo)記為 Spring 組件,允許 Spring 管理該類的生命周期,便于依賴注入。
  • JWT_TTL:JWT 的有效期,設(shè)置為 14 天(單位為毫秒)。
  • JWT_KEY:用于簽名的密鑰字符串,經(jīng)過 Base64 編碼。它是生成和驗證 JWT 的關(guān)鍵。

2. UUID 生成

public static String getUUID() {
    return UUID.randomUUID().toString().replaceAll("-", "");
}
  • 作用:生成一個唯一的 UUID,去掉了其中的連字符。這通常用作 JWT 的 ID(jti),確保每個 JWT 都是唯一的。

3. 創(chuàng)建 JWT

public static String createJWT(String subject) {
    JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
    return builder.compact();
}
  • 參數(shù)subject 是 JWT 的主題,通常是用戶的身份信息。
  • 功能:調(diào)用 getJwtBuilder 方法生成一個 JWT 構(gòu)建器,并將其壓縮為字符串返回。

4. JWT 構(gòu)建器

private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
    //...
}

功能:生成一個 JWT 的構(gòu)建器,配置 JWT 的各個屬性,包括:

  • setId(uuid):設(shè)置 JWT 的唯一標(biāo)識。setSubject(subject):設(shè)置 JWT 的主題。
  • setIssuer("sg"):設(shè)置 JWT 的發(fā)行者,通常是你的應(yīng)用或服務(wù)名稱。
  • setIssuedAt(now):設(shè)置 JWT 的簽發(fā)時間。
  • signWith(signatureAlgorithm, secretKey):使用指定的算法和密鑰進(jìn)行簽名。
  • setExpiration(expDate):設(shè)置 JWT 的過期時間。

5. 生成密鑰

public static SecretKey generalKey() {
    byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
    return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}

功能:將 JWT_KEY 進(jìn)行 Base64 解碼,生成一個 SecretKey 對象,用于 JWT 的簽名和驗證。使用 HMAC SHA-256 算法進(jìn)行簽名。

6. 解析 JWT

public static Claims parseJWT(String jwt) throws Exception {
    SecretKey secretKey = generalKey();
    return Jwts.parserBuilder()
            .setSigningKey(secretKey)
            .build()
            .parseClaimsJws(jwt)
            .getBody();
}
  • 參數(shù)jwt 是要解析的 JWT 字符串。
  • 功能: 使用之前生成的密鑰解析 JWT。
  • 使用之前生成的密鑰解析 JWT。
  • 如果 JWT 有效,將返回 JWT 的聲明(Claims 對象),其中包含了 JWT 的主題、發(fā)行者、過期時間等信息。
  • 該方法可能會拋出異常,表示 JWT 無效或解析失敗。

4.3 應(yīng)用 JWT

先加一個依賴 JetBrains Java Annotations, 下面的代碼會用到其中的一個注解 @NotNull。

這個 JwtAuthenticationTokenFilter 類是一個實現(xiàn)了 OncePerRequestFilter 接口自定義的 Spring Security 過濾器。

  • 功能JwtAuthenticationTokenFilter 通過 JWT 對用戶進(jìn)行身份驗證,確保請求中攜帶的 JWT 是有效的,并根據(jù) JWT 提供的用戶信息設(shè)置認(rèn)證狀態(tài)。
  • 應(yīng)用場景:通常用于保護(hù)需要身份驗證的 API 接口,確保只有經(jīng)過認(rèn)證的用戶才能訪問資源。該過濾器通常放置在 Spring Security 過濾鏈的合適位置,以確保在處理請求之前進(jìn)行身份驗證。
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,那么設(shè)置該用戶為認(rèn)證后的用戶
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request, response);
    }
}

1. 類注解與繼承

  • @Component:標(biāo)記該類為 Spring 組件,使其能夠被 Spring 掃描并注冊到應(yīng)用上下文中。
  • 繼承 OncePerRequestFilter:確保每個請求只調(diào)用一次該過濾器,適合進(jìn)行請求預(yù)處理。

2. 依賴注入

@Autowired
private UserMapper userMapper;

UserMapper:一個數(shù)據(jù)訪問對象(DAO),用于與數(shù)據(jù)庫交互,以便根據(jù)用戶 ID 查詢用戶信息。通過 Spring 的依賴注入機制自動注入。

3 獲取 JWT

String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
    filterChain.doFilter(request, response);
    return;
}
  • 從請求頭中獲取 Authorization 字段的值,檢查是否包含 JWT。
  • 如果沒有 JWT 或格式不正確,直接調(diào)用 filterChain.doFilter(request, response) 繼續(xù)執(zhí)行下一個過濾器,返回。

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 部分。
  • 調(diào)用 JwtUtil.parseJWT(token) 解析 JWT,提取出用戶 ID (userid)。如果解析失敗,會拋出異常。

5 查詢用戶信息

User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
    throw new RuntimeException("用戶名未登錄");
}

根據(jù)解析出的用戶 ID 從數(shù)據(jù)庫中查詢用戶信息。如果用戶不存在,拋出異常。

6 設(shè)置安全上下文

UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  • 創(chuàng)建一個自定義的 UserDetailsImpl 對象,將查詢到的用戶信息封裝。
  • 創(chuàng)建一個 UsernamePasswordAuthenticationToken 對象,表示用戶的認(rèn)證信息,并將其設(shè)置到 Spring Security 的 SecurityContextHolder 中,以便后續(xù)請求能夠訪問到用戶的認(rèn)證信息。

4. 繼續(xù)過濾鏈

filterChain.doFilter(request, response);

調(diào)用 filterChain.doFilter(request, response),繼續(xù)執(zhí)行后續(xù)的過濾器和最終的請求處理。

4.4 注冊自定義的 JwtAuthenticationTokenFilter  過濾器

把我們的過濾器應(yīng)用到 spring security

http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

5、常用操作

5.1 配置 AuthenticationManager

AuthenticationManager 對象添加到spring容器中.(添加到我們創(chuàng)建的 SecurityConfig 配置類中)

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
    return authConfig.getAuthenticationManager();
}

5.2 驗證當(dāng)前用戶

@Autowired
private AuthenticationManager authenticationManager;
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
// 從數(shù)據(jù)庫中對比查找,如果找到了會返回一個帶有認(rèn)證的封裝后的用戶,否則會報錯,自動處理。(這里我們假設(shè)我們配置的security是基于數(shù)據(jù)庫查找的)
Authentication authenticate = authenticationManager.authenticate(authenticationToken);	
// 獲取認(rèn)證后的用戶
User user = (User ) authenticate.getPrincipal();	// 如果強制類型轉(zhuǎn)換報錯的話,可以用我們實現(xiàn)的 `UserDetailsImpl` 類中的 getUser 方法了
String jwt = JwtUtil.createJWT(user.getId().toString());

5.3 獲取當(dāng)前用戶的認(rèn)證信息

以下是獲取當(dāng)前用戶認(rèn)證信息的具體步驟:

// SecurityContextHolder 是一個存儲安全上下文的工具類,提供了一個全局訪問點,用于獲取當(dāng)前請求的安全上下文。
// SecurityContext 是當(dāng)前線程的安全上下文,包含了當(dāng)前用戶的認(rèn)證信息(即 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

SecurityContextHolder.getContext().getAuthentication():

  • 這個方法獲取當(dāng)前線程的安全上下文中的 Authentication 對象。
  • 通常在用戶已經(jīng)被認(rèn)證后使用,用于獲取當(dāng)前用戶的信息(如身份、權(quán)限等)。

authenticationManager.authenticate(authenticationToken):

  • 這個方法是用于實際執(zhí)行身份驗證的過程。
  • 它接收一個憑證(如用戶名和密碼)并返回一個經(jīng)過驗證的 Authentication 對象,或者拋出異常。
  • 在用戶登錄或驗證時使用,屬于身份驗證的實際邏輯。

5.5 Authentication 接口

Authentication 接口包含以下重要方法:

  • getPrincipal():返回當(dāng)前用戶的身份,通常是用戶的詳細(xì)信息(如用戶名)。
  • getCredentials():返回用戶的憑證(如密碼),但通常不直接使用此方法。
  • getAuthorities():返回用戶的權(quán)限列表(角色或權(quán)限)。

6、授權(quán)

Spring Security 的授權(quán)機制用于控制用戶對應(yīng)用程序資源的訪問權(quán)限。授權(quán)通常是基于用戶角色或權(quán)限的,以下是對 Spring Security 授權(quán)的詳細(xì)講解。

1. 授權(quán)的基本概念

授權(quán)(Authorization):指的是決定用戶是否有權(quán)限訪問特定資源的過程。在用戶認(rèn)證成功后,系統(tǒng)會根據(jù)預(yù)定義的規(guī)則來判斷用戶是否可以訪問某些資源。

2. 授權(quán)的主要組成部分

2.1 權(quán)限與角色

  • 權(quán)限(Permission):通常指對特定資源的操作能力,如“讀取”、“寫入”或“刪除”。
  • 角色(Role):一組權(quán)限的集合。例如,管理員角色可能具有所有權(quán)限,而普通用戶角色可能只有讀取權(quán)限。

2.2 GrantedAuthority 接口

  • Spring Security 使用 GrantedAuthority 接口表示用戶的權(quán)限。每個用戶的權(quán)限可以通過實現(xiàn)該接口的對象來表示。

3. 授權(quán)方式

Spring Security 支持多種授權(quán)方式,主要包括:

3.1 基于角色的授權(quán)

  • 定義角色:通常在用戶注冊或用戶管理中定義。
  • 配置角色授權(quán):可以在 Spring Security 配置類中設(shè)置基于角色的訪問控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/admin/**").hasRole("ADMIN")  // 只有 ADMIN 角色可以訪問
        .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")  // USER 和 ADMIN 角色可以訪問
        .anyRequest().authenticated();  // 其他請求需要認(rèn)證
}

3.2 基于權(quán)限的授權(quán)

  • 定義權(quán)限:與角色類似,定義更細(xì)粒度的權(quán)限。
  • 配置權(quán)限授權(quán):在配置類中設(shè)置基于權(quán)限的訪問控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE")  // 僅具有 EDIT_PRIVILEGE 權(quán)限的用戶可以訪問
        .anyRequest().authenticated();
}

4. 自定義授權(quán)邏輯

如果需要更復(fù)雜的授權(quán)邏輯,可以實現(xiàn)自定義的 AccessDecisionVoterAccessDecisionManager。

4.1 AccessDecisionVoter 負(fù)責(zé)評估用戶的訪問請求,返回授權(quán)決策。

4.2 AccessDecisionManager

管理多個 AccessDecisionVoter 的調(diào)用,確定用戶是否有權(quán)訪問請求的資源。

7、其他自定義行為

以下接口和類用于處理不同的安全事件,提供了自定義處理的能力:

1. AuthenticationSuccessHandler

  • 作用:用于處理用戶成功認(rèn)證后的邏輯。
  • 功能:可以自定義成功登錄后的跳轉(zhuǎn)行為,比如重定向到特定頁面、返回 JSON 響應(yīng)等。
  • 實現(xiàn):實現(xiàn) AuthenticationSuccessHandler 接口,并重寫 onAuthenticationSuccess 方法。
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    }
}
// 讓我們實現(xiàn)的類生效
http.formLogin(form ->
                form.successHandler(new MyAuthenticationSuccessHandler()));

2. AuthenticationFailureHandler

  • 作用:用于處理用戶認(rèn)證失敗的邏輯。
  • 功能:可以自定義失敗登錄后的行為,比如返回錯誤信息、重定向到登錄頁面并顯示錯誤提示等。
  • 實現(xiàn):實現(xiàn) AuthenticationFailureHandler 接口,并重寫 onAuthenticationFailure 方法。
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    }
}
// 讓我們實現(xiàn)的類生效
http.formLogin(form ->
        form.failureHandler(new MyAuthenticationFailureHandler()));

3. LogoutSuccessHandler

  • 作用:用于處理用戶成功登出的邏輯。
  • 功能:可以自定義注銷成功后的行為,比如重定向到登錄頁面、顯示注銷成功的消息等。
  • 實現(xiàn):實現(xiàn) LogoutSuccessHandler 接口,并重寫 onLogoutSuccess 方法。
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    }
}
// 讓我們實現(xiàn)的類生效
http.logout(logout -> {
               logout.logoutSuccessHandler(new MyLogoutSuccessHandler());
           });

4. AuthenticationEntryPoint

  • 作用:用于處理未認(rèn)證用戶訪問受保護(hù)資源時的邏輯。
  • 功能:可以自定義未認(rèn)證用戶的響應(yīng),比如返回 401 狀態(tài)碼、重定向到登錄頁面等。
  • 實現(xiàn):實現(xiàn) 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

  • 作用:用于處理用戶會話過期的邏輯。
  • 功能:可以自定義會話超時后的響應(yīng),比如重定向到登錄頁面或返回 JSON 響應(yīng)等。
  • 實現(xiàn):實現(xiàn) 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

  • 作用:用于處理用戶訪問被拒絕的情況。當(dāng)用戶嘗試訪問沒有權(quán)限的資源時,Spring Security 會調(diào)用實現(xiàn)了 AccessDeniedHandler 接口的處理器。
  • 功能:可以自定義拒絕訪問后的響應(yīng)行為,比如重定向到特定的錯誤頁面、返回錯誤信息或 JSON 響應(yīng)。
  • 實現(xiàn):實現(xiàn) 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());
        });
});

文章到這里就結(jié)束了~

到此這篇關(guān)于spring security 超詳細(xì)使用教程(接入springboot、前后端分離)的文章就介紹到這了,更多相關(guān)spring security 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何基于java或js獲取URL返回狀態(tài)碼

    如何基于java或js獲取URL返回狀態(tài)碼

    這篇文章主要介紹了如何基于java或js獲取URL返回狀態(tài)碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-11-11
  • java實現(xiàn)同步的幾種方式(示例詳解)

    java實現(xiàn)同步的幾種方式(示例詳解)

    這篇文章主要介紹了java實現(xiàn)同步的幾種方式,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2024-12-12
  • MybatisPlus分頁排序查詢字段帶有下劃線的坑及解決

    MybatisPlus分頁排序查詢字段帶有下劃線的坑及解決

    這篇文章主要介紹了MybatisPlus分頁排序查詢字段帶有下劃線的坑及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • java中的常用集合類整理

    java中的常用集合類整理

    本篇文章給大家?guī)淼膬?nèi)容是關(guān)于java中List集合及其實現(xiàn)類的方法介紹(附代碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。下面我們就來學(xué)習(xí)一下吧,希望能給你帶來幫助
    2021-06-06
  • java獲取當(dāng)前日期和時間的二種方法分享

    java獲取當(dāng)前日期和時間的二種方法分享

    這篇文章主要介紹了java獲取當(dāng)前日期和時間的二種方法,需要的朋友可以參考下
    2014-03-03
  • MyBatis利用攔截器實現(xiàn)數(shù)據(jù)脫敏詳解

    MyBatis利用攔截器實現(xiàn)數(shù)據(jù)脫敏詳解

    現(xiàn)代網(wǎng)絡(luò)環(huán)境中,敏感數(shù)據(jù)的處理是至關(guān)重要的,敏感數(shù)據(jù)包括個人身份信息、銀行賬號、手機號碼等,所以本文主要為大家詳細(xì)介紹了MyBatis如何利用攔截器實現(xiàn)數(shù)據(jù)脫敏,希望對大家有所幫助
    2023-11-11
  • idea中定時及多數(shù)據(jù)源配置方法

    idea中定時及多數(shù)據(jù)源配置方法

    因項目要求,需要定時從達(dá)夢數(shù)據(jù)庫中取數(shù)據(jù),并插入或更新到ORACLE數(shù)據(jù)庫中,這篇文章主要介紹了idea中定時及多數(shù)據(jù)源配置方法,需要的朋友可以參考下
    2023-12-12
  • Java異步編程之Callbacks與Futures模型詳解

    Java異步編程之Callbacks與Futures模型詳解

    這篇文章主要為大家詳細(xì)介紹了Java異步編程中Callbacks與Futures模型的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-03-03
  • Java使用POI生成Word文檔簡單代碼示例

    Java使用POI生成Word文檔簡單代碼示例

    Java?POI是一個用于操作Microsoft?Office格式文件的Java庫,包括?Word、Excel和PowerPoint等文件,這篇文章主要給大家介紹了關(guān)于Java使用POI生成Word文檔的相關(guān)資料,需要的朋友可以參考下
    2024-08-08
  • springboot如何接收復(fù)雜參數(shù)(同時接收J(rèn)SON與文件)

    springboot如何接收復(fù)雜參數(shù)(同時接收J(rèn)SON與文件)

    文章介紹了在Spring Boot中同時處理JSON和文件上傳時使用`@RequestPart`注解的方法,`@RequestPart`可以接收多種格式的參數(shù),包括JSON和文件,并且可以作為`multipart/form-data`格式中的key
    2025-02-02

最新評論