JSON Web Token (JWT) 是一种开放标准 (RFC 7519),用于在网络应用之间安全地传输信息。JWT 通常用于身份验证和信息交换,因为它可以包含声明(claims),这些声明可以用于验证用户的身份和权限。在 Spring Boot 应用中实现 JWT 认证可以帮助我们构建安全的 RESTful API。本文将详细介绍如何在 Spring Boot 应用中实现 JWT 认证。
1. 什么是 JWT?
JWT 是一种紧凑且自包含的方式,用于在网络应用之间安全地传输信息。JWT 包含三个部分:头部(Header)、载荷(Payload)和签名(Signature)。每个部分都是 Base64 编码的字符串,中间用点(.
)分隔。
- Header:包含令牌类型(通常是 JWT)和哈希算法(如 HMAC SHA-256 或 RSA)。
- Payload:包含声明,这些声明可以是预定义的(如
iss
、sub
、aud
等)或自定义的。 - Signature:用于验证消息在传输过程中没有被篡改,并且对于使用私钥签名的令牌,还可以验证发送方的身份。
2. 添加依赖
首先,我们需要在项目中添加 JWT 相关的依赖。这里我们使用 jjwt
库来处理 JWT 的生成和解析。如果你使用的是 Maven,可以在 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
如果你使用 Gradle,可以在 build.gradle
文件中添加:
implementation 'io.jsonwebtoken:jjwt:0.9.1'
3. 创建 JWT 工具类
接下来,我们创建一个工具类来生成和解析 JWT。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private String secret = "yourSecretKey";
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
private 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() + 1000 * 60 * 60 * 10)) // 10 hours
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Boolean validateToken(String token, String username) {
final String extractedUsername = getUsernameFromToken(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
4. 配置 Spring Security
为了实现 JWT 认证,我们需要配置 Spring Security。首先,添加 Spring Security 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后,创建一个 SecurityConfig
类来配置安全设置。
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.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(UserDetailsService userDetailsService, JwtRequestFilter jwtRequestFilter) {
this.userDetailsService = userDetailsService;
this.jwtRequestFilter = jwtRequestFilter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
5. 创建 JWT 过滤器
我们需要创建一个过滤器来拦截请求并验证 JWT。
import org.springframework.beans.factory.annotation.Autowired;
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 {
private final UserDetailsService userDetailsService;
private final JwtUtil jwtUtil;
@Autowired
public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
@Override
protected 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.getUsernameFromToken(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
6. 创建认证控制器
最后,我们创建一个控制器来处理用户的认证请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthenticateController {
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
@Autowired
public AuthenticateController(AuthenticationManager authenticationManager, JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
} catch (Exception 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));
}
}
class AuthenticationRequest {
private String username;
private String password;
// Getters and Setters
}
class AuthenticationResponse {
private final String token;
public AuthenticationResponse(String token) {
this.token = token;
}
// Getters
}
7. 总结
通过以上步骤,我们成功地在 Spring Boot 应用中实现了 JWT 认证。JWT 认证不仅可以提高应用的安全性,还可以简化认证流程,减少服务器的负载。希望本文对你理解如何在 Spring Boot 应用中实现 JWT 认证有所帮助。如果你有任何疑问或建议,欢迎留言交流。