SpringBoot3的前后端分离架构中使用SpringSecurity的思路

Spring Boot 3 的前后端分离架构中使用 Spring Security 实现登录鉴权,通常采用 基于 Token(如 JWT) 的无状态认证方式。以下是完整的配置思路和关键步骤:


✅ 一、添加依赖(Maven)

xml 复制代码
<!-- 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>

✅ 二、自定义 UserDetailsService

用于从数据库加载用户信息:

java 复制代码
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found"));
        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getUsername())
            .password(user.getPassword()) // 必须是 BCrypt 加密后的
            .roles("USER") // 或根据数据库角色动态设置
            .build();
    }
}

✅ 三、JWT 工具类(示例)

java 复制代码
@Component
public class JwtUtil {
    private String secret = "yourSecretKeyAtLeast32BytesLong"; // 生产环境应配置在 application.yml
    private int jwtExpirationMs = 86400000; // 24 小时

    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

✅ 四、自定义登录接口(绕过 Security 拦截)

java 复制代码
@RestController
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        try {
            authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(),
                    loginRequest.getPassword()
                )
            );
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
        }

        UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());
        String token = jwtUtil.generateToken(userDetails);

        return ResponseEntity.ok(new JwtResponse(token));
    }
}

✅ 五、配置 SecurityFilterChain(核心!)

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // 前后端分离通常禁用 CSRF
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/login").permitAll() // 登录接口放行
                .requestMatchers("/public/**").permitAll() // 其他公开接口
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }
}

✅ 六、实现 JWT 认证过滤器

java 复制代码
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {

        final String header = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (header != null && header.startsWith("Bearer ")) {
            jwt = header.substring(7);
            try {
                username = jwtUtil.getUsernameFromToken(jwt);
            } catch (Exception e) {
                // token 无效
            }
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt)) {
                UsernamePasswordAuthenticationToken authToken =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        chain.doFilter(request, response);
    }
}

✅ 七、前端如何配合?

  • 登录:POST /login,携带 { username, password }

  • 成功后返回 { token: "xxx" }

  • 后续请求在 Header 中带上:

    复制代码
    Authorization: Bearer <token>

✅ 八、其他建议

  • 密码必须使用 BCryptPasswordEncoder 加密存储。
  • JWT Secret 应配置在 application.yml 并避免硬编码。
  • 可考虑刷新 Token 机制(Refresh Token)。
  • 异常处理:可自定义 AccessDeniedHandlerAuthenticationEntryPoint 返回 JSON 而非重定向。

📌 总结

Spring Boot 3 + Security + 前后端分离的关键点:

步骤 说明
禁用 CSRF .csrf().disable()
无状态会话 SessionCreationPolicy.STATELESS
自定义登录接口 放行 /login,手动调用 AuthenticationManager
JWT 过滤器 UsernamePasswordAuthenticationFilter 前校验 Token
前端传 Token Authorization: Bearer xxx

相关推荐
high201115 小时前
【架构】-- Mysql delete vs truncate 深度解析
数据库·mysql·架构
勿忘,瞬间15 小时前
Spring日志
java·spring boot·spring
2601_9577875815 小时前
AI数字人驱动的矩阵内容生产:2026年技术架构与人效革命
人工智能·矩阵·架构
上海云盾第一敬业销售15 小时前
DDoS防护解决方案架构解析:保障网站安全的新利器
安全·架构·ddos
靠谱品牌推荐官16 小时前
【高性能工程】每秒万次物联网数据高频握手:如何设计一套抗丢包的工业级小程序后端微服务架构?
物联网·小程序·架构
fengxin_rou16 小时前
【Redis 位图分片计数详解】:原理、实战架构与避坑最佳实践
数据库·redis·架构·bitmap
靠谱品牌推荐官16 小时前
【高并发实战】如何基于缓存穿透治理机制设计一套高可用的小程序本地缓存中台架构?
缓存·小程序·架构
沪漂阿龙16 小时前
Spring Cloud 面试题深度解析:微服务架构、注册中心、配置中心、Gateway、OpenFeign、负载均衡、熔断降级全攻略
spring cloud·微服务·架构
未若君雅裁16 小时前
SpringMVC 执行流程详解
java·spring boot·spring·状态模式
Mininglamp_271816 小时前
开源端侧 AI Agent 全栈架构解析:Mano-P 模型 + Cider 推理加速 + AFK 自动构建
人工智能·架构·开源·agent·mac·apple silicon·gui agent