Spring Boot中基于Spring Security+JWT实现登录注册(非WebSecurityConfigurerAdapter方案)
解决方案建议:Spring Boot Security + JWT + MySQL(无WebSecurityConfigurerAdapter)
一、核心实现逻辑(适配Spring Security 5.7+)
废弃WebSecurityConfigurerAdapter后,核心通过SecurityFilterChain、UserDetailsService、PasswordEncoder等独立Bean完成安全配置,替代原适配器的链式配置模式,更符合Spring的Bean管理规范。
二、关键代码实现
1. 依赖配置(pom.xml)
引入必要依赖:
<dependencies> <!-- Spring Security核心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </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> <!-- Spring Data JPA与MySQL驱动 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> </dependencies>
2. 安全FilterChain配置
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.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 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.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomUserDetailsService userDetailsService; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomUserDetailsService userDetailsService) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.userDetailsService = userDetailsService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() // 放行注册、登录接口 .anyRequest().authenticated() ) .userDetailsService(userDetailsService); // 在用户名密码过滤器前插入JWT认证过滤器 http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } }
3. JWT工具类
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtUtil { // 建议从配置文件读取密钥,不要硬编码 private final String SECRET_KEY = "your-secure-secret-key"; private final long JWT_EXPIRATION = 86400000; // 24小时有效期 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 = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userDetails.getUsername()); } 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() + JWT_EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }
4. JWT认证过滤器
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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 java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final CustomUserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtUtil jwtUtil, CustomUserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authHeader != null && authHeader.startsWith("Bearer ")) { jwt = authHeader.substring(7); username = jwtUtil.extractUsername(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } }
5. 用户实体与UserDetailsService实现
// 用户实体类 import jakarta.persistence.*; import lombok.Data; @Entity @Table(name = "users") @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String username; private String password; private String email; // 可扩展角色字段:private String role; } // 自定义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; @Service public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在:" + username)); return org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) .roles("USER") // 可根据实体角色字段动态设置 .build(); } } // UserRepository接口 import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByUsername(String username); }
6. 注册/登录控制器
import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/auth") public class AuthController { private final AuthenticationManager authenticationManager; private final CustomUserDetailsService userDetailsService; private final JwtUtil jwtUtil; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public AuthController(AuthenticationManager authenticationManager, CustomUserDetailsService userDetailsService, JwtUtil jwtUtil, UserRepository userRepository, PasswordEncoder passwordEncoder) { this.authenticationManager = authenticationManager; this.userDetailsService = userDetailsService; this.jwtUtil = jwtUtil; this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } @PostMapping("/login") public ResponseEntity<String> login(@RequestBody AuthRequest authRequest) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername()); final String jwt = jwtUtil.generateToken(userDetails); return ResponseEntity.ok(jwt); } @PostMapping("/register") public ResponseEntity<String> register(@RequestBody User user) { if (userRepository.findByUsername(user.getUsername()).isPresent()) { return ResponseEntity.badRequest().body("用户名已存在"); } user.setPassword(passwordEncoder.encode(user.getPassword())); userRepository.save(user); return ResponseEntity.ok("注册成功"); } } // 登录请求DTO import lombok.Data; @Data public class AuthRequest { private String username; private String password; }
三、对你参考示例的评价
- Bezkoder的内容架构清晰,覆盖完整,但需注意其旧版本示例可能仍使用废弃类,优先查看2023年后的更新内容,核心逻辑可复用,仅需替换安全配置部分。
- iamprafful的2022年文章提到的最佳实践逻辑可行,但安全配置需替换为
SecurityFilterChain模式。 - Dev.to的示例若为旧版本,需修改配置部分,JWT生成、验证逻辑可直接复用。
四、额外注意事项
- 密钥
SECRET_KEY需放在配置文件或环境变量中,禁止硬编码。 - 可扩展角色权限控制,通过数据库存储角色信息,在
UserDetailsService中动态加载权限。 - 建议添加JWT刷新令牌机制,避免用户频繁登录。
- MySQL数据库配置需在
application.properties中正确设置:
spring.datasource.url=jdbc:mysql://localhost:3306/your_db_name spring.datasource.username=db_username spring.datasource.password=db_password spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

