package com.xyw.console.config;
import com.fasterxml.jackson.databind.ObjectMapper; import com.xyw.console.auth.AuthTokenFilter; import com.xyw.console.common.ApiResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfException;
@Configuration @EnableWebSecurity
public class SecurityConfig {
  /** 代码作用（白话）：配置无状态认证、CSRF 和统一 401/403 响应；关联文件：AuthTokenFilter.java、WebConfig.java。关联逻辑（调用链/数据流）：浏览器请求 -> Security Filter Chain -> Cookie 验签 -> Controller。 */
  @Bean SecurityFilterChain securityFilterChain(HttpSecurity http, AuthTokenFilter filter, ObjectMapper mapper) throws Exception { return http.httpBasic(AbstractHttpConfigurer::disable).formLogin(AbstractHttpConfigurer::disable).csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(csrfRequestHandler()).ignoringRequestMatchers("/api/auth/login")).cors(cors -> {}).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.OPTIONS,"/**").permitAll().requestMatchers("/api/auth/login", "/api/auth/csrf").permitAll().anyRequest().authenticated()).addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class).exceptionHandling(errors -> errors.authenticationEntryPoint((request,response,error)->write(mapper,response,401,"请先登录")).accessDeniedHandler((request,response,error)->write(mapper,response,403,accessDeniedMessage(error)))).build(); }
  /** 代码作用（白话）：指定单页应用使用的 CSRF 请求值读取方式，让前端从 XSRF-TOKEN Cookie 读取的原始值可由 X-XSRF-TOKEN 请求头提交。关联文件：auth-api-client.js、SecurityConfigTest.java。关联逻辑（调用链/数据流）：CSRF Cookie -> 前端请求头 -> 此处理器 -> Spring Security 放行合法写请求。 */
  CsrfTokenRequestAttributeHandler csrfRequestHandler() { return new CsrfTokenRequestAttributeHandler(); }
  /** Plain purpose: choose a safe client action for CSRF failures without exposing account or token details. Related files: auth-api-client.js, SecurityConfigTest.java. Flow: Spring access denial -> safe message -> JSON response -> browser refresh/retry guidance. */
  String accessDeniedMessage(Exception error) { return error instanceof CsrfException ? "安全校验已失效，请刷新页面后重试" : "没有权限"; }
  /** 代码作用（白话）：生成 BCrypt 编码器，确保密码只存不可逆哈希；关联文件：AuthService.java。关联逻辑（调用链/数据流）：开发者设密码/登录验证 -> BCrypt。 */
  @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); }
  /** 代码作用（白话）：把安全层错误也保持为现有 code/message/data 格式；关联文件：ApiResponse.java、前端 API 客户端。关联逻辑（调用链/数据流）：安全拒绝 -> JSON 响应 -> 页面错误提示。 */
  private void write(ObjectMapper mapper, HttpServletResponse response, int status, String message) throws java.io.IOException { response.setStatus(status); response.setContentType("application/json;charset=UTF-8"); mapper.writeValue(response.getWriter(), ApiResponse.error(status,message)); }
}
