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)了 UserDetailsManager
和 UserDetailsPasswordService
接口,其中 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 對比 authenticationManager
和 SecurityContext
獲取 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)自定義的 AccessDecisionVoter
或 AccessDecisionManager
。
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)文章希望大家以后多多支持腳本之家!
- SpringBoot3.x接入Security6.x實現(xiàn)JWT認(rèn)證的完整步驟
- SpringBoot整合Springsecurity實現(xiàn)數(shù)據(jù)庫登錄及權(quán)限控制功能
- springboot版本升級以及解決springsecurity漏洞的問題
- SpringBoot的Security和OAuth2的使用示例小結(jié)
- SpringBoot集成SpringSecurity安全框架方式
- SpringBoot+SpringSecurity實現(xiàn)認(rèn)證的流程詳解
- SpringSecurity角色權(quán)限控制(SpringBoot+SpringSecurity+JWT)
- SpringBoot整合SpringSecurity和JWT和Redis實現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
相關(guān)文章
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-11Java異步編程之Callbacks與Futures模型詳解
這篇文章主要為大家詳細(xì)介紹了Java異步編程中Callbacks與Futures模型的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03springboot如何接收復(fù)雜參數(shù)(同時接收J(rèn)SON與文件)
文章介紹了在Spring Boot中同時處理JSON和文件上傳時使用`@RequestPart`注解的方法,`@RequestPart`可以接收多種格式的參數(shù),包括JSON和文件,并且可以作為`multipart/form-data`格式中的key2025-02-02