Spring Security中集成JWT的完整實戰(zhàn)指南
在構(gòu)建現(xiàn)代 Web 應(yīng)用和微服務(wù)時,認(rèn)證與授權(quán)是繞不開的核心話題。JWT(JSON Web Token)憑借無狀態(tài)、跨域友好等特性成為業(yè)界寵兒,但并非唯一選擇。同時,很多人疑惑:Spring Security 中到底怎么和 JWT 一起使用?隨機(jī)數(shù)又在哪些安全場景中發(fā)揮作用? 本文將從替代方案對比、隨機(jī)數(shù)應(yīng)用、Spring Security + JWT 實戰(zhàn)三個方面,為你徹底理清這些面試高頻問題。
一、除了 JWT,你還用過哪些認(rèn)證/授權(quán)方案
在實際項目中,我根據(jù)場景還使用過以下方案:
| 方案 | 原理 | 適用場景 | 優(yōu)點 | 缺點 |
|---|---|---|---|---|
| 傳統(tǒng) Session-Cookie | 服務(wù)端存儲 Session,客戶端存儲 Cookie | 單體應(yīng)用、用戶量不大 | 實現(xiàn)簡單,主動失效方便 | 占用服務(wù)端內(nèi)存,橫向擴(kuò)展需共享 Session |
| OAuth2 / OIDC | 第三方授權(quán),頒發(fā) Access Token + Refresh Token | 允許用戶授權(quán)第三方應(yīng)用訪問資源(如“使用微信登錄”) | 標(biāo)準(zhǔn)協(xié)議,廣泛支持 | 實現(xiàn)復(fù)雜,需要引入授權(quán)服務(wù)器 |
| SAML 2.0 | 基于 XML 的安全斷言標(biāo)記語言,通過 SSO 認(rèn)證 | 企業(yè)級單點登錄(如 Microsoft ADFS) | 成熟的企業(yè)標(biāo)準(zhǔn) | XML 臃腫,配置復(fù)雜 |
| API Key | 客戶端攜帶固定 Key 調(diào)用 API | 內(nèi)部服務(wù)調(diào)用、簡單開放 API | 實現(xiàn)極簡 | 易泄露,無有效期,無用戶身份 |
| Basic Auth | HTTP 頭攜帶 base64(user:pass) | 內(nèi)部測試、簡單對接 | HTTP 原生支持 | 明文傳輸(必須配合 HTTPS),無法注銷 |
1.1 什么時候選擇 JWT
- 分布式/微服務(wù)架構(gòu):無需共享存儲,自帶用戶信息。
- 移動端/跨域:Cookie 限制少,頭字段通用。
- 短期授權(quán):如 API 調(diào)用、單點登錄的臨時憑證。
1.2 項目中的真實選型
- 內(nèi)部運維系統(tǒng):Session + Redis 共享存儲。
- 開放平臺 API:API Key + Secret 簽名(防止重放)。
- 用戶端 App + Web:JWT(無狀態(tài),便于水平擴(kuò)展)。
- 企業(yè) SSO 接入:OAuth2 或 SAML(對接公司統(tǒng)一認(rèn)證)。
二、隨機(jī)數(shù)在安全中的妙用(你一定用過?。?/h2>
隨機(jī)數(shù)并非僅僅“隨機(jī)生成一個數(shù)字”,在安全領(lǐng)域有廣泛且關(guān)鍵的用途:
| 應(yīng)用場景 | 隨機(jī)數(shù)作用 | 示例 |
|---|---|---|
| 密碼加鹽(Salt) | 為每個密碼生成唯一隨機(jī)數(shù),與密碼一起哈希,抵抗彩虹表攻擊 | hash(password + salt) |
| 驗證碼 | 隨機(jī)生成數(shù)字/字母,短期有效,防止暴力 破解 | 短信驗證碼 123456 |
| CSRF Token | 每個會話或每個請求生成隨機(jī) Token,防御跨站請求偽造 | Spring Security 默認(rèn)開啟 _csrf |
| 重置密碼 Token | 用戶請求重置密碼時,生成不可猜測的隨機(jī) Token 發(fā)送至郵箱 | token = UUID.randomUUID() |
| 會話 ID | 隨機(jī)生成會話標(biāo)識,難以偽造 | JSESSIONID |
| OAuth2 State 參數(shù) | 防止 CSRF 攻擊,隨機(jī)字符串與請求綁定 | state=abc123 |
| API 簽名 Nonce | 一次性的隨機(jī)數(shù),防止請求重放攻擊 | 微信支付 nonce_str |
| ID 生成器(雪花算法) | 隨機(jī)+時間+機(jī)器號,生成全局唯一 ID | Snowflake ID |
2.1 Java 中生成隨機(jī)數(shù)的正確姿勢
// 安全的隨機(jī)數(shù)(推薦) SecureRandom secureRandom = new SecureRandom(); byte[] bytes = new byte[16]; secureRandom.nextBytes(bytes); String token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); // UUID(部分隨機(jī),不應(yīng)用于高安全場景) String uuid = UUID.randomUUID().toString(); // 生成指定位數(shù)的數(shù)字驗證碼 String code = String.valueOf(secureRandom.nextInt(900000) + 100000);
三、Spring Security 中如何使用 JWT?——完整實戰(zhàn)
Spring Security 本身不直接提供 JWT 支持,需要通過 過濾器 解析 JWT 并手動將認(rèn)證信息存入 SecurityContext。下面是標(biāo)準(zhǔn)做法。
3.1 整體架構(gòu)流程圖

3.2 關(guān)鍵組件與代碼實現(xiàn)
3.2.1 添加依賴(JJWT)
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>3.2.2 JwtTokenUtil(生成與解析)
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
3.2.3 JwtAuthenticationFilter(繼承 OncePerRequestFilter)
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String token = extractToken(request);
if (token != null && jwtTokenUtil.validateToken(token)) {
String username = jwtTokenUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
3.2.4 SecurityConfig 配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtFilter;
@Autowired
private UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return new ProviderManager(provider);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3.2.5 登錄接口(生成 JWT)
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
String token = jwtTokenUtil.generateToken(request.getUsername());
return ResponseEntity.ok(new JwtResponse(token));
} catch (BadCredentialsException e) {
return ResponseEntity.status(401).body("用戶名或密碼錯誤");
}
}
}
3.3 Spring Security 在 JWT 中所起的作用
| 角色 | 說明 |
|---|---|
| AuthenticationManager | 負(fù)責(zé)驗證用戶名密碼 |
| SecurityContextHolder | 存儲已認(rèn)證的用戶信息,供后續(xù)業(yè)務(wù)代碼使用(如 @PreAuthorize) |
| 過濾器鏈 | 我們插入的 JwtAuthenticationFilter 在 UsernamePasswordAuthenticationFilter 之前執(zhí)行 |
| UserDetailsService | 加載用戶權(quán)限,用于生成 Authentication 對象 |
核心:Spring Security 負(fù)責(zé)管理認(rèn)證狀態(tài),JWT 只是傳遞憑證的一種形式。我們編寫的過濾器將 JWT 解析后轉(zhuǎn)換為 Spring Security 能識別的 Authentication 對象。
四、總結(jié)與面試回答模板
面試官問:除了 JWT 你還用過其他認(rèn)證方案嗎?隨機(jī)數(shù)用過沒?
參考回答:
“用過。在傳統(tǒng)單體項目中我使用 Session + Redis 共享存儲;對接第三方登錄時使用 OAuth2;內(nèi)部服務(wù)間調(diào)用使用 API Key + 簽名。隨機(jī)數(shù)在安全方面應(yīng)用廣泛:比如用戶密碼的 鹽值、短信驗證碼、重置密碼的 Token、OAuth2 的 state 參數(shù) 以及防止重放攻擊的 Nonce。我們項目中用 SecureRandom 生成高強(qiáng)度的隨機(jī)數(shù),比 Random 更安全。”
面試官問:你在 JWT 哪兒使用過 Spring Security?
參考回答:“在 Spring Boot 項目中,我將 JWT 作為無狀態(tài)認(rèn)證方案集成到 Spring Security。具體做法是:
- 編寫 JwtAuthenticationFilter 繼承
OncePerRequestFilter,解析請求頭中的 JWT。 - 如果 JWT 有效,從中提取用戶名,調(diào)用
UserDetailsService加載權(quán)限,構(gòu)造UsernamePasswordAuthenticationToken并存入SecurityContextHolder。 - 在
SecurityConfig中禁用 Session,設(shè)置SessionCreationPolicy.STATELESS,并將自定義過濾器加到UsernamePasswordAuthenticationFilter之前。 - 登錄接口使用
AuthenticationManager校驗用戶名密碼,校驗通過后生成 JWT 返回。
Spring Security 在這里提供認(rèn)證管理和權(quán)限控制的骨架,JWT 只是憑證載體。”
到此這篇關(guān)于Spring Security中集成JWT的完整實戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Spring Security集成JWT內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JWT?+?Spring?Security?/?OAuth2.0:微服務(wù)統(tǒng)一登錄、鑒權(quán)、單點登錄全解析
- SpringSecurity+JWT簡單集成的實現(xiàn)
- Spring Security JWT 鑒權(quán)鏈路完整解析
- Spring Security與JWT實現(xiàn)微服務(wù)安全認(rèn)證的方法
- SpringSecurity和jwt實現(xiàn)登錄及權(quán)限認(rèn)證功能
- SpringBoot Security+JWT簡單搭建的實現(xiàn)示例
- Spring?Security+JWT如何實現(xiàn)前后端分離權(quán)限控制
相關(guān)文章
Spring Boot+Nginx實現(xiàn)大文件下載功能
相信很多小伙伴,在日常開放中都會遇到大文件下載的情況,大文件下載方式也有很多,比如非常流行的分片下載、斷點下載;當(dāng)然也可以結(jié)合Nginx來實現(xiàn)大文件下載,在中小項目非常適合使用,這篇文章主要介紹了Spring Boot結(jié)合Nginx實現(xiàn)大文件下載,需要的朋友可以參考下2024-05-05
對SpringBoot項目Jar包進(jìn)行加密防止反編譯
最近項目要求部署到其他公司的服務(wù)器上,但是又不想將源碼泄露出去,要求對正式環(huán)境的啟動包進(jìn)行安全性處理,防止客戶直接通過反編譯工具將代碼反編譯出來,本文介紹了如何對SpringBoot項目Jar包進(jìn)行加密防止反編譯,需要的朋友可以參考下2023-10-10
IDEA 插件 mapper和xml互相跳轉(zhuǎn)操作
這篇文章主要介紹了IDEA 插件 mapper和xml互相跳轉(zhuǎn)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
IDEA生成項目后出現(xiàn)的iml和idea文件問題
這篇文章主要介紹了IDEA生成項目后出現(xiàn)的iml和idea文件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
Java 8 Stream Api 中的 map和 flatMap 操作方法
Java 8提供了非常好用的 Stream API ,可以很方便的操作集合。今天通過這篇文章給大家分享Java 8 Stream Api 中的 map和 flatMap 操作方法,需要的朋友可以參考下2019-11-11

