package com.tanqidi.survey.config; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.web.cors.CorsConfiguration; import java.util.*; @Configuration @EnableWebSecurity @EnableMethodSecurity public class OAuth2LoginSecurityConfig { @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(jwt -> { Collection authorities = new ArrayList<>(); Object realmAccess = jwt.getClaim("realm_access"); if (realmAccess instanceof Map map) { Object roles = map.get("roles"); if (roles instanceof Collection roleList) { for (Object role : roleList) { // System.out.println("ROLE_" + role); authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } } } return authorities; }); return converter; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.configurationSource(request -> { CorsConfiguration configuration = new CorsConfiguration(); // String originsProp = env.getProperty("cors.allowed-origins"); // 你的前端调试地址,防止出现跨域。可以稍微修改下代码做成从application.properties中读取 String originsProp = "http://localhost:3000,http://localhost:3002,"; if (!originsProp.isBlank()) { configuration.setAllowedOrigins(Arrays.asList(originsProp.split(","))); } // 只允许常用的安全方法,满足前端常见请求 configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); // 允许所有请求头,便于前端携带token等自定义头 configuration.setAllowedHeaders(Arrays.asList("*")); // 允许携带cookie和认证信息,适合需要登录态的前后端分离场景 configuration.setAllowCredentials(true); return configuration; })) .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth // .requestMatchers("/login", "/logout", "/error").permitAll() .requestMatchers("/api/admin/**").hasRole("test-service-admin") // .requestMatchers("/api/user/**").hasAnyRole("test-service-user", "test-service-admin") // .anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) // 设置JWT权限转换器,支持自定义角色映射 .authenticationEntryPoint(unauthorizedEntryPoint()) // 指定自定义401响应,token过期/无效也会返回JSON ) .exceptionHandling(handling -> handling .accessDeniedHandler(accessDeniedHandler())); // 全局无权限处理 return http.build(); } @Bean public AuthenticationEntryPoint unauthorizedEntryPoint() { return (request, response, authException) -> { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json;charset=UTF-8"); Map result = new HashMap<>(); result.put("code", 401); result.put("message", "您的访问令牌无效或已过期,请重新登录后再试。"); // 优雅提示 result.put("data", null); response.getWriter().write(new ObjectMapper().writeValueAsString(result)); // 返回自定义JSON格式 }; } @Bean public AccessDeniedHandler accessDeniedHandler() { return (request, response, accessDeniedException) -> { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json;charset=UTF-8"); Map result = new HashMap<>(); result.put("code", 403); result.put("message", "权限不足,您无权访问该资源。"); // 简洁优雅提示 result.put("data", null); response.getWriter().write(new ObjectMapper().writeValueAsString(result)); }; } }