Spring Security 完整指南:从入门到实战

一、基础配置示例

1.1 最小化安全配置

@Configuration
@EnableWebSecurity
public class BasicSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .permitAll()
            );
        
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        
        return new InMemoryUserDetailsManager(user);
    }
}
图片[1]_Spring Security 完整指南:从入门到实战_知途无界

1.2 数据库用户存储

@Configuration
public class JdbcSecurityConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public UserDetailsService userDetailsService() {
        JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager(dataSource);
        
        // 自定义查询(如使用现有表结构)
        userDetailsManager.setUsersByUsernameQuery(
            "SELECT username, password, enabled FROM users WHERE username = ?");
        userDetailsManager.setAuthoritiesByUsernameQuery(
            "SELECT username, authority FROM authorities WHERE username = ?");
        
        return userDetailsManager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

二、认证机制详解

2.1 多种认证方式配置

graph TD
    A[认证请求] --> B{认证方式}
    B --> C[表单登录]
    B --> D[JWT认证]
    B --> E[OAuth2]
    B --> F[Basic认证]
    C --> G[Session管理]
    D --> H[Token验证]
    E --> I[第三方登录]
    F --> J[简单认证]

2.2 JWT认证实现

@Component
public class JwtTokenProvider {
    
    private String jwtSecret = "your-secret-key";
    private long jwtExpiration = 86400000; // 24小时

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (Exception ex) {
            // 处理各种异常
        }
        return false;
    }
}

@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );

        return http.build();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter(tokenProvider);
    }
}

三、授权控制

3.1 方法级安全控制

@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig {
    // 方法级安全配置
}

@Service
public class ProductService {

    @PreAuthorize("hasRole('ADMIN') or hasAuthority('PRODUCT_WRITE')")
    public Product createProduct(Product product) {
        // 创建产品逻辑
    }

    @PostAuthorize("returnObject.owner == authentication.name")
    public Product getProduct(Long id) {
        // 获取产品逻辑
    }

    @Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
    public void deleteProduct(Long id) {
        // 删除产品逻辑
    }
}

3.2 动态权限控制

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication, 
                               Object targetDomainObject, 
                               Object permission) {
        // 实现自定义权限逻辑
        if (targetDomainObject instanceof Product product) {
            if ("delete".equals(permission)) {
                return product.getOwner().equals(authentication.getName()) ||
                       authentication.getAuthorities().stream()
                           .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
            }
        }
        return false;
    }
}

四、常见问题解决方案

4.1 密码编码问题

@Configuration
public class PasswordConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 推荐使用BCrypt
        return new BCryptPasswordEncoder();
    }

    // 解决常见密码编码错误
    @Bean
    public DaoAuthenticationProvider authenticationProvider(
            UserDetailsService userDetailsService, 
            PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        provider.setHideUserNotFoundExceptions(false); // 重要配置
        return provider;
    }
}

4.2 CSRF保护配置

@Configuration
public class CsrfConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                // 禁用CSRF(不推荐生产环境)
                // .disable()
                
                // 或配置CSRF token仓库
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                
                // 忽略某些端点
                .ignoringRequestMatchers("/api/public/**")
            );
        
        return http.build();
    }
}

4.3 CORS配置问题

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        return source;
    }
}

五、错误处理与调试

5.1 自定义认证失败处理

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, 
                       HttpServletResponse response,
                       AuthenticationException authException) throws IOException {
        
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json");
        
        Map<String, Object> body = new HashMap<>();
        body.put("timestamp", new Date());
        body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
        body.put("error", "Unauthorized");
        body.put("message", authException.getMessage());
        body.put("path", request.getRequestURI());

        response.getWriter().write(new ObjectMapper().writeValueAsString(body));
    }
}

5.2 访问拒绝处理

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, 
                     HttpServletResponse response,
                     AccessDeniedException accessDeniedException) throws IOException {
        
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json");
        
        Map<String, Object> body = new HashMap<>();
        body.put("timestamp", new Date());
        body.put("status", HttpServletResponse.SC_FORBIDDEN);
        body.put("error", "Forbidden");
        body.put("message", "Access denied");
        body.put("path", request.getRequestURI());

        response.getWriter().write(new ObjectMapper().writeValueAsString(body));
    }
}

六、实战配置示例

6.1 生产环境安全配置

@Configuration
@EnableWebSecurity
public class ProductionSecurityConfig {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private CustomAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 基本配置
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/health", "/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/**").authenticated()
                .anyRequest().denyAll() // 默认拒绝所有
            )
            // 认证配置
            .formLogin(form -> form
                .loginPage("/login")
                .successHandler(authenticationSuccessHandler())
                .failureHandler(authenticationFailureHandler())
                .permitAll()
            )
            // 退出登录
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler())
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
            )
            // 异常处理
            .exceptionHandling(exceptions -> exceptions
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
            )
            // 会话管理
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .maximumSessions(1)
                .expiredUrl("/login?expired")
            )
            // 安全头
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'self'")
                )
                .frameOptions(frame -> frame.sameOrigin())
            )
            // 记住我
            .rememberMe(remember -> remember
                .tokenValiditySeconds(86400) // 24小时
                .userDetailsService(userDetailsService)
            );

        return http.build();
    }
}

6.2 常见问题排查表

问题现象可能原因解决方案
403 ForbiddenCSRF保护启用添加CSRF token或配置忽略
401 Unauthorized认证失败检查密码编码器配置
登录后重定向循环Session配置问题检查session管理配置
权限注解不生效未启用方法安全添加@EnableMethodSecurity
CORS错误跨域配置问题配置CorsConfigurationSource

七、测试与调试

7.1 安全测试配置

@SpringBootTest
@AutoConfigureMockMvc
public class SecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(username = "user", roles = "USER")
    public void testAuthenticatedAccess() throws Exception {
        mockMvc.perform(get("/api/user"))
                .andExpect(status().isOk());
    }

    @Test
    public void testUnauthenticatedAccess() throws Exception {
        mockMvc.perform(get("/api/user"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(username = "user", roles = "USER")
    public void testAccessDenied() throws Exception {
        mockMvc.perform(get("/api/admin"))
                .andExpect(status().isForbidden());
    }
}

7.2 调试技巧

@Configuration
public class DebugConfig {

    @Bean
    public Filter debugFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                                         HttpServletResponse response,
                                         FilterChain filterChain) throws ServletException, IOException {
                // 调试信息输出
                Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                if (auth != null) {
                    System.out.println("Current user: " + auth.getName());
                    System.out.println("Authorities: " + auth.getAuthorities());
                }
                filterChain.doFilter(request, response);
            }
        };
    }
}

这个Spring Security指南涵盖了从基础配置到高级特性的全面内容,包括常见问题的解决方案和最佳实践建议。

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞65 分享
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容