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

相关推荐
七夜zippoe2 小时前
分布式配置中心终极对决 Spring Cloud Config与Apollo架构深度解析
分布式·架构·springcloud·apollo·配置中心
那我掉的头发算什么2 小时前
【Mybatis】动态SQL与留言板小项目
数据库·spring boot·sql·spring·mybatis·配置
切糕师学AI2 小时前
ARM 架构中的 CurrentEL
arm开发·架构
vx-bot5556662 小时前
企业微信接口在AI智能体与知识库集成中的架构实践
人工智能·架构·企业微信
Warren982 小时前
一次文件上传异常的踩坑、定位与修复复盘(Spring Boot + 接口测试)
java·开发语言·spring boot·笔记·后端·python·面试
生成论实验室2 小时前
生成式通用智能(GAGI):基于《易经》状态空间的认知架构
人工智能·神经网络·算法·架构·信息与通信
indexsunny2 小时前
互联网大厂Java面试实录:Spring Boot微服务与Kafka消息队列实战解析
java·spring boot·微服务·面试·kafka·电商·技术解析
一灰灰blog2 小时前
Jar包会自己消失?Excel会“记忆“数据?我遇到了两个灵异bug
java·spring boot·bug·excel
计算机毕设指导62 小时前
基于微信小程序的非物质文化遗产推广管理系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven