一、项目依赖配置
首先,在 pom.xml中添加必要的依赖:
<dependencies>
<!-- Spring Boot Starter Web (包含Spring MVC) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security (安全核心) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.5</version> <!-- 建议使用稳定版本 -->
</dependency>
<!-- 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>
</dependencies>
二、安全配置类
创建 SecurityConfig配置类,这是 Spring Security 的核心配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.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 JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
public SecurityConfig(JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter) {
this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
}
// 1. 密码编码器(必须配置)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 禁用CSRF保护(API项目通常不需要)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话
.and()
.authorizeRequests()
// 公开访问的接口
.antMatchers("/api/auth/login", "/api/auth/register").permitAll()
.antMatchers("/api/public/**", "/test/**").permitAll()
.antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // Swagger文档
.anyRequest().authenticated(); // 其他所有请求都需要认证
// 添加JWT过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter,
UsernamePasswordAuthenticationFilter.class);
}
}
三、JWT工具类
创建 JwtUtil用于生成和验证JWT令牌:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
// 从配置文件中读取JWT密钥和有效期
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
// 生成安全的密钥
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
// 生成JWT令牌
public String generateToken(String username, Long userId) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", username);
claims.put("userId", userId);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
// 从令牌中获取用户名
public String getUsernameFromToken(String token) {
return getAllClaimsFromToken(token).getSubject();
}
// 从令牌中获取用户ID
public Long getUserIdFromToken(String token) {
return getAllClaimsFromToken(token).get("userId", Long.class);
}
// 获取所有声明
private Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
// 验证令牌
public boolean validateToken(String token, String username) {
final String tokenUsername = getUsernameFromToken(token);
return (tokenUsername.equals(username) && !isTokenExpired(token));
}
// 检查令牌是否过期
private boolean isTokenExpired(String token) {
final Date expiration = getAllClaimsFromToken(token).getExpiration();
return expiration.before(new Date());
}
}
四、JWT认证过滤器
创建 JwtAuthenticationTokenFilter处理HTTP请求中的JWT令牌:
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 JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtAuthenticationTokenFilter(JwtUtil jwtUtil,
UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// 1. 从请求头中获取Token
final String authHeader = request.getHeader("Authorization");
String token = null;
String username = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
try {
username = jwtUtil.getUsernameFromToken(token);
} catch (Exception e) {
logger.error("JWT令牌解析失败", e);
}
}
// 2. 验证Token并设置认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 从数据库加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 验证Token有效性
if (jwtUtil.validateToken(token, username)) {
// 创建认证令牌
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource()
.buildDetails(request));
// 将认证信息设置到Security上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// 3. 继续过滤器链
filterChain.doFilter(request, response);
}
}
五、自定义UserDetailsService
实现 UserDetailsService接口,用于从数据库加载用户信息:
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 org.springframework.transaction.annotation.Transactional;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserMapper userMapper; // MyBatis Mapper
public CustomUserDetailsService(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户
User user = userMapper.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(
"用户不存在: " + username));
// 转换为Spring Security的UserDetails
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword()) // 数据库存储的是加密后的密码
.authorities("ROLE_USER") // 设置角色/权限
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
}
}