spring security 超詳細(xì)使用教程及如何接入springboot、前后端分離
Spring Security 是一個(gè)強(qiáng)大且可擴(kuò)展的框架,用于保護(hù) Java 應(yīng)用程序,尤其是基于 Spring 的應(yīng)用。它提供了身份驗(yàn)證(驗(yàn)證用戶(hù)身份)、授權(quán)(管理用戶(hù)權(quán)限)和防護(hù)機(jī)制(如 CSRF 保護(hù)和防止會(huì)話劫持)等功能。
Spring Security 允許開(kāi)發(fā)者通過(guò)靈活的配置實(shí)現(xiàn)安全控制,確保應(yīng)用程序的數(shù)據(jù)和資源安全。通過(guò)與其他 Spring 生態(tài)系統(tǒng)的無(wú)縫集成,Spring Security 成為構(gòu)建安全應(yīng)用的理想選擇。
核心概念
- 身份驗(yàn)證 (Authentication): 驗(yàn)證用戶(hù)的身份(例如,用戶(hù)名/密碼)。
- 授權(quán) (Authorization): 確定用戶(hù)是否有權(quán)限訪問(wèn)特定資源。
- 安全上下文 (Security Context): 存儲(chǔ)已認(rèn)證用戶(hù)的詳細(xì)信息,應(yīng)用程序中可以訪問(wèn)。
1、準(zhǔn)備工作
1.1 引入依賴(lài)
當(dāng)我們引入 security 依賴(lài)后,訪問(wèn)需要授權(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 類(lèi),來(lái)管理我們所有與 security 相關(guān)的配置。(我們講的是 security 5.7 版本之后的配置方法,之前的方法跟現(xiàn)在不太一樣)
@Configuration
@EnableWebSecurity // 該注解啟用 Spring Security 的 web 安全功能。
public class SecurityConfig {
}下面的都要寫(xiě)到 SecurityConfig 類(lèi)中
1.2 用戶(hù)認(rèn)證的配置
基于內(nèi)存的用戶(hù)認(rèn)證
通過(guò) createUser , manager 把用戶(hù)配置的賬號(hào)密碼添加到spring的內(nèi)存中, InMemoryUserDetailsManager 類(lèi)中有一個(gè) loadUserByUsername 的方法通過(guò)賬號(hào)(username)從內(nèi)存中獲取我們配置的賬號(hào)密碼,之后調(diào)用其他方法來(lái)判斷前端用戶(hù)輸入的密碼和內(nèi)存中的密碼是否匹配。
@Bean
public UserDetailsService userDetailsService() {
// 創(chuàng)建基于內(nèi)存的用戶(hù)信息管理器
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
// 創(chuàng)建UserDetails對(duì)象,用于管理用戶(hù)名、用戶(hù)密碼、用戶(hù)角色、用戶(hù)權(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ù)的用戶(hù)認(rèn)證
上面講到,spring security 是通過(guò) loadUserByUsername 方法來(lái)獲取 User 并用這個(gè) User 來(lái)判斷用戶(hù)輸入的密碼是否正確。所以我們只需要繼承 UserDetailsService 接口并重寫(xiě) loadUserByUsername 方法即可
下面的樣例我用的 mybatis-plus 來(lái)查詢(xún)數(shù)據(jù)庫(kù)中的 user, 然后通過(guò)當(dāng)前查詢(xún)到的 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ù)你自己寫(xiě)的查詢(xún)邏輯
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new UserDetailsImpl(user); // UserDetailsImpl 是我們實(shí)現(xiàn)的類(lèi)
}
}UserDetailsImpl 是實(shí)現(xiàn)了 UserDetails 接口的類(lèi)。UserDetails 接口是 Spring Security 身份驗(yàn)證機(jī)制的基礎(chǔ),通過(guò)實(shí)現(xiàn)該接口,開(kāi)發(fā)者可以定義自己的用戶(hù)模型,并提供用戶(hù)相關(guān)的信息,以便進(jìn)行身份驗(yàn)證和權(quán)限檢查。
@Data
@AllArgsConstructor
@NoArgsConstructor // 這三個(gè)注解可以幫我們自動(dòng)生成 get、set、有參、無(wú)參構(gòu)造函數(shù)
public class UserDetailsImpl implements UserDetails {
private User user; // 通過(guò)有參構(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() { // 檢查賬戶(hù)是否 沒(méi)過(guò)期。
return true;
}
@Override
public boolean isAccountNonLocked() { // 檢查賬戶(hù)是否 沒(méi)有被鎖定。
return true;
}
@Override
public boolean isCredentialsNonExpired() { //檢查憑據(jù)(密碼)是否 沒(méi)過(guò)期。
return true;
}
@Override
public boolean isEnabled() { // 檢查賬戶(hù)是否啟用。
return true;
}
// 這個(gè)方法是 @Data注解 會(huì)自動(dòng)幫我們生成,用來(lái)獲取 loadUserByUsername 中最后我們返回的創(chuàng)建UserDetailsImpl對(duì)象時(shí)傳入的User。
// 如果你的字段包含 username和password 的話可以用強(qiáng)制類(lèi)型轉(zhuǎn)換, 把 UserDetailsImpl 轉(zhuǎn)換成 User。如果不能強(qiáng)制類(lèi)型轉(zhuǎn)換的話就需要用到這個(gè)方法了
public User getUser() {
return user;
}
}1.3 基本的配置
下面這個(gè)是 security 的默認(rèn)配置。我們可以修改并把它加到spring容器中,完成我們特定的需求。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 開(kāi)啟授權(quán)保護(hù)
.authorizeHttpRequests(authorize -> authorize
// 不需要認(rèn)證的地址有哪些
.requestMatchers("/blog/**", "/public/**", "/about").permitAll() // ** 通配符
// 對(duì)所有請(qǐng)求開(kāi)啟授權(quán)保護(hù)
.anyRequest().
// 已認(rèn)證的請(qǐng)求會(huì)被自動(dòng)授權(quán)
authenticated()
)
// 使用默認(rèn)的登陸登出頁(yè)面進(jìn)行授權(quán)登陸
.formLogin(Customizer.withDefaults())
// 啟用“記住我”功能的。允許用戶(hù)在關(guān)閉瀏覽器后,仍然保持登錄狀態(tài),直到他們主動(dòng)注銷(xiāo)或超出設(shè)定的過(guò)期時(shí)間。
.rememberMe(Customizer.withDefaults());
// 關(guān)閉 csrf CSRF(跨站請(qǐng)求偽造)是一種網(wǎng)絡(luò)攻擊,攻擊者通過(guò)欺騙已登錄用戶(hù),誘使他們?cè)诓恢榈那闆r下向受信任的網(wǎng)站發(fā)送請(qǐng)求。
http.csrf(csrf -> csrf.disable());
return http.build();
}關(guān)于上面放行路徑寫(xiě)法的一些細(xì)節(jié)問(wèn)題:
如果在 application.properities 中配置的有 server.servlet.context-path=/api 前綴的話,在放行路徑中不需要寫(xiě) /api。
如果 @RequestMapping(value = "/test/") 中寫(xiě)的是 /test/, 那么放行路徑必須也寫(xiě)成 /test/, (/test)是不行的,反之亦然。
如果 @RequestMapping(value = "/test") 鏈接 /test 后面要加查詢(xún)字符的話(/test?type=0),不要寫(xiě)成 @RequestMapping(value = "/test/")
上面都是一些細(xì)節(jié)問(wèn)題,但是遇到 bug 的時(shí)候不容易發(fā)現(xiàn)。(筆者初學(xué)時(shí)找了一個(gè)小時(shí),… … .)
1.4 常用配置
下面這個(gè)是我常用的配置,配置了jwt,加密的類(lèi),過(guò)濾器啟用 jwt,而不是session。(jwt的類(lèi)會(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ī)制來(lái)保護(hù)用戶(hù)的敏感信息,尤其是在用戶(hù)身份驗(yàn)證和密碼管理方面。(我們只講默認(rèn)的 BCryptPasswordEncoder)
1. 密碼加密的重要性
在應(yīng)用程序中,用戶(hù)密碼是最敏感的數(shù)據(jù)之一。為了防止密碼泄露,即使數(shù)據(jù)庫(kù)被攻擊者獲取,密碼也應(yīng)以加密形式存儲(chǔ)。加密可以保護(hù)用戶(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í)行加密,適用于開(kāi)發(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)性:通過(guò)增加計(jì)算復(fù)雜性(如工作因子),可以提高密碼的加密強(qiáng)度。
3. 使用 PasswordEncoder
3.1 配置 PasswordEncoder
可以在 Spring Security 的配置類(lèi)中定義 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è)用戶(hù)或更新密碼時(shí),可以使用 PasswordEncoder 來(lái)加密密碼:
@Autowired
private PasswordEncoder passwordEncoder;
public void registerUser(String username, String rawPassword) {
String encryptedPassword = passwordEncoder.encode(rawPassword);
// 保存用戶(hù)信息到數(shù)據(jù)庫(kù),包括加密后的密碼
}3.3 驗(yàn)證密碼
在用戶(hù)登錄時(shí),可以使用 matches 方法驗(yàn)證輸入的密碼與存儲(chǔ)的加密密碼是否匹配:
public boolean login(String username, String rawPassword) {
// 從數(shù)據(jù)庫(kù)中獲取用戶(hù)信息,包括加密后的密碼
String storedEncryptedPassword = // 從數(shù)據(jù)庫(kù)獲取;
return passwordEncoder.matches(rawPassword, storedEncryptedPassword);
}3、前后端分離
1. 用戶(hù)認(rèn)證流程
1.1 用戶(hù)登錄
前端:
- 用戶(hù)在登錄界面輸入用戶(hù)名和密碼。
- 前端將這些憑證以 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)行用戶(hù)認(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 通過(guò)過(guò)濾器來(lái)解析和驗(yàn)證 JWT??梢宰远x一個(gè) OncePerRequestFilter 以攔截請(qǐng)求,提取 JWT,并驗(yàn)證其有效性。
3. 退出登錄
由于 JWT 是無(wú)狀態(tài)的,后端不需要記錄會(huì)話狀態(tài)。用戶(hù)可以通過(guò)前端操作(例如,刪除存儲(chǔ)的 JWT)來(lái)“退出登錄”??梢詫?shí)現(xiàn)一個(gè)注銷(xiāo)接口,用于前端執(zhí)行相關(guān)邏輯。
4. 保護(hù)敏感信息
- 確保 HTTPS:在前后端通信中使用 HTTPS,確保傳輸中的數(shù)據(jù)安全。
- 令牌過(guò)期:設(shè)置 JWT 的有效期,過(guò)期后需要用戶(hù)重新登錄。
- 刷新令牌:可以實(shí)現(xiàn)刷新令牌的機(jī)制,以提高用戶(hù)體驗(yàn)。
4. 實(shí)現(xiàn) jwt
4.1 OncePerRequestFilter
- 作用:
OncePerRequestFilter是 Spring Security 提供的一個(gè)抽象類(lèi),確保在每個(gè)請(qǐng)求中只執(zhí)行一次特定的過(guò)濾邏輯。它是實(shí)現(xiàn)自定義過(guò)濾器的基礎(chǔ),通常用于對(duì)請(qǐng)求進(jìn)行預(yù)處理或后處理。(實(shí)現(xiàn)JWT會(huì)用到這個(gè)接口) - 功能:提供了一種機(jī)制,以確保過(guò)濾器的邏輯在每個(gè)請(qǐng)求中只執(zhí)行一次,非常適合需要對(duì)每個(gè)請(qǐng)求進(jìn)行處理的場(chǎng)景。通過(guò)繼承該類(lèi),可以輕松實(shí)現(xiàn)自定義過(guò)濾器適合用于記錄日志、身份驗(yàn)證、權(quán)限檢查等場(chǎng)景。
- 實(shí)現(xiàn):繼承
OncePerRequestFilter類(lèi),并重寫(xiě)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 {
// 自定義過(guò)濾邏輯,例如記錄請(qǐng)求日志
System.out.println("Request URI: " + request.getRequestURI());
// 繼續(xù)執(zhí)行過(guò)濾鏈
filterChain.doFilter(request, response);
}
}注冊(cè)過(guò)濾器:可以在 Spring Security 配置類(lèi)中注冊(cè)自定義的過(guò)濾器。
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)的依賴(lài),寫(xiě)JwtUtil 類(lèi)
- 功能:
JwtUtil類(lèi)提供了生成和解析 JWT 的功能,是實(shí)現(xiàn)基于 JWT 的認(rèn)證和授權(quán)的重要工具。 - 應(yīng)用場(chǎng)景:可以在用戶(hù)登錄后生成 JWT,并在后續(xù)請(qǐng)求中攜帶 JWT 用于用戶(hù)身份驗(yàn)證和權(quán)限校驗(yàn)。通過(guò)設(shè)置合適的過(guò)期時(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. 類(lèi)注解與常量
@Component:將這個(gè)類(lèi)標(biāo)記為 Spring 組件,允許 Spring 管理該類(lèi)的生命周期,便于依賴(lài)注入。JWT_TTL:JWT 的有效期,設(shè)置為 14 天(單位為毫秒)。JWT_KEY:用于簽名的密鑰字符串,經(jīng)過(guò) 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 的主題,通常是用戶(hù)的身份信息。 - 功能:調(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ù)名稱(chēng)。setIssuedAt(now):設(shè)置 JWT 的簽發(fā)時(shí)間。signWith(signatureAlgorithm, secretKey):使用指定的算法和密鑰進(jìn)行簽名。setExpiration(expDate):設(shè)置 JWT 的過(guò)期時(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ā)行者、過(guò)期時(shí)間等信息。 - 該方法可能會(huì)拋出異常,表示 JWT 無(wú)效或解析失敗。
4.3 應(yīng)用 JWT
先加一個(gè)依賴(lài) JetBrains Java Annotations, 下面的代碼會(huì)用到其中的一個(gè)注解 @NotNull。

這個(gè) JwtAuthenticationTokenFilter 類(lèi)是一個(gè)實(shí)現(xiàn)了 OncePerRequestFilter 接口自定義的 Spring Security 過(guò)濾器。
- 功能:
JwtAuthenticationTokenFilter通過(guò) JWT 對(duì)用戶(hù)進(jìn)行身份驗(yàn)證,確保請(qǐng)求中攜帶的 JWT 是有效的,并根據(jù) JWT 提供的用戶(hù)信息設(shè)置認(rèn)證狀態(tài)。 - 應(yīng)用場(chǎng)景:通常用于保護(hù)需要身份驗(yàn)證的 API 接口,確保只有經(jīng)過(guò)認(rèn)證的用戶(hù)才能訪問(wèn)資源。該過(guò)濾器通常放置在 Spring Security 過(guò)濾鏈的合適位置,以確保在處理請(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("用戶(hù)名未登錄");
}
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
// 如果是有效的jwt,那么設(shè)置該用戶(hù)為認(rèn)證后的用戶(hù)
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}1. 類(lèi)注解與繼承
@Component:標(biāo)記該類(lèi)為 Spring 組件,使其能夠被 Spring 掃描并注冊(cè)到應(yīng)用上下文中。- 繼承
OncePerRequestFilter:確保每個(gè)請(qǐng)求只調(diào)用一次該過(guò)濾器,適合進(jìn)行請(qǐng)求預(yù)處理。
2. 依賴(lài)注入
@Autowired private UserMapper userMapper;
UserMapper:一個(gè)數(shù)據(jù)訪問(wèn)對(duì)象(DAO),用于與數(shù)據(jù)庫(kù)交互,以便根據(jù)用戶(hù) ID 查詢(xún)用戶(hù)信息。通過(guò) Spring 的依賴(lài)注入機(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。 - 如果沒(méi)有 JWT 或格式不正確,直接調(diào)用
filterChain.doFilter(request, response)繼續(xù)執(zhí)行下一個(gè)過(guò)濾器,返回。
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,提取出用戶(hù) ID (userid)。如果解析失敗,會(huì)拋出異常。
5 查詢(xún)用戶(hù)信息
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用戶(hù)名未登錄");
}根據(jù)解析出的用戶(hù) ID 從數(shù)據(jù)庫(kù)中查詢(xún)用戶(hù)信息。如果用戶(hù)不存在,拋出異常。
6 設(shè)置安全上下文
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);- 創(chuàng)建一個(gè)自定義的
UserDetailsImpl對(duì)象,將查詢(xún)到的用戶(hù)信息封裝。 - 創(chuàng)建一個(gè)
UsernamePasswordAuthenticationToken對(duì)象,表示用戶(hù)的認(rèn)證信息,并將其設(shè)置到 Spring Security 的SecurityContextHolder中,以便后續(xù)請(qǐng)求能夠訪問(wèn)到用戶(hù)的認(rèn)證信息。
4. 繼續(xù)過(guò)濾鏈
filterChain.doFilter(request, response);
調(diào)用 filterChain.doFilter(request, response),繼續(xù)執(zhí)行后續(xù)的過(guò)濾器和最終的請(qǐng)求處理。
4.4 注冊(cè)自定義的 JwtAuthenticationTokenFilter 過(guò)濾器
把我們的過(guò)濾器應(yīng)用到 spring security 中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
5、常用操作
5.1 配置 AuthenticationManager
將 AuthenticationManager 對(duì)象添加到spring容器中.(添加到我們創(chuàng)建的 SecurityConfig 配置類(lèi)中)
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}5.2 驗(yàn)證當(dāng)前用戶(hù)
@Autowired private AuthenticationManager authenticationManager; UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); // 從數(shù)據(jù)庫(kù)中對(duì)比查找,如果找到了會(huì)返回一個(gè)帶有認(rèn)證的封裝后的用戶(hù),否則會(huì)報(bào)錯(cuò),自動(dòng)處理。(這里我們假設(shè)我們配置的security是基于數(shù)據(jù)庫(kù)查找的) Authentication authenticate = authenticationManager.authenticate(authenticationToken); // 獲取認(rèn)證后的用戶(hù) User user = (User ) authenticate.getPrincipal(); // 如果強(qiáng)制類(lèi)型轉(zhuǎn)換報(bào)錯(cuò)的話,可以用我們實(shí)現(xiàn)的 `UserDetailsImpl` 類(lèi)中的 getUser 方法了 String jwt = JwtUtil.createJWT(user.getId().toString());
5.3 獲取當(dāng)前用戶(hù)的認(rèn)證信息
以下是獲取當(dāng)前用戶(hù)認(rèn)證信息的具體步驟:
// SecurityContextHolder 是一個(gè)存儲(chǔ)安全上下文的工具類(lèi),提供了一個(gè)全局訪問(wèn)點(diǎn),用于獲取當(dāng)前請(qǐng)求的安全上下文。 // SecurityContext 是當(dāng)前線程的安全上下文,包含了當(dāng)前用戶(hù)的認(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ì)象。 - 通常在用戶(hù)已經(jīng)被認(rèn)證后使用,用于獲取當(dāng)前用戶(hù)的信息(如身份、權(quán)限等)。
authenticationManager.authenticate(authenticationToken):
- 這個(gè)方法是用于實(shí)際執(zhí)行身份驗(yàn)證的過(guò)程。
- 它接收一個(gè)憑證(如用戶(hù)名和密碼)并返回一個(gè)經(jīng)過(guò)驗(yàn)證的
Authentication對(duì)象,或者拋出異常。 - 在用戶(hù)登錄或驗(yàn)證時(shí)使用,屬于身份驗(yàn)證的實(shí)際邏輯。
5.5 Authentication 接口
Authentication 接口包含以下重要方法:
getPrincipal():返回當(dāng)前用戶(hù)的身份,通常是用戶(hù)的詳細(xì)信息(如用戶(hù)名)。getCredentials():返回用戶(hù)的憑證(如密碼),但通常不直接使用此方法。getAuthorities():返回用戶(hù)的權(quán)限列表(角色或權(quán)限)。
6、授權(quán)
Spring Security 的授權(quán)機(jī)制用于控制用戶(hù)對(duì)應(yīng)用程序資源的訪問(wèn)權(quán)限。授權(quán)通常是基于用戶(hù)角色或權(quán)限的,以下是對(duì) Spring Security 授權(quán)的詳細(xì)講解。
1. 授權(quán)的基本概念
授權(quán)(Authorization):指的是決定用戶(hù)是否有權(quán)限訪問(wèn)特定資源的過(guò)程。在用戶(hù)認(rèn)證成功后,系統(tǒng)會(huì)根據(jù)預(yù)定義的規(guī)則來(lái)判斷用戶(hù)是否可以訪問(wèn)某些資源。
2. 授權(quán)的主要組成部分
2.1 權(quán)限與角色
- 權(quán)限(Permission):通常指對(duì)特定資源的操作能力,如“讀取”、“寫(xiě)入”或“刪除”。
- 角色(Role):一組權(quán)限的集合。例如,管理員角色可能具有所有權(quán)限,而普通用戶(hù)角色可能只有讀取權(quán)限。
2.2 GrantedAuthority 接口
- Spring Security 使用
GrantedAuthority接口表示用戶(hù)的權(quán)限。每個(gè)用戶(hù)的權(quán)限可以通過(guò)實(shí)現(xiàn)該接口的對(duì)象來(lái)表示。
3. 授權(quán)方式
Spring Security 支持多種授權(quán)方式,主要包括:
3.1 基于角色的授權(quán)
- 定義角色:通常在用戶(hù)注冊(cè)或用戶(hù)管理中定義。
- 配置角色授權(quán):可以在 Spring Security 配置類(lèi)中設(shè)置基于角色的訪問(wèn)控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 只有 ADMIN 角色可以訪問(wèn)
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 和 ADMIN 角色可以訪問(wèn)
.anyRequest().authenticated(); // 其他請(qǐng)求需要認(rèn)證
}3.2 基于權(quán)限的授權(quán)
- 定義權(quán)限:與角色類(lèi)似,定義更細(xì)粒度的權(quán)限。
- 配置權(quán)限授權(quán):在配置類(lèi)中設(shè)置基于權(quán)限的訪問(wèn)控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE") // 僅具有 EDIT_PRIVILEGE 權(quán)限的用戶(hù)可以訪問(wèn)
.anyRequest().authenticated();
}4. 自定義授權(quán)邏輯
如果需要更復(fù)雜的授權(quán)邏輯,可以實(shí)現(xiàn)自定義的 AccessDecisionVoter 或 AccessDecisionManager。
4.1 AccessDecisionVoter 負(fù)責(zé)評(píng)估用戶(hù)的訪問(wèn)請(qǐng)求,返回授權(quán)決策。
4.2 AccessDecisionManager
管理多個(gè) AccessDecisionVoter 的調(diào)用,確定用戶(hù)是否有權(quán)訪問(wèn)請(qǐng)求的資源。
7、其他自定義行為
以下接口和類(lèi)用于處理不同的安全事件,提供了自定義處理的能力:
1. AuthenticationSuccessHandler
- 作用:用于處理用戶(hù)成功認(rèn)證后的邏輯。
- 功能:可以自定義成功登錄后的跳轉(zhuǎn)行為,比如重定向到特定頁(yè)面、返回 JSON 響應(yīng)等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
AuthenticationSuccessHandler接口,并重寫(xiě)onAuthenticationSuccess方法。
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
}
// 讓我們實(shí)現(xiàn)的類(lèi)生效
http.formLogin(form ->
form.successHandler(new MyAuthenticationSuccessHandler()));2. AuthenticationFailureHandler
- 作用:用于處理用戶(hù)認(rèn)證失敗的邏輯。
- 功能:可以自定義失敗登錄后的行為,比如返回錯(cuò)誤信息、重定向到登錄頁(yè)面并顯示錯(cuò)誤提示等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
AuthenticationFailureHandler接口,并重寫(xiě)onAuthenticationFailure方法。
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
}
}
// 讓我們實(shí)現(xiàn)的類(lèi)生效
http.formLogin(form ->
form.failureHandler(new MyAuthenticationFailureHandler()));3. LogoutSuccessHandler
- 作用:用于處理用戶(hù)成功登出的邏輯。
- 功能:可以自定義注銷(xiāo)成功后的行為,比如重定向到登錄頁(yè)面、顯示注銷(xiāo)成功的消息等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
LogoutSuccessHandler接口,并重寫(xiě)onLogoutSuccess方法。
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
}
// 讓我們實(shí)現(xiàn)的類(lèi)生效
http.logout(logout -> {
logout.logoutSuccessHandler(new MyLogoutSuccessHandler());
});4. AuthenticationEntryPoint
- 作用:用于處理未認(rèn)證用戶(hù)訪問(wèn)受保護(hù)資源時(shí)的邏輯。
- 功能:可以自定義未認(rèn)證用戶(hù)的響應(yīng),比如返回 401 狀態(tài)碼、重定向到登錄頁(yè)面等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
AuthenticationEntryPoint接口,并重寫(xiě)commence方法。
// 重寫(xiě) AuthenticationEntryPoint 接口
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
}
}
// 讓我們重寫(xiě)的類(lèi)生效
http.exceptionHandling(exception -> {
exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());
});5. SessionInformationExpiredStrategy
- 作用:用于處理用戶(hù)會(huì)話過(guò)期的邏輯。
- 功能:可以自定義會(huì)話超時(shí)后的響應(yīng),比如重定向到登錄頁(yè)面或返回 JSON 響應(yīng)等。
- 實(shí)現(xiàn):實(shí)現(xiàn)
SessionInformationExpiredStrategy接口,并重寫(xiě)onExpiredSession方法。
// 重寫(xiě) SessionInformationExpiredStrategy 接口
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
}
}
// 讓我們重寫(xiě)的類(lèi)生效
http.sessionManagement(session -> {
session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy());
});6. AccessDeniedHandler
- 作用:用于處理用戶(hù)訪問(wèn)被拒絕的情況。當(dāng)用戶(hù)嘗試訪問(wèn)沒(méi)有權(quán)限的資源時(shí),Spring Security 會(huì)調(diào)用實(shí)現(xiàn)了
AccessDeniedHandler接口的處理器。 - 功能:可以自定義拒絕訪問(wèn)后的響應(yīng)行為,比如重定向到特定的錯(cuò)誤頁(yè)面、返回錯(cuò)誤信息或 JSON 響應(yīng)。
- 實(shí)現(xiàn):實(shí)現(xiàn)
AccessDeniedHandler接口,并重寫(xiě)handle方法。
// 重寫(xiě) 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漏洞的問(wèn)題
- 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)同步的幾種方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-12-12
MybatisPlus分頁(yè)排序查詢(xún)字段帶有下劃線的坑及解決
這篇文章主要介紹了MybatisPlus分頁(yè)排序查詢(xún)字段帶有下劃線的坑及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
java獲取當(dāng)前日期和時(shí)間的二種方法分享
這篇文章主要介紹了java獲取當(dāng)前日期和時(shí)間的二種方法,需要的朋友可以參考下2014-03-03
MyBatis利用攔截器實(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-11
idea中定時(shí)及多數(shù)據(jù)源配置方法
因項(xiàng)目要求,需要定時(shí)從達(dá)夢(mèng)數(shù)據(jù)庫(kù)中取數(shù)據(jù),并插入或更新到ORACLE數(shù)據(jù)庫(kù)中,這篇文章主要介紹了idea中定時(shí)及多數(shù)據(jù)源配置方法,需要的朋友可以參考下2023-12-12
Java異步編程之Callbacks與Futures模型詳解
這篇文章主要為大家詳細(xì)介紹了Java異步編程中Callbacks與Futures模型的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03
springboot如何接收復(fù)雜參數(shù)(同時(shí)接收J(rèn)SON與文件)
文章介紹了在Spring Boot中同時(shí)處理JSON和文件上傳時(shí)使用`@RequestPart`注解的方法,`@RequestPart`可以接收多種格式的參數(shù),包括JSON和文件,并且可以作為`multipart/form-data`格式中的key2025-02-02

