spring security 超詳細(xì)使用教程及如何接入springboot、前后端分離
Spring Security 是一個(gè)強(qiáng)大且可擴(kuò)展的框架,用于保護(hù) Java 應(yīng)用程序,尤其是基于 Spring 的應(yīng)用。它提供了身份驗(yàn)證(驗(yàn)證用戶身份)、授權(quán)(管理用戶權(quán)限)和防護(hù)機(jī)制(如 CSRF 保護(hù)和防止會(huì)話劫持)等功能。
Spring Security 允許開發(fā)者通過靈活的配置實(shí)現(xiàn)安全控制,確保應(yīng)用程序的數(shù)據(jù)和資源安全。通過與其他 Spring 生態(tài)系統(tǒng)的無縫集成,Spring Security 成為構(gòu)建安全應(yīng)用的理想選擇。
核心概念
- 身份驗(yàn)證 (Authentication): 驗(yàn)證用戶的身份(例如,用戶名/密碼)。
- 授權(quán) (Authorization): 確定用戶是否有權(quán)限訪問特定資源。
- 安全上下文 (Security Context): 存儲(chǔ)已認(rèn)證用戶的詳細(xì)信息,應(yīng)用程序中可以訪問。
1、準(zhǔn)備工作
1.1 引入依賴
當(dāng)我們引入 security
依賴后,訪問需要授權(quán)的 url 時(shí),會(huì)重定向到 login
頁(yè)面(security 自己創(chuàng)建的),login
頁(yè)面需要賬號(hào)密碼,賬號(hào)默認(rèn)是 user
, 密碼是隨機(jī)的字符串,在spring項(xiàng)目的輸出信息中
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>
一般我們會(huì)創(chuàng)建一個(gè) 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 把用戶配置的賬號(hào)密碼添加到spring的內(nèi)存中, InMemoryUserDetailsManager
類中有一個(gè) loadUserByUsername
的方法通過賬號(hào)(username)從內(nèi)存中獲取我們配置的賬號(hào)密碼,之后調(diào)用其他方法來判斷前端用戶輸入的密碼和內(nèi)存中的密碼是否匹配。
@Bean public UserDetailsService userDetailsService() { // 創(chuàng)建基于內(nèi)存的用戶信息管理器 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser( // 創(chuàng)建UserDetails對(duì)象,用于管理用戶名、用戶密碼、用戶角色、用戶權(quán)限等內(nèi)容 User.withDefaultPasswordEncoder().username("user").password("user123").roles("USER").build() ); // 如果自己配置的有賬號(hào)密碼, 那么上面講的 user 和 隨機(jī)字符串 的默認(rèn)密碼就不能用了 return manager; }
當(dāng)我們點(diǎn)進(jìn) InMemoryUserDetailsManager
中 可以發(fā)現(xiàn)它實(shí)現(xiàn)了 UserDetailsManager
和 UserDetailsPasswordService
接口,其中 UserDetailsManager
接口繼承的 UserDetailsService
接口中就有 loadUserByUsername
方法
基于數(shù)據(jù)庫(kù)的用戶認(rèn)證
上面講到,spring security 是通過 loadUserByUsername
方法來獲取 User
并用這個(gè) User
來判斷用戶輸入的密碼是否正確。所以我們只需要繼承 UserDetailsService
接口并重寫 loadUserByUsername
方法即可
下面的樣例我用的 mybatis-plus 來查詢數(shù)據(jù)庫(kù)中的 user
, 然后通過當(dāng)前查詢到的 user
返回特定的 UserDetails
對(duì)象
@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 是我們實(shí)現(xiàn)的類 } }
UserDetailsImpl
是實(shí)現(xiàn)了 UserDetails
接口的類。UserDetails
接口是 Spring Security 身份驗(yàn)證機(jī)制的基礎(chǔ),通過實(shí)現(xiàn)該接口,開發(fā)者可以定義自己的用戶模型,并提供用戶相關(guān)的信息,以便進(jìn)行身份驗(yàn)證和權(quán)限檢查。
@Data @AllArgsConstructor @NoArgsConstructor // 這三個(gè)注解可以幫我們自動(dòng)生成 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; } // 這個(gè)方法是 @Data注解 會(huì)自動(dòng)幫我們生成,用來獲取 loadUserByUsername 中最后我們返回的創(chuàng)建UserDetailsImpl對(duì)象時(shí)傳入的User。 // 如果你的字段包含 username和password 的話可以用強(qiáng)制類型轉(zhuǎn)換, 把 UserDetailsImpl 轉(zhuǎn)換成 User。如果不能強(qiáng)制類型轉(zhuǎn)換的話就需要用到這個(gè)方法了 public User getUser() { return user; } }
1.3 基本的配置
下面這個(gè)是 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() // ** 通配符 // 對(duì)所有請(qǐng)求開啟授權(quán)保護(hù) .anyRequest(). // 已認(rèn)證的請(qǐng)求會(huì)被自動(dòng)授權(quán) authenticated() ) // 使用默認(rèn)的登陸登出頁(yè)面進(jìn)行授權(quán)登陸 .formLogin(Customizer.withDefaults()) // 啟用“記住我”功能的。允許用戶在關(guān)閉瀏覽器后,仍然保持登錄狀態(tài),直到他們主動(dòng)注銷或超出設(shè)定的過期時(shí)間。 .rememberMe(Customizer.withDefaults()); // 關(guān)閉 csrf CSRF(跨站請(qǐng)求偽造)是一種網(wǎng)絡(luò)攻擊,攻擊者通過欺騙已登錄用戶,誘使他們?cè)诓恢榈那闆r下向受信任的網(wǎng)站發(fā)送請(qǐng)求。 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 的時(shí)候不容易發(fā)現(xiàn)。(筆者初學(xué)時(shí)找了一個(gè)小時(shí),… … .)
1.4 常用配置
下面這個(gè)是我常用的配置,配置了jwt,加密的類,過濾器啟用 jwt,而不是session。(jwt的類會(huì)在下面講到)
@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 提供了多種加密和安全機(jī)制來保護(hù)用戶的敏感信息,尤其是在用戶身份驗(yàn)證和密碼管理方面。(我們只講默認(rèn)的 BCryptPasswordEncoder
)
1. 密碼加密的重要性
在應(yīng)用程序中,用戶密碼是最敏感的數(shù)據(jù)之一。為了防止密碼泄露,即使數(shù)據(jù)庫(kù)被攻擊者獲取,密碼也應(yīng)以加密形式存儲(chǔ)。加密可以保護(hù)用戶的隱私,并在一定程度上增加安全性。
2. Spring Security 的加密機(jī)制
2.1 PasswordEncoder 接口
Spring Security 提供了 PasswordEncoder
接口,用于定義密碼的加密和驗(yàn)證方法。主要有以下幾種實(shí)現(xiàn):
- BCryptPasswordEncoder:基于 BCrypt 算法,具有適應(yīng)性和強(qiáng)大的加密強(qiáng)度。它可以根據(jù)需求自動(dòng)調(diào)整加密的復(fù)雜性。
- NoOpPasswordEncoder:不執(zhí)行加密,適用于開發(fā)和測(cè)試環(huán)境,不建議在生產(chǎn)環(huán)境中使用。
- Pbkdf2PasswordEncoder、Argon2PasswordEncoder 等:這些都是基于不同算法的實(shí)現(xiàn),具有不同的安全特性。
2.2 BCrypt 算法
BCrypt 是一種安全的密碼哈希算法,具有以下特點(diǎn):
- 鹽值(Salt):每次加密時(shí)都會(huì)生成一個(gè)隨機(jī)鹽值,確保相同的密碼在每次加密時(shí)生成不同的哈希值。
- 適應(yīng)性:通過增加計(jì)算復(fù)雜性(如工作因子),可以提高密碼的加密強(qiáng)度。
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ù)值越大,加密計(jì)算越復(fù)雜 return new BCryptPasswordEncoder(); } }
3.2 加密密碼
在注冊(cè)用戶或更新密碼時(shí),可以使用 PasswordEncoder
來加密密碼:
@Autowired private PasswordEncoder passwordEncoder; public void registerUser(String username, String rawPassword) { String encryptedPassword = passwordEncoder.encode(rawPassword); // 保存用戶信息到數(shù)據(jù)庫(kù),包括加密后的密碼 }
3.3 驗(yàn)證密碼
在用戶登錄時(shí),可以使用 matches
方法驗(yàn)證輸入的密碼與存儲(chǔ)的加密密碼是否匹配:
public boolean login(String username, String rawPassword) { // 從數(shù)據(jù)庫(kù)中獲取用戶信息,包括加密后的密碼 String storedEncryptedPassword = // 從數(shù)據(jù)庫(kù)獲取; return passwordEncoder.matches(rawPassword, storedEncryptedPassword); }
3、前后端分離
1. 用戶認(rèn)證流程
1.1 用戶登錄
前端:
- 用戶在登錄界面輸入用戶名和密碼。
- 前端將這些憑證以 JSON 格式發(fā)送到后端的登錄 API(例如
POST /api/login
)。
后端:
- Spring Security 接收請(qǐng)求,使用
AuthenticationManager
進(jìn)行身份驗(yàn)證。 - 如果認(rèn)證成功,后端生成一個(gè) JWT(JSON Web Token)或其他認(rèn)證令牌,并將其返回給前端。
2. 使用 JWT 進(jìn)行用戶認(rèn)證
2.1 前端存儲(chǔ) JWT
- 前端收到 JWT 后,可以將其存儲(chǔ)在
localStorage
或sessionStorage
中,以便后續(xù)請(qǐng)求使用。
2.2 發(fā)送受保護(hù)請(qǐng)求
在發(fā)送需要認(rèn)證的請(qǐng)求時(shí),前端將 JWT 添加到請(qǐng)求頭中:
fetch('/api/protected-endpoint', { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } });
2.3 后端解析
JWT Spring Security 通過過濾器來解析和驗(yàn)證 JWT??梢宰远x一個(gè) OncePerRequestFilter
以攔截請(qǐng)求,提取 JWT,并驗(yàn)證其有效性。
3. 退出登錄
由于 JWT 是無狀態(tài)的,后端不需要記錄會(huì)話狀態(tài)。用戶可以通過前端操作(例如,刪除存儲(chǔ)的 JWT)來“退出登錄”??梢詫?shí)現(xiàn)一個(gè)注銷接口,用于前端執(zhí)行相關(guān)邏輯。
4. 保護(hù)敏感信息
- 確保 HTTPS:在前后端通信中使用 HTTPS,確保傳輸中的數(shù)據(jù)安全。
- 令牌過期:設(shè)置 JWT 的有效期,過期后需要用戶重新登錄。
- 刷新令牌:可以實(shí)現(xiàn)刷新令牌的機(jī)制,以提高用戶體驗(yàn)。
4. 實(shí)現(xiàn) jwt
4.1 OncePerRequestFilter
- 作用:
OncePerRequestFilter
是 Spring Security 提供的一個(gè)抽象類,確保在每個(gè)請(qǐng)求中只執(zhí)行一次特定的過濾邏輯。它是實(shí)現(xiàn)自定義過濾器的基礎(chǔ),通常用于對(duì)請(qǐng)求進(jìn)行預(yù)處理或后處理。(實(shí)現(xiàn)JWT
會(huì)用到這個(gè)接口) - 功能:提供了一種機(jī)制,以確保過濾器的邏輯在每個(gè)請(qǐng)求中只執(zhí)行一次,非常適合需要對(duì)每個(gè)請(qǐng)求進(jìn)行處理的場(chǎng)景。通過繼承該類,可以輕松實(shí)現(xiàn)自定義過濾器適合用于記錄日志、身份驗(yàn)證、權(quán)限檢查等場(chǎng)景。
- 實(shí)現(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 { // 自定義過濾邏輯,例如記錄請(qǐng)求日志 System.out.println("Request URI: " + request.getRequestURI()); // 繼續(xù)執(zhí)行過濾鏈 filterChain.doFilter(request, response); } }
注冊(cè)過濾器:可以在 Spring Security 配置類中注冊(cè)自定義的過濾器。
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
基于我們上面引入的三個(gè) JWT
相關(guān)的依賴,寫JwtUtil
類
- 功能:
JwtUtil
類提供了生成和解析 JWT 的功能,是實(shí)現(xiàn)基于 JWT 的認(rèn)證和授權(quán)的重要工具。 - 應(yīng)用場(chǎng)景:可以在用戶登錄后生成 JWT,并在后續(xù)請(qǐng)求中攜帶 JWT 用于用戶身份驗(yàn)證和權(quán)限校驗(yàn)。通過設(shè)置合適的過期時(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
:將這個(gè)類標(biāo)記為 Spring 組件,允許 Spring 管理該類的生命周期,便于依賴注入。JWT_TTL
:JWT 的有效期,設(shè)置為 14 天(單位為毫秒)。JWT_KEY
:用于簽名的密鑰字符串,經(jīng)過 Base64 編碼。它是生成和驗(yàn)證 JWT 的關(guān)鍵。
2. UUID 生成
public static String getUUID() { return UUID.randomUUID().toString().replaceAll("-", ""); }
- 作用:生成一個(gè)唯一的 UUID,去掉了其中的連字符。這通常用作 JWT 的 ID(
jti
),確保每個(gè) 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
方法生成一個(gè) JWT 構(gòu)建器,并將其壓縮為字符串返回。
4. JWT 構(gòu)建器
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { //... }
功能:生成一個(gè) JWT 的構(gòu)建器,配置 JWT 的各個(gè)屬性,包括:
setId(uuid)
:設(shè)置 JWT 的唯一標(biāo)識(shí)。setSubject(subject)
:設(shè)置 JWT 的主題。setIssuer("sg")
:設(shè)置 JWT 的發(fā)行者,通常是你的應(yīng)用或服務(wù)名稱。setIssuedAt(now)
:設(shè)置 JWT 的簽發(fā)時(shí)間。signWith(signatureAlgorithm, secretKey)
:使用指定的算法和密鑰進(jìn)行簽名。setExpiration(expDate)
:設(shè)置 JWT 的過期時(shí)間。
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 解碼,生成一個(gè) SecretKey
對(duì)象,用于 JWT 的簽名和驗(yàn)證。使用 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
對(duì)象),其中包含了 JWT 的主題、發(fā)行者、過期時(shí)間等信息。 - 該方法可能會(huì)拋出異常,表示 JWT 無效或解析失敗。
4.3 應(yīng)用 JWT
先加一個(gè)依賴 JetBrains Java Annotations
, 下面的代碼會(huì)用到其中的一個(gè)注解 @NotNull
。
這個(gè) JwtAuthenticationTokenFilter
類是一個(gè)實(shí)現(xiàn)了 OncePerRequestFilter
接口自定義的 Spring Security 過濾器。
- 功能:
JwtAuthenticationTokenFilter
通過 JWT 對(duì)用戶進(jìn)行身份驗(yàn)證,確保請(qǐng)求中攜帶的 JWT 是有效的,并根據(jù) JWT 提供的用戶信息設(shè)置認(rèn)證狀態(tài)。 - 應(yīng)用場(chǎng)景:通常用于保護(hù)需要身份驗(yàn)證的 API 接口,確保只有經(jīng)過認(rèn)證的用戶才能訪問資源。該過濾器通常放置在 Spring Security 過濾鏈的合適位置,以確保在處理請(qǐng)求之前進(jìn)行身份驗(yà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 掃描并注冊(cè)到應(yīng)用上下文中。- 繼承
OncePerRequestFilter
:確保每個(gè)請(qǐng)求只調(diào)用一次該過濾器,適合進(jìn)行請(qǐng)求預(yù)處理。
2. 依賴注入
@Autowired private UserMapper userMapper;
UserMapper
:一個(gè)數(shù)據(jù)訪問對(duì)象(DAO),用于與數(shù)據(jù)庫(kù)交互,以便根據(jù)用戶 ID 查詢用戶信息。通過 Spring 的依賴注入機(jī)制自動(dòng)注入。
3 獲取 JWT
String token = request.getHeader("Authorization"); if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; }
- 從請(qǐng)求頭中獲取
Authorization
字段的值,檢查是否包含 JWT。 - 如果沒有 JWT 或格式不正確,直接調(diào)用
filterChain.doFilter(request, response)
繼續(xù)執(zhí)行下一個(gè)過濾器,返回。
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
)。如果解析失敗,會(huì)拋出異常。
5 查詢用戶信息
User user = userMapper.selectById(Integer.parseInt(userid)); if (user == null) { throw new RuntimeException("用戶名未登錄"); }
根據(jù)解析出的用戶 ID 從數(shù)據(jù)庫(kù)中查詢用戶信息。如果用戶不存在,拋出異常。
6 設(shè)置安全上下文
UserDetailsImpl loginUser = new UserDetailsImpl(user); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null); SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- 創(chuàng)建一個(gè)自定義的
UserDetailsImpl
對(duì)象,將查詢到的用戶信息封裝。 - 創(chuàng)建一個(gè)
UsernamePasswordAuthenticationToken
對(duì)象,表示用戶的認(rèn)證信息,并將其設(shè)置到 Spring Security 的SecurityContextHolder
中,以便后續(xù)請(qǐng)求能夠訪問到用戶的認(rèn)證信息。
4. 繼續(xù)過濾鏈
filterChain.doFilter(request, response);
調(diào)用 filterChain.doFilter(request, response)
,繼續(xù)執(zhí)行后續(xù)的過濾器和最終的請(qǐng)求處理。
4.4 注冊(cè)自定義的 JwtAuthenticationTokenFilter 過濾器
把我們的過濾器應(yīng)用到 spring security
中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
5、常用操作
5.1 配置 AuthenticationManager
將 AuthenticationManager
對(duì)象添加到spring容器中.(添加到我們創(chuàng)建的 SecurityConfig
配置類中)
@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); }
5.2 驗(yàn)證當(dāng)前用戶
@Autowired private AuthenticationManager authenticationManager; UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); // 從數(shù)據(jù)庫(kù)中對(duì)比查找,如果找到了會(huì)返回一個(gè)帶有認(rèn)證的封裝后的用戶,否則會(huì)報(bào)錯(cuò),自動(dòng)處理。(這里我們假設(shè)我們配置的security是基于數(shù)據(jù)庫(kù)查找的) Authentication authenticate = authenticationManager.authenticate(authenticationToken); // 獲取認(rèn)證后的用戶 User user = (User ) authenticate.getPrincipal(); // 如果強(qiáng)制類型轉(zhuǎn)換報(bào)錯(cuò)的話,可以用我們實(shí)現(xiàn)的 `UserDetailsImpl` 類中的 getUser 方法了 String jwt = JwtUtil.createJWT(user.getId().toString());
5.3 獲取當(dāng)前用戶的認(rèn)證信息
以下是獲取當(dāng)前用戶認(rèn)證信息的具體步驟:
// SecurityContextHolder 是一個(gè)存儲(chǔ)安全上下文的工具類,提供了一個(gè)全局訪問點(diǎn),用于獲取當(dāng)前請(qǐng)求的安全上下文。 // SecurityContext 是當(dāng)前線程的安全上下文,包含了當(dāng)前用戶的認(rèn)證信息(即 Authentication 對(duì)象)。 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 對(duì)比 authenticationManager
和 SecurityContext
獲取 Authentication
SecurityContextHolder.getContext().getAuthentication()
:
- 這個(gè)方法獲取當(dāng)前線程的安全上下文中的
Authentication
對(duì)象。 - 通常在用戶已經(jīng)被認(rèn)證后使用,用于獲取當(dāng)前用戶的信息(如身份、權(quán)限等)。
authenticationManager.authenticate(authenticationToken)
:
- 這個(gè)方法是用于實(shí)際執(zhí)行身份驗(yàn)證的過程。
- 它接收一個(gè)憑證(如用戶名和密碼)并返回一個(gè)經(jīng)過驗(yàn)證的
Authentication
對(duì)象,或者拋出異常。 - 在用戶登錄或驗(yàn)證時(shí)使用,屬于身份驗(yàn)證的實(shí)際邏輯。
5.5 Authentication 接口
Authentication
接口包含以下重要方法:
getPrincipal()
:返回當(dāng)前用戶的身份,通常是用戶的詳細(xì)信息(如用戶名)。getCredentials()
:返回用戶的憑證(如密碼),但通常不直接使用此方法。getAuthorities()
:返回用戶的權(quán)限列表(角色或權(quán)限)。
6、授權(quán)
Spring Security 的授權(quán)機(jī)制用于控制用戶對(duì)應(yīng)用程序資源的訪問權(quán)限。授權(quán)通常是基于用戶角色或權(quán)限的,以下是對(duì) Spring Security 授權(quán)的詳細(xì)講解。
1. 授權(quán)的基本概念
授權(quán)(Authorization):指的是決定用戶是否有權(quán)限訪問特定資源的過程。在用戶認(rèn)證成功后,系統(tǒng)會(huì)根據(jù)預(yù)定義的規(guī)則來判斷用戶是否可以訪問某些資源。
2. 授權(quán)的主要組成部分
2.1 權(quán)限與角色
- 權(quán)限(Permission):通常指對(duì)特定資源的操作能力,如“讀取”、“寫入”或“刪除”。
- 角色(Role):一組權(quán)限的集合。例如,管理員角色可能具有所有權(quán)限,而普通用戶角色可能只有讀取權(quán)限。
2.2 GrantedAuthority 接口
- Spring Security 使用
GrantedAuthority
接口表示用戶的權(quán)限。每個(gè)用戶的權(quán)限可以通過實(shí)現(xiàn)該接口的對(duì)象來表示。
3. 授權(quán)方式
Spring Security 支持多種授權(quán)方式,主要包括:
3.1 基于角色的授權(quán)
- 定義角色:通常在用戶注冊(cè)或用戶管理中定義。
- 配置角色授權(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(); // 其他請(qǐng)求需要認(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)邏輯,可以實(shí)現(xiàn)自定義的 AccessDecisionVoter
或 AccessDecisionManager
。
4.1 AccessDecisionVoter 負(fù)責(zé)評(píng)估用戶的訪問請(qǐng)求,返回授權(quán)決策。
4.2 AccessDecisionManager
管理多個(gè) AccessDecisionVoter
的調(diào)用,確定用戶是否有權(quán)訪問請(qǐng)求的資源。
7、其他自定義行為
以下接口和類用于處理不同的安全事件,提供了自定義處理的能力:
1. AuthenticationSuccessHandler
- 作用:用于處理用戶成功認(rèn)證后的邏輯。
- 功能:可以自定義成功登錄后的跳轉(zhuǎn)行為,比如重定向到特定頁(yè)面、返回 JSON 響應(yīng)等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
AuthenticationSuccessHandler
接口,并重寫onAuthenticationSuccess
方法。
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { } } // 讓我們實(shí)現(xiàn)的類生效 http.formLogin(form -> form.successHandler(new MyAuthenticationSuccessHandler()));
2. AuthenticationFailureHandler
- 作用:用于處理用戶認(rèn)證失敗的邏輯。
- 功能:可以自定義失敗登錄后的行為,比如返回錯(cuò)誤信息、重定向到登錄頁(yè)面并顯示錯(cuò)誤提示等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
AuthenticationFailureHandler
接口,并重寫onAuthenticationFailure
方法。
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { } } // 讓我們實(shí)現(xiàn)的類生效 http.formLogin(form -> form.failureHandler(new MyAuthenticationFailureHandler()));
3. LogoutSuccessHandler
- 作用:用于處理用戶成功登出的邏輯。
- 功能:可以自定義注銷成功后的行為,比如重定向到登錄頁(yè)面、顯示注銷成功的消息等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
LogoutSuccessHandler
接口,并重寫onLogoutSuccess
方法。
public class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { } } // 讓我們實(shí)現(xiàn)的類生效 http.logout(logout -> { logout.logoutSuccessHandler(new MyLogoutSuccessHandler()); });
4. AuthenticationEntryPoint
- 作用:用于處理未認(rèn)證用戶訪問受保護(hù)資源時(shí)的邏輯。
- 功能:可以自定義未認(rèn)證用戶的響應(yīng),比如返回 401 狀態(tài)碼、重定向到登錄頁(yè)面等。
- 實(shí)現(xiàn):實(shí)現(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
- 作用:用于處理用戶會(huì)話過期的邏輯。
- 功能:可以自定義會(huì)話超時(shí)后的響應(yīng),比如重定向到登錄頁(yè)面或返回 JSON 響應(yīng)等。
- 實(shí)現(xiàn):實(shí)現(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)限的資源時(shí),Spring Security 會(huì)調(diào)用實(shí)現(xiàn)了
AccessDeniedHandler
接口的處理器。 - 功能:可以自定義拒絕訪問后的響應(yīng)行為,比如重定向到特定的錯(cuò)誤頁(yè)面、返回錯(cuò)誤信息或 JSON 響應(yīng)。
- 實(shí)現(xiàn):實(shí)現(xiàn)
AccessDeniedHandler
接口,并重寫handle
方法。
// 重寫 AccessDeniedHandler 接口 public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { } } // 在安全配置中注冊(cè)自定義的 AccessDeniedHandler http.exceptionHandling(exception -> { exception.accessDeniedHandler(new MyAccessDeniedHandler()); }); });
文章到這里就結(jié)束了~
到此這篇關(guān)于spring security 超詳細(xì)使用教程(接入springboot、前后端分離)的文章就介紹到這了,更多相關(guān)spring security 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot3.x接入Security6.x實(shí)現(xiàn)JWT認(rèn)證的完整步驟
- SpringBoot整合Springsecurity實(shí)現(xiàn)數(shù)據(jù)庫(kù)登錄及權(quán)限控制功能
- springboot版本升級(jí)以及解決springsecurity漏洞的問題
- SpringBoot的Security和OAuth2的使用示例小結(jié)
- SpringBoot集成SpringSecurity安全框架方式
- SpringBoot+SpringSecurity實(shí)現(xiàn)認(rèn)證的流程詳解
- SpringSecurity角色權(quán)限控制(SpringBoot+SpringSecurity+JWT)
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
相關(guān)文章
java實(shí)現(xiàn)同步的幾種方式(示例詳解)
這篇文章主要介紹了java實(shí)現(xiàn)同步的幾種方式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-12-12MybatisPlus分頁(yè)排序查詢字段帶有下劃線的坑及解決
這篇文章主要介紹了MybatisPlus分頁(yè)排序查詢字段帶有下劃線的坑及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12java獲取當(dāng)前日期和時(shí)間的二種方法分享
這篇文章主要介紹了java獲取當(dāng)前日期和時(shí)間的二種方法,需要的朋友可以參考下2014-03-03MyBatis利用攔截器實(shí)現(xiàn)數(shù)據(jù)脫敏詳解
現(xiàn)代網(wǎng)絡(luò)環(huán)境中,敏感數(shù)據(jù)的處理是至關(guān)重要的,敏感數(shù)據(jù)包括個(gè)人身份信息、銀行賬號(hào)、手機(jī)號(hào)碼等,所以本文主要為大家詳細(xì)介紹了MyBatis如何利用攔截器實(shí)現(xiàn)數(shù)據(jù)脫敏,希望對(duì)大家有所幫助2023-11-11idea中定時(shí)及多數(shù)據(jù)源配置方法
因項(xiàng)目要求,需要定時(shí)從達(dá)夢(mèng)數(shù)據(jù)庫(kù)中取數(shù)據(jù),并插入或更新到ORACLE數(shù)據(jù)庫(kù)中,這篇文章主要介紹了idea中定時(shí)及多數(shù)據(jù)源配置方法,需要的朋友可以參考下2023-12-12Java異步編程之Callbacks與Futures模型詳解
這篇文章主要為大家詳細(xì)介紹了Java異步編程中Callbacks與Futures模型的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03springboot如何接收復(fù)雜參數(shù)(同時(shí)接收J(rèn)SON與文件)
文章介紹了在Spring Boot中同時(shí)處理JSON和文件上傳時(shí)使用`@RequestPart`注解的方法,`@RequestPart`可以接收多種格式的參數(shù),包括JSON和文件,并且可以作為`multipart/form-data`格式中的key2025-02-02