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

相关推荐
A懿轩A3 分钟前
【SpringBoot 快速开发】面向后端开发的 HTTP 协议详解:请求报文、响应码与常见设计规范
spring boot·http·设计规范
兴趣使然黄小黄5 分钟前
【Docker】Docker架构详解:核心组件及其应用指南
docker·容器·架构
玄〤6 分钟前
个人博客网站搭建day6--Spring Boot自定义RedisTemplate配置:优化序列化与Java8时间类型支持
java·spring boot·redis·后端·spring
我爱娃哈哈17 分钟前
SpringBoot + MQTT + EMQX:物联网设备上行数据实时接入与指令下发平台
spring boot·后端·物联网
2501_9333295518 分钟前
技术深度拆解:Infoseek媒体发布系统的分布式架构与自动化实现
分布式·架构·媒体
麦聪聊数据23 分钟前
数据流通的最后一公里:SQL2API 在企业数据市场中的履约架构实践
数据库·sql·低代码·微服务·架构
学嵌入式的小杨同学1 小时前
嵌入式硬件开发入门:PCB 设计核心流程 + 基础元器件实战指南
vscode·后端·嵌入式硬件·架构·vim·智能硬件·pcb工艺
凌云拓界12 小时前
前端开发的“平衡木”:在取舍之间找到最优解
前端·性能优化·架构·前端框架·代码规范·设计规范
nbsaas-boot13 小时前
多租户低代码 SaaS 平台架构白皮书
低代码·架构
葡萄城技术团队14 小时前
从 Shortcut 的爆火,看 AI 时代电子表格的技术底座与架构演进
人工智能·架构