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

相关推荐
凤山老林2 分钟前
Spring Boot 集成国产开源图库 HugeGraph 实现图谱分析的技术方案
spring boot·后端·开源·hugegraph·图谱分析
jiangbo_dev13 分钟前
.NET 微服务监控避坑指南:告别盲翻日志,10 分钟搞定 OpenTelemetry 全链路追踪
架构
架构源启18 分钟前
2026 进阶篇:Spring Boot响应式编程 + Spring AI 1.1.4 流式实战 + Vue前端完整实现(避坑指南)
java·前端·vue.js·人工智能·spring boot·spring·ai编程
米高梅狮子33 分钟前
07.基于LNMP架构部署blog应用和DaemonSet、Job
架构
毛骗导演35 分钟前
Cladue Code 源码解析-键盘事件与 Vim 模式:parse-keypress 解析状态机
前端·架构
不甘先生39 分钟前
Go 包引用架构指南:从 internal 隔离到破解循环依赖的实战手册
架构·golang
空中海40 分钟前
第一篇:入门篇 — 认识 Spring Boot 与基础开发
java·spring boot·后端
Devin~Y1 小时前
大厂Java面试实录:Spring Boot/Cloud + Redis/Kafka + JWT + RAG/Agent(小Y翻车版)
java·spring boot·redis·spring cloud·kafka·spring security·jwt
胡利光1 小时前
Context Engineering 实战 02|System Prompt 是架构决策,不是写说明书
java·架构·prompt
薛定猫AI1 小时前
【深度解析】Memo 2.5 Pro:面向长程 Agent 工作流的 MoE 大模型架构与实战接入
架构