diff --git a/src/main/java/com/vibevault/config/SecurityConfig.java b/src/main/java/com/vibevault/config/SecurityConfig.java index f179cf6..301571b 100644 --- a/src/main/java/com/vibevault/config/SecurityConfig.java +++ b/src/main/java/com/vibevault/config/SecurityConfig.java @@ -1,15 +1,16 @@ package com.vibevault.config; import com.vibevault.security.JwtAuthenticationFilter; -import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.Customizer; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 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.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @@ -17,9 +18,9 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic /** * Spring Security 配置 - * - * 需要实现: - * - 公开接口无需认证:/api/auth/**, GET /api/playlists, GET /api/playlists/{id} + * + * 需要实现: + * - 公开接口无需认证: /api/auth/**, GET /api/playlists, GET /api/playlists/{id} * - 其他接口需要认证 * - 未认证访问受保护资源返回 401(不是 403) * - 配置 JWT 过滤器 @@ -32,26 +33,68 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final UserDetailsService userDetailsService; - public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { + // 构造注入过滤器和用户详情服务 + public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, UserDetailsService userDetailsService) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; + this.userDetailsService = userDetailsService; } - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // TODO: 配置安全规则 - // 提示: - // - 使用 http.authorizeHttpRequests() 配置路径权限 - // - 使用 http.csrf(csrf -> csrf.disable()) 禁用 CSRF - // - 使用 http.sessionManagement() 配置无状态会话 - // - 使用 http.exceptionHandling() 配置 401 响应 - // - 使用 http.addFilterBefore() 添加 JWT 过滤器 - - return http.build(); - } - + // 配置密码编码器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} + + // 配置认证提供者 + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + // 配置认证管理器 + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + // 核心安全过滤链配置 + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // 1. 禁用CSRF(REST API不需要) + .csrf(csrf -> csrf.disable()) + + // 2. 配置接口权限 + .authorizeHttpRequests(auth -> auth + // 公开接口无需认证 + .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("GET", "/api/playlists").permitAll() + .requestMatchers("GET", "/api/playlists/{id}").permitAll() + // 其他接口需要认证 + .anyRequest().authenticated() + ) + + // 3. 未认证返回401(默认行为,确保不被覆盖) + .exceptionHandling(ex -> ex + .authenticationEntryPoint((request, response, authException) -> { + response.setStatus(401); + response.getWriter().write("Unauthorized: " + authException.getMessage()); + }) + ) + + // 4. 使用无状态会话(JWT不需要会话) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + + // 5. 配置JWT过滤器(在用户名密码过滤器之前执行) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file