Spring Boot JWT(JSON Web Token):無狀態認證
在現代 Web 開發中,無狀態認證是一種重要的安全機制,它允許服務器在不存儲會話信息的情況下驗證用戶身份。JSON Web Token(JWT)是一種常用的無狀態認證技術,它通過一個緊湊的 URL 安全令牌來傳遞用戶身份信息。Spring Boot 與 JWT 的結合可以為應用提供強大的安全保護。本文將詳細介紹如何在 Spring Boot 中集成 JWT 實現無狀態認證。
一、JWT 概述
1.1 什么是 JWT
JWT 是一種基于 JSON 的開放標準(RFC 7519),用于在各方之間傳遞聲明信息。它由三部分組成:頭部(Header)、載荷(Payload)和簽名(Signature)。JWT 的主要特點是無狀態、自包含和安全,非常適合用于分布式系統和微服務架構中的身份驗證和信息交換。
1.2 JWT 的工作原理
當用戶使用用戶名和密碼登錄系統時,認證服務器會驗證用戶的身份。如果驗證成功,服務器會生成一個 JWT,并將其返回給客戶端。客戶端在后續的請求中,將 JWT 放在請求頭的 Authorization
字段中,格式為 Bearer <token>
。服務器接收到請求后,會驗證 JWT 的合法性,如果驗證通過,則允許用戶訪問受保護的資源。
二、Spring Boot 集成 JWT
2.1 添加依賴
在 Spring Boot 項目中,需要在 pom.xml
文件中添加 JWT 和 Spring Security 的依賴。
<!-- Spring Security -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><!-- JWT -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
2.2 配置 JWT 工具類
創建一個工具類 JwtUtil
,用于生成和驗證 JWT。
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Component
public class JwtUtil {@Value("${jwt.secret}")private String secret;public String generateToken(String username) {Map<String, Object> claims = new HashMap<>();return Jwts.builder().setClaims(claims).setSubject(username).setIssuedAt(new Date(System.currentTimeMillis())).setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 小時有效期.signWith(SignatureAlgorithm.HS256, secret).compact();}public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}public Date extractExpiration(String token) {return extractClaim(token, Claims::getExpiration);}public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {final Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();return claimsResolver.apply(claims);}public Boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}public Boolean validateToken(String token, String username) {final String extractedUsername = extractUsername(token);return (extractedUsername.equals(username) && !isTokenExpired(token));}
}
2.3 配置 Spring Security
創建一個 SecurityConfig
類,配置 Spring Security 以支持 JWT 認證。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtRequestFilter jwtRequestFilter;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/api/authenticate").permitAll().anyRequest().authenticated().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
2.4 創建 JWT 請求過濾器
創建一個 JwtRequestFilter
類,用于在每個請求中解析 JWT,并將其設置到 Spring Security 上下文中。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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;@Component
public class JwtRequestFilter extends OncePerRequestFilter {@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {final String authorizationHeader = request.getHeader("Authorization");String username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {jwt = authorizationHeader.substring(7);username = jwtUtil.extractUsername(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (jwtUtil.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}chain.doFilter(request, response);}
}
2.5 創建認證和授權接口
創建一個 AuthenticationController
類,用于處理用戶登錄和生成 JWT。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api")
public class AuthenticationController {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserDetailsService userDetailsService;@PostMapping("/authenticate")public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {try {authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));} catch (BadCredentialsException e) {throw new Exception("Incorrect username or password", e);}final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());final String jwt = jwtUtil.generateToken(userDetails.getUsername());return ResponseEntity.ok(new AuthenticationResponse(jwt));}
}
2.6 創建用戶詳情服務
創建一個 UserDetailsService
實現類,用于加載用戶信息。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.ArrayList;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());}
}
2.7 配置 application.properties
在 application.properties
文件中配置 JWT 密鑰。
jwt.secret=yourSecretKey
2.8 測試
啟動應用后,可以通過發送 POST 請求到 /api/authenticate
接口進行登錄,獲取 JWT。然后在請求頭中添加 Authorization: Bearer <token>
,即可訪問受保護的接口。
三、總結
通過本文的介紹,我們詳細學習了如何在 Spring Boot 中集成 JWT 實現無狀態認證。我們添加了必要的依賴,配置了 JWT 工具類、Spring Security、JWT 請求過濾器、認證和授權接口以及用戶詳情服務。希望這些內容能夠幫助你在實際開發中更好地應用 JWT,提升應用的安全性。