【Spring Security,强大的Java认证与授权框架!】

在企业级Java应用安全领域,Spring Security作为Spring生态的安全基石,已经成为保护Web应用和微服务系统的行业标准。这个全面的安全框架通过深度集成的认证和授权机制,为Java应用提供了企业级的安全防护能力。在金融交易平台中,Spring Security保护着数亿用户的资金安全;在医疗健康系统中,它确保着患者数据的隐私合规;在政府服务平台中,它实现着多级权限的精细控制。从用户登录认证到API访问授权,从防止CSRF攻击到会话安全管理,Spring Security都在幕后构建着坚实的安全防线,为现代数字化应用的可信运行提供了根本保障。

核心架构与安全概念

1. 认证与授权基础架构

Spring Security的核心基于过滤器链机制,提供全面的安全保护。

java

// 基础安全配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

    private final UserDetailsService userDetailsService;
    private final JwtTokenProvider jwtTokenProvider;
    private final AuthenticationEntryPoint authenticationEntryPoint;
    private final AccessDeniedHandler accessDeniedHandler;
    
    @Autowired
    public SecurityConfig(UserDetailsService userDetailsService,
                         JwtTokenProvider jwtTokenProvider,
                         AuthenticationEntryPoint authenticationEntryPoint,
                         AccessDeniedHandler accessDeniedHandler) {
        this.userDetailsService = userDetailsService;
        this.jwtTokenProvider = jwtTokenProvider;
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.accessDeniedHandler = accessDeniedHandler;
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 禁用CSRF(用于API服务)或根据需求配置
            .csrf().disable()
            
            // 异常处理
            .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
            
            // 会话管理
            .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            
            // 授权配置
            .and()
            .authorizeHttpRequests(authorize -> authorize
                // 公开端点
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/api-docs/**", "/swagger-ui/**").permitAll()
                
                // 基于角色的授权
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/manager/**").hasAnyRole("ADMIN", "MANAGER")
                
                // 基于权限的授权
                .requestMatchers(HttpMethod.GET, "/api/products/**").hasAuthority("PRODUCT_READ")
                .requestMatchers(HttpMethod.POST, "/api/products/**").hasAuthority("PRODUCT_WRITE")
                .requestMatchers(HttpMethod.PUT, "/api/products/**").hasAuthority("PRODUCT_UPDATE")
                .requestMatchers(HttpMethod.DELETE, "/api/products/**").hasAuthority("PRODUCT_DELETE")
                
                // 业务特定授权
                .requestMatchers("/api/orders/**").authenticated()
                .requestMatchers("/api/users/{userId}/**")
                    .access("@userSecurity.checkUserId(authentication,#userId)")
                
                // 默认规则
                .anyRequest().authenticated()
            )
            
            // JWT过滤器
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            
            // 安全头配置
            .headers()
                .contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'")
                .and()
                .frameOptions().deny()
                .xssProtection().block(false);
        
        return http.build();
    }
    
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用BCrypt加密算法
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com", "http://localhost:3000"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        configuration.setExposedHeaders(Arrays.asList("Authorization", "X-Total-Count"));
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

2. 用户详细服务与密码加密

java

// 自定义UserDetailsService实现
@Service
@Transactional(readOnly = true)
@Slf4j
public class CustomUserDetailsService implements UserDetailsService {
    
    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final LoginAttemptService loginAttemptService;
    
    @Autowired
    public CustomUserDetailsService(UserRepository userRepository,
                                  RoleRepository roleRepository,
                                  LoginAttemptService loginAttemptService) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.loginAttemptService = loginAttemptService;
    }
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 检查登录尝试次数(防止暴力破解)
        if (loginAttemptService.isBlocked(username)) {
            throw new AccountLockedException("账户已被锁定,请稍后重试");
        }
        
        // 查找用户
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
        
        // 检查账户状态
        if (!user.isEnabled()) {
            throw new DisabledException("账户已被禁用");
        }
        
        if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException("账户已过期");
        }
        
        if (!user.isAccountNonLocked()) {
            throw new LockedException("账户已被锁定");
        }
        
        if (!user.isCredentialsNonExpired()) {
            throw new CredentialsExpiredException("凭证已过期");
        }
        
        // 加载用户权限
        List<GrantedAuthority> authorities = loadUserAuthorities(user.getId());
        
        // 构建UserDetails对象
        return new CustomUserDetails(
            user.getId(),
            user.getUsername(),
            user.getPassword(),
            user.getEmail(),
            user.isEnabled(),
            user.isAccountNonExpired(),
            user.isCredentialsNonExpired(),
            user.isAccountNonLocked(),
            authorities,
            user.getLastLoginTime(),
            user.getFailedLoginAttempts()
        );
    }
    
    @Override
    public UserDetails loadUserById(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + userId));
        
        List<GrantedAuthority> authorities = loadUserAuthorities(userId);
        
        return new CustomUserDetails(
            user.getId(),
            user.getUsername(),
            user.getPassword(),
            user.getEmail(),
            user.isEnabled(),
            true, true, true,
            authorities,
            user.getLastLoginTime(),
            user.getFailedLoginAttempts()
        );
    }
    
    private List<GrantedAuthority> loadUserAuthorities(Long userId) {
        // 查询用户角色和权限
        List<Role> roles = roleRepository.findByUserId(userId);
        
        return roles.stream()
            .flatMap(role -> {
                // 角色本身作为权限
                SimpleGrantedAuthority roleAuthority = 
                    new SimpleGrantedAuthority("ROLE_" + role.getCode());
                
                // 角色的所有权限
                List<SimpleGrantedAuthority> permissions = role.getPermissions().stream()
                    .map(permission -> new SimpleGrantedAuthority(permission.getCode()))
                    .collect(Collectors.toList());
                
                // 合并角色和权限
                List<SimpleGrantedAuthority> authorities = new ArrayList<>();
                authorities.add(roleAuthority);
                authorities.addAll(permissions);
                
                return authorities.stream();
            })
            .collect(Collectors.toList());
    }
}

// 自定义UserDetails实现
public class CustomUserDetails implements UserDetails {
    
    private final Long userId;
    private final String username;
    private final String password;
    private final String email;
    private final boolean enabled;
    private final boolean accountNonExpired;
    private final boolean credentialsNonExpired;
    private final boolean accountNonLocked;
    private final Collection<? extends GrantedAuthority> authorities;
    private final LocalDateTime lastLoginTime;
    private final int failedLoginAttempts;
    
    // 构造函数、getter方法等
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
    
    @Override
    public String getPassword() {
        return password;
    }
    
    @Override
    public String getUsername() {
        return username;
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }
    
    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }
    
    @Override
    public boolean isEnabled() {
        return enabled;
    }
    
    // 自定义方法
    public Long getUserId() {
        return userId;
    }
    
    public String getEmail() {
        return email;
    }
    
    public LocalDateTime getLastLoginTime() {
        return lastLoginTime;
    }
    
    public int getFailedLoginAttempts() {
        return failedLoginAttempts;
    }
    
    // 检查权限的便捷方法
    public boolean hasRole(String role) {
        return authorities.stream()
            .anyMatch(auth -> auth.getAuthority().equals("ROLE_" + role));
    }
    
    public boolean hasPermission(String permission) {
        return authorities.stream()
            .anyMatch(auth -> auth.getAuthority().equals(permission));
    }
}

高级特性与应用

1. JWT认证与令牌管理

java

// JWT令牌提供者
@Component
public class JwtTokenProvider {
    
    private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
    
    @Value("${security.jwt.secret:defaultSecretKey}")
    private String jwtSecret;
    
    @Value("${security.jwt.expiration:86400000}")
    private long jwtExpirationMs;
    
    @Value("${security.jwt.refresh-expiration:604800000}")
    private long refreshExpirationMs;
    
    public String generateAccessToken(UserDetails userDetails) {
        return generateTokenFromUsername(userDetails.getUsername(), jwtExpirationMs);
    }
    
    public String generateRefreshToken(UserDetails userDetails) {
        return generateTokenFromUsername(userDetails.getUsername(), refreshExpirationMs);
    }
    
    private String generateTokenFromUsername(String username, long expirationMs) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expirationMs);
        
        // 构建JWT声明
        Map<String, Object> claims = new HashMap<>();
        claims.put("sub", username);
        claims.put("iat", now);
        claims.put("exp", expiryDate);
        
        // 可以添加自定义声明
        if (userDetails instanceof CustomUserDetails) {
            CustomUserDetails customUserDetails = (CustomUserDetails) userDetails;
            claims.put("userId", customUserDetails.getUserId());
            claims.put("email", customUserDetails.getEmail());
        }
        
        return Jwts.builder()
            .setClaims(claims)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
    
    public String getUsernameFromToken(String token) {
        return getClaimsFromToken(token).getSubject();
    }
    
    public Long getUserIdFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.get("userId", Long.class);
    }
    
    public Date getExpirationDateFromToken(String token) {
        return getClaimsFromToken(token).getExpiration();
    }
    
    private Claims getClaimsFromToken(String token) {
        return Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (SignatureException ex) {
            logger.error("无效的JWT签名");
        } catch (MalformedJwtException ex) {
            logger.error("无效的JWT令牌");
        } catch (ExpiredJwtException ex) {
            logger.error("JWT令牌已过期");
        } catch (UnsupportedJwtException ex) {
            logger.error("不支持的JWT令牌");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT声明字符串为空");
        }
        return false;
    }
    
    public long getRemainingValidity(String token) {
        Date expiration = getExpirationDateFromToken(token);
        Date now = new Date();
        return expiration.getTime() - now.getTime();
    }
}

// JWT认证过滤器
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    private final JwtTokenProvider tokenProvider;
    private final UserDetailsService userDetailsService;
    
    public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, 
                                  UserDetailsService userDetailsService) {
        this.tokenProvider = tokenProvider;
        this.userDetailsService = userDetailsService;
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain filterChain) throws ServletException, IOException {
        
        try {
            String jwt = getJwtFromRequest(request);
            
            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                String username = tokenProvider.getUsernameFromToken(jwt);
                
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = 
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("无法设置用户认证", ex);
        }
        
        filterChain.doFilter(request, response);
    }
    
    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

2. 多因素认证与OAuth2集成

java

// 多因素认证配置
@Configuration
public class MultiFactorAuthenticationConfig {
    
    @Bean
    public AuthenticationProvider authenticationProvider() {
        return new MultiFactorAuthenticationProvider();
    }
    
    @Bean
    public MultiFactorAuthenticationFilter multiFactorAuthenticationFilter() {
        return new MultiFactorAuthenticationFilter();
    }
}

// 多因素认证过滤器
public class MultiFactorAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain filterChain) throws ServletException, IOException {
        
        String path = request.getRequestURI();
        
        // 检查是否需要MFA验证
        if (path.startsWith("/api/mfa/verify") || 
            path.startsWith("/api/mfa/setup")) {
            filterChain.doFilter(request, response);
            return;
        }
        
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication != null && authentication.isAuthenticated() &&
            authentication.getPrincipal() instanceof CustomUserDetails) {
            
            CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
            
            // 检查是否启用了MFA
            if (userDetails.isMfaEnabled() && !userDetails.isMfaVerified()) {
                // 重定向到MFA验证页面
                response.sendRedirect("/mfa/verify");
                return;
            }
        }
        
        filterChain.doFilter(request, response);
    }
}

// OAuth2配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
        
        // 内存客户端配置示例
        clients.inMemory()
            .withClient("web-app")
            .secret(passwordEncoder.encode("web-secret"))
            .authorizedGrantTypes("password", "refresh_token")
            .scopes("read", "write")
            .accessTokenValiditySeconds(3600)
            .refreshTokenValiditySeconds(86400)
            
            .and()
            .withClient("mobile-app")
            .secret(passwordEncoder.encode("mobile-secret"))
            .authorizedGrantTypes("password", "refresh_token", "implicit")
            .scopes("read")
            .autoApprove(true)
            .accessTokenValiditySeconds(7200)
            
            .and()
            .withClient("admin-client")
            .secret(passwordEncoder.encode("admin-secret"))
            .authorizedGrantTypes("client_credentials")
            .scopes("admin")
            .authorities("ROLE_ADMIN");
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService)
            .tokenStore(tokenStore())
            .accessTokenConverter(accessTokenConverter());
    }
    
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }
    
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("my-signing-key");
        return converter;
    }
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()")
            .allowFormAuthenticationForClients();
    }
}

实战案例:金融支付系统安全设计

下面通过一个完整的金融支付系统案例,展示Spring Security在复杂安全场景中的应用。

java

// 支付系统安全配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, order = 0)
public class PaymentSecurityConfig {
    
    @Autowired
    private CustomUserDetailsService userDetailsService;
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    @Autowired
    private RateLimiterFilter rateLimiterFilter;
    
    @Autowired
    private FraudDetectionFilter fraudDetectionFilter;
    
    @Bean
    @Order(1)
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**")
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeHttpRequests(authorize -> authorize
                // API特定授权规则
                .requestMatchers("/api/v1/payments/**").authenticated()
                .requestMatchers("/api/v1/transactions/**").hasAnyRole("USER", "ADMIN")
                .requestMatchers("/api/v1/accounts/**").authenticated()
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
            )
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(rateLimiterFilter, JwtAuthenticationFilter.class)
            .addFilterBefore(fraudDetectionFilter, JwtAuthenticationFilter.class)
            .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint())
                .accessDeniedHandler(accessDeniedHandler());
        
        return http.build();
    }
    
    @Bean
    @Order(2)
    public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/web/**")
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/web/login").permitAll()
                .requestMatchers("/web/register").permitAll()
                .requestMatchers("/web/dashboard/**").authenticated()
                .requestMatchers("/web/admin/**").hasRole("ADMIN")
            )
            .formLogin(form -> form
                .loginPage("/web/login")
                .loginProcessingUrl("/web/login-process")
                .defaultSuccessUrl("/web/dashboard")
                .failureUrl("/web/login?error=true")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/web/logout")
                .logoutSuccessUrl("/web/login?logout=true")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
            )
            .rememberMe(remember -> remember
                .key("uniqueAndSecret")
                .tokenValiditySeconds(86400) // 24小时
                .rememberMeParameter("remember-me")
            )
            .sessionManagement(session -> session
                .sessionFixation().migrateSession()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .expiredUrl("/web/login?expired=true")
            );
        
        return http.build();
    }
    
    // 其他bean定义...
}

// 支付控制器 - 方法级安全控制
@RestController
@RequestMapping("/api/v1/payments")
@Validated
@Slf4j
public class PaymentController {
    
    private final PaymentService paymentService;
    private final AuditService auditService;
    
    @Autowired
    public PaymentController(PaymentService paymentService, AuditService auditService) {
        this.paymentService = paymentService;
        this.auditService = auditService;
    }
    
    @PostMapping("/process")
    @PreAuthorize("hasAuthority('PAYMENT_CREATE') and @paymentSecurity.canProcessPayment(authentication, #request)")
    @RateLimit(key = "payment_process", limit = 10, duration = 60) // 每分钟10次
    @FraudCheck
    public ResponseEntity<PaymentResponse> processPayment(
            @Valid @RequestBody PaymentRequest request,
            @AuthenticationPrincipal CustomUserDetails userDetails) {
        
        log.info("用户 {} 发起支付请求,金额: {}", 
                userDetails.getUserId(), request.getAmount());
        
        // 审计日志
        auditService.logPaymentAttempt(userDetails.getUserId(), request);
        
        PaymentResult result = paymentService.processPayment(request, userDetails.getUserId());
        
        if (result.isSuccess()) {
            return ResponseEntity.ok(PaymentResponse.success(result));
        } else {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(PaymentResponse.failed(result.getErrorMessage()));
        }
    }
    
    @GetMapping("/{paymentId}")
    @PreAuthorize("hasAuthority('PAYMENT_READ') and "
                + "@paymentSecurity.canViewPayment(authentication, #paymentId)")
    public ResponseEntity<PaymentDetailResponse> getPaymentDetail(
            @PathVariable Long paymentId,
            @AuthenticationPrincipal CustomUserDetails userDetails) {
        
        PaymentDetail detail = paymentService.getPaymentDetail(paymentId);
        
        // 检查权限(通过自定义权限评估器)
        if (!paymentSecurity.canViewPayment(userDetails, detail)) {
            throw new AccessDeniedException("无权查看此支付记录");
        }
        
        return ResponseEntity.ok(PaymentDetailResponse.from(detail));
    }
    
    @PostMapping("/{paymentId}/refund")
    @PreAuthorize("hasRole('ADMIN') or "
                + "(hasAuthority('PAYMENT_REFUND') and "
                + "@paymentSecurity.canRefundPayment(authentication, #paymentId))")
    public ResponseEntity<RefundResponse> refundPayment(
            @PathVariable Long paymentId,
            @Valid @RequestBody RefundRequest request,
            @AuthenticationPrincipal CustomUserDetails userDetails) {
        
        log.warn("用户 {} 发起退款请求,支付ID: {}", 
                userDetails.getUserId(), paymentId);
        
        // 需要管理员审核或双重授权
        if (request.getAmount().compareTo(new BigDecimal("10000")) > 0) {
            if (!userDetails.hasRole("ADMIN")) {
                throw new AccessDeniedException("大额退款需要管理员审批");
            }
        }
        
        RefundResult result = paymentService.refundPayment(paymentId, request, userDetails.getUserId());
        
        // 审计日志
        auditService.logRefund(userDetails.getUserId(), paymentId, request.getAmount());
        
        return ResponseEntity.ok(RefundResponse.from(result));
    }
    
    @GetMapping("/history")
    @PreAuthorize("hasAuthority('PAYMENT_READ')")
    public ResponseEntity<Page<PaymentHistoryResponse>> getPaymentHistory(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size,
            @AuthenticationPrincipal CustomUserDetails userDetails,
            HttpServletRequest request) {
        
        // IP白名单检查
        String clientIp = request.getRemoteAddr();
        if (!securityConfig.isIpWhitelisted(clientIp)) {
            throw new AccessDeniedException("IP地址未授权");
        }
        
        Page<PaymentHistory> history = paymentService.getUserPaymentHistory(
            userDetails.getUserId(), PageRequest.of(page, size));
        
        return ResponseEntity.ok(history.map(PaymentHistoryResponse::from));
    }
    
    @PostMapping("/batch")
    @PreAuthorize("hasAuthority('PAYMENT_BATCH')")
    @Secured("ROLE_ADMIN") // 替代方案:使用@Secured注解
    @RolesAllowed("ADMIN") // 替代方案:使用JSR-250注解
    public ResponseEntity<BatchPaymentResponse> batchProcess(
            @Valid @RequestBody List<BatchPaymentRequest> requests,
            @AuthenticationPrincipal CustomUserDetails userDetails) {
        
        // 检查批量操作限制
        if (requests.size() > 100) {
            throw new IllegalArgumentException("批量操作数量超过限制");
        }
        
        BatchPaymentResult result = paymentService.batchProcess(requests);
        
        return ResponseEntity.ok(BatchPaymentResponse.from(result));
    }
}

// 自定义权限评估器
@Component("paymentSecurity")
public class PaymentSecurityEvaluator {
    
    private final PaymentRepository paymentRepository;
    private final UserRepository userRepository;
    
    @Autowired
    public PaymentSecurityEvaluator(PaymentRepository paymentRepository,
                                  UserRepository userRepository) {
        this.paymentRepository = paymentRepository;
        this.userRepository = userRepository;
    }
    
    public boolean canProcessPayment(Authentication authentication, PaymentRequest request) {
        if (!(authentication.getPrincipal() instanceof CustomUserDetails)) {
            return false;
        }
        
        CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
        
        // 检查用户状态
        if (!userDetails.isEnabled() || userDetails.isAccountNonLocked()) {
            return false;
        }
        
        // 检查支付金额限制
        if (request.getAmount().compareTo(new BigDecimal("50000")) > 0) {
            // 大额支付需要额外验证
            return userDetails.hasPermission("PAYMENT_LARGE_AMOUNT");
        }
        
        return true;
    }
    
    public boolean canViewPayment(Authentication authentication, Long paymentId) {
        if (!(authentication.getPrincipal() instanceof CustomUserDetails)) {
            return false;
        }
        
        CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
        
        // 管理员可以查看所有支付
        if (userDetails.hasRole("ADMIN")) {
            return true;
        }
        
        // 普通用户只能查看自己的支付
        Optional<Payment> payment = paymentRepository.findById(paymentId);
        return payment.isPresent() && 
               payment.get().getUserId().equals(userDetails.getUserId());
    }
    
    public boolean canRefundPayment(Authentication authentication, Long paymentId) {
        if (!(authentication.getPrincipal() instanceof CustomUserDetails)) {
            return false;
        }
        
        CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
        
        // 查找支付记录
        Payment payment = paymentRepository.findById(paymentId)
            .orElseThrow(() -> new PaymentNotFoundException("支付记录不存在"));
        
        // 退款规则:
        // 1. 只能退款自己的支付
        // 2. 支付时间在30天内
        // 3. 支付状态为成功
        
        boolean isOwner = payment.getUserId().equals(userDetails.getUserId());
        boolean within30Days = payment.getCreatedAt()
            .isAfter(LocalDateTime.now().minusDays(30));
        boolean isSuccess = payment.getStatus() == PaymentStatus.SUCCESS;
        
        return isOwner && within30Days && isSuccess;
    }
    
    public boolean canViewPayment(CustomUserDetails userDetails, PaymentDetail detail) {
        // 管理员或支付创建者可以查看
        return userDetails.hasRole("ADMIN") || 
               detail.getUserId().equals(userDetails.getUserId());
    }
}

// 防欺诈过滤器
@Component
public class FraudDetectionFilter extends OncePerRequestFilter {
    
    private final FraudDetectionService fraudDetectionService;
    private final AuditService auditService;
    
    @Autowired
    public FraudDetectionFilter(FraudDetectionService fraudDetectionService,
                              AuditService auditService) {
        this.fraudDetectionService = fraudDetectionService;
        this.auditService = auditService;
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain filterChain) throws ServletException, IOException {
        
        String path = request.getRequestURI();
        
        // 只检查支付相关请求
        if (path.contains("/payments/") && 
            (request.getMethod().equals("POST") || request.getMethod().equals("PUT"))) {
            
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            
            if (authentication != null && authentication.isAuthenticated() &&
                authentication.getPrincipal() instanceof CustomUserDetails) {
                
                CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
                
                // 收集请求信息进行欺诈检测
                FraudDetectionContext context = FraudDetectionContext.builder()
                    .userId(userDetails.getUserId())
                    .userAgent(request.getHeader("User-Agent"))
                    .clientIp(request.getRemoteAddr())
                    .requestPath(path)
                    .requestMethod(request.getMethod())
                    .timestamp(LocalDateTime.now())
                    .build();
                
                FraudDetectionResult result = fraudDetectionService.detectFraud(context);
                
                if (result.isSuspicious()) {
                    // 记录可疑行为
                    auditService.logSuspiciousActivity(userDetails.getUserId(), 
                        context, result.getReasons());
                    
                    // 根据风险等级采取不同措施
                    if (result.getRiskLevel() == RiskLevel.HIGH) {
                        // 高风险:直接拒绝请求
                        response.setStatus(HttpStatus.FORBIDDEN.value());
                        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                        response.getWriter().write("{\"error\": \"请求被拒绝,请联系客服\"}");
                        return;
                    } else if (result.getRiskLevel() == RiskLevel.MEDIUM) {
                        // 中风险:要求额外验证
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.setHeader("X-Requires-MFA", "true");
                    }
                }
            }
        }
        
        filterChain.doFilter(request, response);
    }
}

// 审计服务
@Service
@Slf4j
public class AuditService {
    
    @Async
    public void logPaymentAttempt(Long userId, PaymentRequest request) {
        // 异步记录支付尝试
        PaymentAuditLog log = PaymentAuditLog.builder()
            .userId(userId)
            .action("PAYMENT_ATTEMPT")
            .amount(request.getAmount())
            .paymentMethod(request.getPaymentMethod())
            .recipient(request.getRecipient())
            .timestamp(LocalDateTime.now())
            .build();
        
        // 保存到数据库或发送到审计系统
        auditRepository.save(log);
    }
    
    @Async
    public void logSuspiciousActivity(Long userId, 
                                     FraudDetectionContext context,
                                     List<String> reasons) {
        SecurityAuditLog log = SecurityAuditLog.builder()
            .userId(userId)
            .action("SUSPICIOUS_ACTIVITY")
            .ipAddress(context.getClientIp())
            .userAgent(context.getUserAgent())
            .reasons(reasons)
            .riskLevel(context.getRiskLevel())
            .timestamp(LocalDateTime.now())
            .build();
        
        // 同时发送告警
        alertService.sendSecurityAlert(log);
    }
}

Spring Security的真正威力在于它为Java应用安全提供了一套完整、可扩展且深度集成的解决方案。从基础的认证授权到高级的OAuth2、JWT、多因素认证,Spring Security覆盖了现代应用安全的各个方面。其过滤器链机制和丰富的扩展点使得开发者可以根据具体业务需求定制安全策略,而无需从零开始构建安全基础设施。

然而,安全是一个持续的过程而非一次性的配置。Spring Security虽然提供了强大的工具,但正确的使用方式和持续的安全监控同样重要。在实践中,应该遵循最小权限原则、深度防御策略,并定期进行安全审计和漏洞扫描。特别是在处理敏感数据(如支付信息、个人隐私)时,更需要结合业务特点设计多层安全防护。

看完这篇文章,你是否在项目中使用过Spring Security实现复杂的安全需求?或者你在应用安全架构设计方面有什么独特的经验?欢迎在评论区分享你的Spring Security实战心得,也欢迎提出关于Java应用安全的任何技术问题,让我们一起探讨如何更好地构建安全可靠的Java应用系统!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐