目录
(二)配置 Spring Security 以使用 JWT 过滤器
在现代 Web 应用开发中,JSON Web Tokens(JWT)作为一种流行的认证机制,为用户认证和授权提供了安全且便捷的解决方案。本章节将详细介绍在 Spring Boot 项目中使用 JWT 令牌进行用户认证的步骤,包括 JWT 相关依赖的引入、令牌的生成与验证逻辑实现,以及与 Spring Security 的集成等方面,并附上完整的代码示例。
一、JWT 概述
JWT 是一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。它可以在客户端和服务器之间传递用户信息,并且由于其签名机制,信息的完整性和真实性可以得到验证。这使得 JWT 在无状态的分布式系统和前后端分离项目中得到广泛应用。
二、引入 JWT 依赖
(一)添加 Maven 依赖
- 在项目的
pom.xml
文件中添加 JWT 相关依赖:
<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>
这些依赖将使我们能够在项目中使用 JWT 的功能,包括令牌的创建、解析和验证。
三、创建 JWT 工具类
(一)定义JwtUtils
类
- 在项目的
com.example.myproject.utils
包下(假设项目包名为com.example.myproject
)创建一个名为JwtUtils
的工具类,用于处理 JWT 令牌的生成和验证操作。 - 代码示例:
package com.example.myproject.utils;
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;
import java.util.function.Function;
public class JwtUtils {
// 定义密钥,用于签名和验证令牌,应妥善保管
private static final String SECRET_KEY = "your-secret-key";
// 定义令牌过期时间,单位为毫秒,这里设置为1小时
private static final long EXPIRATION_TIME = 1000 * 60 * 60;
// 从令牌中获取用户名
public static String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
// 获取令牌过期时间
public static Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
// 通用方法,从令牌中获取指定声明
public static <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
// 解析令牌,获取所有声明
private static Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
// 检查令牌是否过期
private static boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
// 生成令牌
public static String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
// 内部方法,实际生成令牌
private static String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
// 验证令牌是否有效
public static boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) &&!isTokenExpired(token));
}
}
这个工具类提供了生成 JWT 令牌、从令牌中获取信息(如用户名)以及验证令牌有效性的方法。
四、集成 JWT 与 Spring Security
(一)自定义用户认证过滤器
- 创建
JwtAuthenticationFilter
类- 在
com.example.myproject.filter
包下(如果不存在则创建)创建一个名为JwtAuthenticationFilter
的过滤器类,用于拦截请求并验证 JWT 令牌。 - 代码示例:
- 在
package com.example.myproject.filter;
import com.example.myproject.utils.JwtUtils;
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 JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader!= null && authorizationHeader.startsWith("Bearer ")) {
String token = authorizationHeader.substring(7);
String username = JwtUtils.getUsernameFromToken(token);
if (username!= null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (JwtUtils.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(request, response);
}
}
这个过滤器会从请求头中获取 JWT 令牌,验证令牌的有效性,并根据令牌中的用户信息设置 Spring Security 的认证上下文。
(二)配置 Spring Security 以使用 JWT 过滤器
- 修改
SecurityConfig
类- 在之前创建的
SecurityConfig
类(用于配置 Spring Security)中,添加对JwtAuthenticationFilter
的配置。 - 代码示例:
- 在之前创建的
package com.example.myproject.config;
import com.example.myproject.filter.JwtAuthenticationFilter;
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.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
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationFilter jwtAuthenticationFilter) {
this.userDetailsService = userDetailsService;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
在上述配置中,我们允许/api/auth/**
路径下的请求无需认证,其他请求需要认证。同时,将JwtAuthenticationFilter
添加到 Spring Security 的过滤器链中,在UsernamePasswordAuthenticationFilter
之前执行,以便在基于用户名和密码的认证之前先进行 JWT 令牌的验证。
(三)创建用户登录接口(用于生成 JWT 令牌)
- 在
UserController
中添加登录方法(假设已有UserController
用于处理用户相关请求)- 代码示例:
package com.example.myproject.controller;
import com.example.myproject.entity.User;
import com.example.myproject.service.UserService;
import com.example.myproject.utils.JwtUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final AuthenticationManager authenticationManager;
private final UserService userService;
public UserController(AuthenticationManager authenticationManager, UserService userService) {
this.authenticationManager = authenticationManager;
this.userService = userService;
}
@PostMapping("/api/auth/login")
public ResponseEntity<?> login(@RequestBody UserLoginRequest userLoginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(userLoginRequest.getUsername(), userLoginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
User user = userService.findByUsername(userLoginRequest.getUsername());
String token = JwtUtils.generateToken(user);
return new ResponseEntity<>(new JwtResponse(token), HttpStatus.OK);
}
}
这里,当用户登录成功后,会生成一个 JWT 令牌并返回给客户端。客户端在后续的请求中需要携带这个令牌进行认证。
通过以上步骤,我们成功地在 Spring Boot 项目中引入并使用了 JWT 令牌进行用户认证。在实际应用中,还可以进一步优化,如处理令牌刷新、添加更多的令牌验证规则(如令牌黑名单机制)、优化错误处理等,以提高系统的安全性和用户体验。同时,确保密钥的安全性,避免泄露导致安全问题。