package com.tourguide.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.tourguide.common.api.ApiResponse; import com.tourguide.security.JwtAuthenticationFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; @Configuration public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtFilter, ObjectMapper objectMapper) throws Exception { http.csrf().disable() .cors().and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .exceptionHandling() .authenticationEntryPoint((request, response, exception) -> { response.setStatus(401); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); objectMapper.writeValue(response.getWriter(), ApiResponse.failure(401, "请先登录")); }) .accessDeniedHandler((request, response, exception) -> { response.setStatus(403); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); objectMapper.writeValue(response.getWriter(), ApiResponse.failure(403, "无权访问")); }).and() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers("/api/admin/auth/login", "/api/mini/auth/wechat-phone-login", "/api/guide/auth/login", "/api/guide/auth/password-login", "/api/guide/auth/sms-code", "/api/health").permitAll() .antMatchers("/api/guide/registration/**").permitAll() .antMatchers("/base/dougong/**", "/api/base/dougong/**").permitAll() .antMatchers(HttpMethod.GET, "/uploads/avatars/**").permitAll() .antMatchers("/uploads/guide-applications/**") .hasAnyAuthority("PERM_GUIDE_LIST_VIEW", "PERM_GUIDE_AUDIT_VIEW") .antMatchers(HttpMethod.GET, "/api/mini/scenic-spots", "/api/mini/scenic-spots/**").permitAll() .antMatchers(HttpMethod.GET, "/api/mini/agreements", "/api/mini/agreements/**").permitAll() .antMatchers(HttpMethod.GET, "/api/guide/agreements", "/api/guide/agreements/**").permitAll() .antMatchers("/api/admin/auth/me", "/api/admin/auth/logout").hasRole("ADMIN") .antMatchers(HttpMethod.GET, "/api/admin/dashboard/**") .hasAuthority("PERM_DASHBOARD_VIEW") .antMatchers(HttpMethod.PUT, "/api/admin/mini-users/*/status") .hasAuthority("PERM_VISITOR_LIST_STATUS") .antMatchers(HttpMethod.GET, "/api/admin/mini-users/**") .hasAuthority("PERM_VISITOR_LIST_VIEW") .antMatchers("/api/admin/mini-users/**").hasAuthority("PERM_VISITOR_LIST") .antMatchers(HttpMethod.POST, "/api/admin/guide-users") .hasAuthority("PERM_GUIDE_LIST_ADD") .antMatchers(HttpMethod.PUT, "/api/admin/guide-users/*/status") .hasAuthority("PERM_GUIDE_LIST_STATUS") .antMatchers(HttpMethod.PUT, "/api/admin/guide-users/**") .hasAuthority("PERM_GUIDE_LIST_EDIT") .antMatchers(HttpMethod.POST, "/api/admin/guide-users/**") .hasAuthority("PERM_GUIDE_LIST_EDIT") .antMatchers(HttpMethod.DELETE, "/api/admin/guide-users/**") .hasAuthority("PERM_GUIDE_LIST_EDIT") .antMatchers(HttpMethod.GET, "/api/admin/guide-users/**") .hasAuthority("PERM_GUIDE_LIST_VIEW") .antMatchers("/api/admin/guide-users/**").hasAuthority("PERM_GUIDE_LIST") .antMatchers(HttpMethod.PUT, "/api/admin/guide-applications/*/audit") .hasAuthority("PERM_GUIDE_AUDIT_AUDIT") .antMatchers(HttpMethod.GET, "/api/admin/guide-applications/**") .hasAuthority("PERM_GUIDE_AUDIT_VIEW") .antMatchers("/api/admin/guide-applications/**").hasAuthority("PERM_GUIDE_AUDIT") .antMatchers(HttpMethod.POST, "/api/admin/guide-teams") .hasAuthority("PERM_GUIDE_TEAM_ADD") .antMatchers(HttpMethod.PUT, "/api/admin/guide-teams/*/status") .hasAuthority("PERM_GUIDE_TEAM_STATUS") .antMatchers(HttpMethod.PUT, "/api/admin/guide-teams/**") .hasAuthority("PERM_GUIDE_TEAM_EDIT") .antMatchers(HttpMethod.DELETE, "/api/admin/guide-teams/**") .hasAuthority("PERM_GUIDE_TEAM_DELETE") .antMatchers(HttpMethod.GET, "/api/admin/guide-teams/**") .hasAuthority("PERM_GUIDE_TEAM_VIEW") .antMatchers("/api/admin/guide-teams/**").hasAuthority("PERM_GUIDE_TEAM") .antMatchers(HttpMethod.PUT, "/api/admin/orders/*/refund") .hasAuthority("PERM_ORDER_LIST_REFUND") .antMatchers(HttpMethod.GET, "/api/admin/orders/**") .hasAuthority("PERM_ORDER_LIST_VIEW") .antMatchers("/api/admin/orders/**").hasAuthority("PERM_ORDER_LIST") .antMatchers(HttpMethod.POST, "/api/admin/operation-cities") .hasAuthority("PERM_CITY_LIST_ADD") .antMatchers(HttpMethod.PUT, "/api/admin/operation-cities/*/status") .hasAuthority("PERM_CITY_LIST_STATUS") .antMatchers(HttpMethod.GET, "/api/admin/operation-cities/**") .hasAuthority("PERM_CITY_LIST_VIEW") .antMatchers("/api/admin/operation-cities/**").hasAuthority("PERM_CITY_LIST") .antMatchers(HttpMethod.POST, "/api/admin/operation-scenic-spots") .hasAuthority("PERM_SCENIC_LIST_ADD") .antMatchers(HttpMethod.PUT, "/api/admin/operation-scenic-spots/*/status") .hasAuthority("PERM_SCENIC_LIST_STATUS") .antMatchers(HttpMethod.PUT, "/api/admin/operation-scenic-spots/**") .hasAuthority("PERM_SCENIC_LIST_EDIT") .antMatchers(HttpMethod.DELETE, "/api/admin/operation-scenic-spots/**") .hasAuthority("PERM_SCENIC_LIST_DELETE") .antMatchers(HttpMethod.GET, "/api/admin/operation-scenic-spots/**") .hasAuthority("PERM_SCENIC_LIST_VIEW") .antMatchers("/api/admin/operation-scenic-spots/**").hasAuthority("PERM_SCENIC_LIST") .antMatchers(HttpMethod.DELETE, "/api/admin/reviews/**") .hasAuthority("PERM_REVIEW_LIST_DELETE") .antMatchers(HttpMethod.GET, "/api/admin/reviews/**") .hasAuthority("PERM_REVIEW_LIST_VIEW") .antMatchers("/api/admin/reviews/**").hasAuthority("PERM_REVIEW_LIST") .antMatchers(HttpMethod.PUT, "/api/admin/feedback/**") .hasAuthority("PERM_FEEDBACK_LIST_EDIT") .antMatchers(HttpMethod.GET, "/api/admin/feedback/**") .hasAuthority("PERM_FEEDBACK_LIST_VIEW") .antMatchers("/api/admin/feedback/**").hasAuthority("PERM_FEEDBACK_LIST") .antMatchers(HttpMethod.PUT, "/api/admin/platform-settings/**") .hasAuthority("PERM_PLATFORM_SETTINGS_EDIT") .antMatchers(HttpMethod.GET, "/api/admin/platform-settings/**") .hasAuthority("PERM_PLATFORM_SETTINGS_VIEW") .antMatchers("/api/admin/platform-settings/**").hasAuthority("PERM_PLATFORM_SETTINGS") .antMatchers(HttpMethod.PUT, "/api/admin/agreements/**") .hasAuthority("PERM_AGREEMENT_SETTINGS_EDIT") .antMatchers(HttpMethod.GET, "/api/admin/agreements/**") .hasAuthority("PERM_AGREEMENT_SETTINGS_VIEW") .antMatchers("/api/admin/agreements/**").hasAuthority("PERM_AGREEMENT_SETTINGS") .antMatchers("/api/admin/finance/ledger/**") .hasAnyAuthority("PERM_FINANCE_DETAIL_VIEW", "PERM_FINANCE_REFUND_VIEW") .antMatchers(HttpMethod.PUT, "/api/admin/withdrawals/*/process") .hasAuthority("PERM_WITHDRAWAL_LIST_AUDIT") .antMatchers(HttpMethod.GET, "/api/admin/withdrawals/**") .hasAuthority("PERM_WITHDRAWAL_LIST_VIEW") .antMatchers("/api/admin/withdrawals/**").hasAuthority("PERM_WITHDRAWAL_LIST") .antMatchers(HttpMethod.POST, "/api/admin/roles") .hasAuthority("PERM_ROLE_LIST_ADD") .antMatchers(HttpMethod.PUT, "/api/admin/roles/**") .hasAuthority("PERM_ROLE_LIST_EDIT") .antMatchers(HttpMethod.DELETE, "/api/admin/roles/**") .hasAuthority("PERM_ROLE_LIST_DELETE") .antMatchers(HttpMethod.GET, "/api/admin/roles/**") .hasAuthority("PERM_ROLE_LIST_VIEW") .antMatchers("/api/admin/roles/**").hasAuthority("PERM_ROLE_LIST") .antMatchers(HttpMethod.POST, "/api/admin/users") .hasAuthority("PERM_EMPLOYEE_LIST_ADD") .antMatchers(HttpMethod.PUT, "/api/admin/users/*/status") .hasAuthority("PERM_EMPLOYEE_LIST_STATUS") .antMatchers(HttpMethod.PUT, "/api/admin/users/*/password") .hasAuthority("PERM_EMPLOYEE_LIST_RESET_PASSWORD") .antMatchers(HttpMethod.PUT, "/api/admin/users/**") .hasAuthority("PERM_EMPLOYEE_LIST_EDIT") .antMatchers(HttpMethod.DELETE, "/api/admin/users/**") .hasAuthority("PERM_EMPLOYEE_LIST_DELETE") .antMatchers(HttpMethod.GET, "/api/admin/users/**") .hasAuthority("PERM_EMPLOYEE_LIST_VIEW") .antMatchers("/api/admin/users/**").hasAuthority("PERM_EMPLOYEE_LIST") .antMatchers("/api/admin/**").denyAll() .antMatchers("/api/mini/**").hasRole("MINI_USER") .antMatchers("/api/guide/**").hasRole("GUIDE_USER") .anyRequest().authenticated().and() .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOriginPatterns(Collections.singletonList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Collections.singletonList("*")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }