Spring Boot 集成 JWT 详细指南
在当今的 Web 应用开发中,安全认证是至关重要的一环。JSON Web Token(JWT)作为一种轻量级的认证和授权机制,因其跨域支持、自包含性以及易于使用等优点,被广泛应用于各种前后端分离的项目中。Spring Boot 作为一款快速开发框架,与 JWT 的集成可以帮助开发者快速搭建安全可靠的认证体系。本文将详细介绍如何在 Spring Boot 项目中集成 JWT。
一、JWT 简介
JWT 是一种基于 JSON 的开放标准(RFC 7519),用于在各方之间安全地传输声明。它通常由三部分组成:头部(Header)、负载(Payload)和签名(Signature)。
- 头部(Header):包含令牌的类型(通常是 JWT)和使用的签名算法(如 HMAC SHA256 或 RSA)。
- 负载(Payload):包含声明(Claims),声明是关于实体(通常是用户)和其他数据的声明。声明分为三种类型:注册声明、公开声明和私有声明。
- 签名(Signature):为了创建签名部分,需要使用编码后的头部、编码后的负载、一个密钥(secret)和头部中指定的签名算法来进行签名。
二、创建 Spring Boot 项目
首先,我们需要创建一个新的 Spring Boot 项目。可以使用 Spring Initializr来快速生成项目骨架。在创建项目时,选择以下依赖:
- Spring Web
- Spring Security
创建好项目后,导入到 IDE 中。
三、添加 JWT 依赖
在 pom.xml 文件中添加 JWT 相关的依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
四、配置 JWT 工具类
创建一个 JWT 工具类,用于生成和验证 JWT 令牌。以下是一个简单的示例:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private static final String SECRET_KEY = "yourSecretKey";
private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 10; // 10 hours
public static String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private static String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static String extractUsername(String token) {
return extractClaims(token).getSubject();
}
public static Date extractExpiration(String token) {
return extractClaims(token).getExpiration();
}
private static Claims extractClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private static Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public static Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) &&!isTokenExpired(token));
}
}
五、配置 Spring Security
配置 Spring Security 以使用 JWT 进行认证和授权。创建一个配置类,继承
WebSecurityConfigurerAdapter:
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(new JwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
六、创建 JWT 请求过滤器
创建一个 JWT 请求过滤器,用于拦截请求并验证 JWT 令牌:
import io.jsonwebtoken.ExpiredJwtException;
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.core.userdetails.UserDetailsService;
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 {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = JwtUtil.extractUsername(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (JwtUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
七、创建认证控制器
创建一个认证控制器,用于处理用户登录请求并生成 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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
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);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
class AuthenticationRequest {
private String username;
private String password;
// Getters and setters
}
class AuthenticationResponse {
private String jwt;
public AuthenticationResponse(String jwt) {
this.jwt = jwt;
}
// Getters and setters
}
八、测试集成
启动 Spring Boot 项目,使用 Postman 等工具进行测试。发送 POST 请求到 /authenticate 端点,提供用户名和密码,获取 JWT 令牌。然后在后续的请求中,在请求头中添加 Authorization: Bearer <token> 来进行认证。
九、总结
通过以上步骤,我们成功地在 Spring Boot 项目中集成了 JWT。JWT 为我们提供了一种安全、便捷的认证和授权方式,结合 Spring Security 的强大功能,可以有效地保护我们的 Web 应用。在实际项目中,还可以根据需求对 JWT 的配置和使用进行进一步的优化和扩展。