在2026年的互联网环境下,应用安全已成为系统设计的重中之重。Spring Security作为Java生态中最成熟的安全框架,为现代应用提供了全方位的安全保障。本文将通过实战案例,带你从零搭建一个完整的安全防护体系。
1. Spring Security核心架构与认证流程 1.1 核心架构概览 Spring Security采用了模块化的设计,主要包括以下几个核心组件:
1 2 3 4 5 6 7 8 SecurityContextHolder → SecurityContext → Authentication ↓ FilterChain ↓ SecurityFilterChain ↓ Filter SecurityInterceptor
SecurityContextHolder :安全上下文持有者,存储当前认证信息
SecurityContext :安全上下文,包含Authentication对象
Authentication :认证信息主体,包含用户权限和详细信息
FilterChain :过滤器链,处理HTTP请求的安全拦截
SecurityFilterChain :安全过滤器链,包含多个安全过滤器
1.2 认证流程详解 Spring Security的认证流程可以分为以下几个步骤:
用户提交认证信息 :通过UsernamePasswordAuthenticationFilter接收用户名和密码
创建AuthenticationToken :将用户提交的信息封装为UsernamePasswordAuthenticationToken
调用AuthenticationManager :通过认证管理器进行认证
ProviderManager认证 :委托给具体的AuthenticationProvider
UserDetailsService查询用户 :从数据库中加载用户信息
权限信息封装 :将用户信息和权限封装为完整的Authentication对象
存储到SecurityContext :将认证后的Authentication对象存储到安全上下文中
1.3 配置示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**" ).permitAll() .requestMatchers("/api/public/**" ).permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public AuthenticationProvider authenticationProvider () { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider (); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } }
2. JWT vs OAuth2.0选择策略 2.1 JWT认证方案 JWT(JSON Web Token)是一种无状态的认证方案,适合现代微服务架构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Configuration public class JwtConfig { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; @Bean public JwtTokenProvider jwtTokenProvider () { return new JwtTokenProvider (secret, expiration); } } public class JwtTokenProvider { private final String secret; private final Long expiration; public String generateToken (String username, List<String> roles) { Map<String, Object> claims = new HashMap <>(); claims.put("roles" , roles); return Jwts.builder() .setClaims(claims) .setSubject(username) .setIssuedAt(new Date ()) .setExpiration(new Date (System.currentTimeMillis() + expiration)) .signWith(getSignInKey(), SignatureAlgorithm.HS512) .compact(); } public boolean validateToken (String token) { try { Jwts.parserBuilder().setSigningKey(getSignInKey()).build().parseClaimsJws(token); return true ; } catch (JwtException | IllegalArgumentException e) { return false ; } } private Key getSignInKey () { byte [] keyBytes = Decoders.BASE64.decode(secret); return Keys.hmacShaKeyFor(keyBytes); } }
JWT适用场景:
单体应用或简单微服务架构
移动端API认证
需要跨域认证的场景
对性能要求较高的系统
2.2 OAuth2.0认证方案 OAuth2.0是一种开放标准的授权框架,适合复杂的授权场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Override public void configure (ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client_id" ) .secret("{noop}client_secret" ) .authorizedGrantTypes("password" , "refresh_token" ) .scopes("read" , "write" ) .accessTokenValiditySeconds(3600 ) .refreshTokenValiditySeconds(86400 ); } @Override public void configure (AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .tokenStore(tokenStore()); } @Bean public TokenStore tokenStore () { return new JwtTokenStore (jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter () { JwtAccessTokenConverter converter = new JwtAccessTokenConverter (); converter.setSigningKey("secret" ); return converter; } }
OAuth2.0适用场景:
复杂的多租户系统
第三方应用授权
需要细粒度权限控制的系统
企业级应用集成
2.3 选择策略对比
特性
JWT
OAuth2.0
复杂度
简单,适合快速开发
复杂,需要更多配置
扩展性
有限,Token大小固定
高,支持多种授权方式
安全性
相对简单,容易管理
完善,支持多种安全机制
性能
高,无状态验证
稍低,需要Token存储
适用场景
单体应用、简单微服务
复杂微服务、企业应用
3. RBAC权限模型实现 3.1 数据库设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR (50 ) UNIQUE NOT NULL , password VARCHAR (100 ) NOT NULL , email VARCHAR (100 ), enabled BOOLEAN DEFAULT TRUE , created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE roles ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR (50 ) UNIQUE NOT NULL , description VARCHAR (200 ) ); CREATE TABLE permissions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR (50 ) UNIQUE NOT NULL , description VARCHAR (200 ), resource VARCHAR (50 ) NOT NULL , action VARCHAR (20 ) NOT NULL ); CREATE TABLE user_roles ( user_id BIGINT , role_id BIGINT , PRIMARY KEY (user_id, role_id), FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (role_id) REFERENCES roles(id) ); CREATE TABLE role_permissions ( role_id BIGINT , permission_id BIGINT , PRIMARY KEY (role_id, permission_id), FOREIGN KEY (role_id) REFERENCES roles(id), FOREIGN KEY (permission_id) REFERENCES permissions(id) );
3.2 实体类设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String username; @Column(nullable = false) private String password; private String email; private Boolean enabled = true ; @CreationTimestamp private LocalDateTime createdAt; @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private Set<Role> roles = new HashSet <>(); } @Entity @Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String name; private String description; @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "role_permissions", joinColumns = @JoinColumn(name = "role_id"), inverseJoinColumns = @JoinColumn(name = "permission_id") ) private Set<Permission> permissions = new HashSet <>(); } @Entity @Table(name = "permissions") public class Permission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String name; private String description; @Column(nullable = false) private String resource; @Column(nullable = false) private String action; }
3.3 自定义UserDetailsService 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException ("User not found: " + username)); return new org .springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), user.isEnabled(), true , true , true , getAuthorities(user.getRoles()) ); } private Collection<? extends GrantedAuthority > getAuthorities(Set<Role> roles) { Set<GrantedAuthority> authorities = new HashSet <>(); roles.forEach(role -> { authorities.add(new SimpleGrantedAuthority ("ROLE_" + role.getName())); role.getPermissions().forEach(permission -> { authorities.add(new SimpleGrantedAuthority (permission.getAuthority())); }); }); return authorities; } }
3.4 权限检查注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasAuthority('PERMISSION_NAME')") public @interface RequirePermission { String value () ; } @RestController @RequestMapping("/api/admin") @PreAuthorize("hasRole('ADMIN')") public class AdminController { @GetMapping("/users") @RequirePermission(value = "USER:READ") public ResponseEntity<List<User>> getAllUsers () { return ResponseEntity.ok(userService.getAllUsers()); } @PostMapping("/users") @RequirePermission(value = "USER:CREATE") public ResponseEntity<User> createUser (@RequestBody User user) { return ResponseEntity.ok(userService.createUser(user)); } }
4. 安全防护:CSRF、XSS、SQL注入防护 4.1 CSRF防护 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .sessionAuthenticationStrategy(new CsrfTokenRequestAttributeHandler ()) ) .authorizeHttpRequests(auth -> auth ); return http.build(); } }
4.2 XSS防护 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @Configuration public class XSSProtectionConfig { @Bean public FilterRegistrationBean<XssFilter> xssFilter () { FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean <>(); registrationBean.setFilter(new XssFilter ()); registrationBean.addUrlPatterns("/*" ); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); return registrationBean; } } public class XssFilter implements Filter { @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper wrappedRequest = new XssHttpServletRequestWrapper ( (HttpServletRequest) request); chain.doFilter(wrappedRequest, response); } } public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper (HttpServletRequest request) { super (request); } @Override public String getParameter (String name) { String value = super .getParameter(name); return value != null ? cleanXSS(value) : null ; } @Override public String[] getParameterValues(String name) { String[] values = super .getParameterValues(name); if (values == null ) { return null ; } String[] cleanValues = new String [values.length]; for (int i = 0 ; i < values.length; i++) { cleanValues[i] = cleanXSS(values[i]); } return cleanValues; } private String cleanXSS (String value) { if (value == null ) return null ; value = value.replaceAll("<" , "<" ) .replaceAll(">" , ">" ) .replaceAll("\\(" , "(" ) .replaceAll("\\)" , ")" ) .replaceAll("'" , "'" ) .replaceAll("eval\\((.*)\\)" , "" ) .replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']" , "\"\"" ); return value; } }
4.3 SQL注入防护 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Service public class UserService { @Autowired private JdbcTemplate jdbcTemplate; public User findById (Long id) { String sql = "SELECT * FROM users WHERE id = ?" ; return jdbcTemplate.queryForObject(sql, new Object []{id}, (rs, rowNum) -> { User user = new User (); user.setId(rs.getLong("id" )); user.setUsername(rs.getString("username" )); user.setEmail(rs.getString("email" )); user.setEnabled(rs.getBoolean("enabled" )); return user; }); } public User findByUsername (String username) { String sql = "SELECT * FROM users WHERE username = :username" ; Map<String, Object> params = new HashMap <>(); params.put("username" , username); return jdbcTemplate.queryForObject(sql, params, (rs, rowNum) -> { User user = new User (); user.setId(rs.getLong("id" )); user.setUsername(rs.getString("username" )); user.setEmail(rs.getString("email" )); user.setEnabled(rs.getBoolean("enabled" )); return user; }); } }
4.4 输入验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class UserValidator { public static void validateUser (User user) { if (user == null ) { throw new IllegalArgumentException ("User cannot be null" ); } if (user.getUsername() == null || user.getUsername().trim().isEmpty()) { throw new IllegalArgumentException ("Username cannot be empty" ); } if (user.getUsername().length() < 3 || user.getUsername().length() > 50 ) { throw new IllegalArgumentException ("Username must be between 3 and 50 characters" ); } if (user.getEmail() != null && !isValidEmail(user.getEmail())) { throw new IllegalArgumentException ("Invalid email format" ); } } private static boolean isValidEmail (String email) { String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$" ; return email.matches(emailRegex); } }
5. 多租户安全隔离 5.1 多租户数据隔离策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Configuration public class MultiTenantConfig { @Bean public FilterRegistrationBean<TenantFilter> tenantFilter () { FilterRegistrationBean<TenantFilter> registrationBean = new FilterRegistrationBean <>(); registrationBean.setFilter(new TenantFilter ()); registrationBean.addUrlPatterns("/*" ); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE + 1 ); return registrationBean; } } public class TenantFilter implements Filter { @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String tenantId = httpRequest.getHeader("X-Tenant-ID" ); if (tenantId == null || tenantId.trim().isEmpty()) { throw new TenantNotFoundException ("Tenant ID is required" ); } TenantContext.setCurrentTenant(tenantId); try { chain.doFilter(request, response); } finally { TenantContext.clear(); } } } public class TenantContext { private static final ThreadLocal<String> currentTenant = new ThreadLocal <>(); public static void setCurrentTenant (String tenantId) { currentTenant.set(tenantId); } public static String getCurrentTenant () { return currentTenant.get(); } public static void clear () { currentTenant.remove(); } }
5.2 多租户Repository实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Repository public class UserRepository { @PersistenceContext private EntityManager entityManager; public User findByUsernameAndTenant (String username, String tenantId) { String jpql = "SELECT u FROM User u WHERE u.username = :username AND u.tenantId = :tenantId" ; return entityManager.createQuery(jpql, User.class) .setParameter("username" , username) .setParameter("tenantId" , tenantId) .getSingleResult(); } public List<User> findByTenant (String tenantId) { String jpql = "SELECT u FROM User u WHERE u.tenantId = :tenantId" ; return entityManager.createQuery(jpql, User.class) .setParameter("tenantId" , tenantId) .getResultList(); } }
5.3 多租户安全注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasRole('TENANT_ADMIN') or @tenantService.isSameTenant(authentication.name, #tenantId)") public @interface RequireTenantAccess { String value () ; } @Service public class TenantService { public boolean isSameTenant (String currentUser, String tenantId) { User currentUser = userRepository.findByUsername(currentUser); return currentUser.getTenantId().equals(tenantId); } }
6. 完整安全体系搭建实战 6.1 完整配置示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Autowired private CustomUserDetailsService userDetailsService; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired private JwtTokenProvider jwtTokenProvider; @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .ignoringRequestMatchers("/api/**" ) ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .maximumSessions(10 ) .maxSessionsPreventsLogin(true ) ) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**" , "/api/public/**" ).permitAll() .requestMatchers("/api/admin/**" ).hasRole("ADMIN" ) .requestMatchers("/api/user/**" ).hasRole("USER" ) .requestMatchers("/h2-console/**" ).permitAll() .anyRequest().authenticated() ) .httpBasic(withDefaults()) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling(ex -> ex .authenticationEntryPoint(new HttpStatusEntryPoint (HttpStatus.UNAUTHORIZED)) .accessDeniedHandler((request, response, accessDeniedException) -> { response.sendError(HttpStatus.FORBIDDEN.value(), "Access denied" ); }) ) .headers(headers -> headers .frameOptions(frameOptions -> frameOptions.sameOrigin()) ); return http.build(); } @Bean public AuthenticationProvider authenticationProvider () { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider (); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean public WebSecurityCustomizer webSecurityCustomizer () { return (web) -> web.ignoring() .requestMatchers("/images/**" , "/css/**" , "/js/**" ); } }
6.2 登录控制器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<AuthResponse> login (@Valid @RequestBody LoginRequest loginRequest) { try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken ( loginRequest.getUsername(), loginRequest.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); User user = (User) authentication.getPrincipal(); String token = jwtTokenProvider.generateToken(user.getUsername()); return ResponseEntity.ok(new AuthResponse (token, "Login successful" )); } catch (AuthenticationException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(new AuthResponse (null , "Invalid username or password" )); } } @PostMapping("/refresh") public ResponseEntity<AuthResponse> refresh (@RequestBody RefreshTokenRequest refreshTokenRequest) { try { if (jwtTokenProvider.validateToken(refreshTokenRequest.getRefreshToken())) { String username = jwtTokenProvider.getUsernameFromToken(refreshTokenRequest.getRefreshToken()); String newToken = jwtTokenProvider.generateToken(username); return ResponseEntity.ok(new AuthResponse (newToken, "Token refreshed" )); } return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(new AuthResponse (null , "Invalid refresh token" )); } catch (JwtException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(new AuthResponse (null , "Invalid refresh token" )); } } @PostMapping("/logout") public ResponseEntity<AuthResponse> logout () { SecurityContextHolder.clearContext(); return ResponseEntity.ok(new AuthResponse (null , "Logout successful" )); } } @Data @AllArgsConstructor @NoArgsConstructor class AuthResponse { private String token; private String message; } @Data class LoginRequest { @NotBlank(message = "Username is required") private String username; @NotBlank(message = "Password is required") private String password; } @Data class RefreshTokenRequest { @NotBlank(message = "Refresh token is required") private String refreshToken; }
6.3 安全监控与日志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 @Configuration @EnableWebSecurity public class SecurityAuditConfig { @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .addFilterBefore(new SecurityAuditFilter (), UsernamePasswordAuthenticationFilter.class) ; return http.build(); } } public class SecurityAuditFilter extends OncePerRequestFilter { @Autowired private AuditLogRepository auditLogRepository; @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { long startTime = System.currentTimeMillis(); try { filterChain.doFilter(request, response); } finally { long duration = System.currentTimeMillis() - startTime; AuditLog auditLog = createAuditLog(request, response, duration); auditLogRepository.save(auditLog); } } private AuditLog createAuditLog (HttpServletRequest request, HttpServletResponse response, long duration) { AuditLog auditLog = new AuditLog (); auditLog.setIpAddress(request.getRemoteAddr()); auditLog.setMethod(request.getMethod()); auditLog.setUri(request.getRequestURI()); auditLog.setStatusCode(response.getStatus()); auditLog.setDuration(duration); auditLog.setUserAgent(request.getHeader("User-Agent" )); auditLog.setTimestamp(new Date ()); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { auditLog.setUsername(authentication.getName()); } return auditLog; } } @Entity @Table(name = "audit_logs") public class AuditLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String ipAddress; private String method; private String uri; private Integer statusCode; private Long duration; private String userAgent; private String username; private Date timestamp; }
6.4 安全监控仪表板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @RestController @RequestMapping("/api/security") @PreAuthorize("hasRole('ADMIN')") public class SecurityMonitorController { @Autowired private AuditLogRepository auditLogRepository; @GetMapping("/dashboard") public ResponseEntity<SecurityDashboard> getSecurityDashboard () { SecurityDashboard dashboard = new SecurityDashboard (); long todayLogins = auditLogRepository.countByTodayLogin(); dashboard.setTodayLogins(todayLogins); long todayFailedLogins = auditLogRepository.countByFailedLoginsToday(); dashboard.setTodayFailedLogins(todayFailedLogins); long activeUsers = auditLogRepository.countDistinctUsersToday(); dashboard.setActiveUsers(activeUsers); List<RiskAlert> riskAlerts = detectPotentialRisks(); dashboard.setRiskAlerts(riskAlerts); return ResponseEntity.ok(dashboard); } private List<RiskAlert> detectPotentialRisks () { List<RiskAlert> alerts = new ArrayList <>(); List<String> suspiciousIPs = auditLogRepository.findSuspiciousIPs(); if (!suspiciousIPs.isEmpty()) { alerts.add(new RiskAlert ("SUSPICIOUS_LOGIN" , "检测到来自异常IP的登录行为" )); } List<String> bruteForceIPs = auditLogRepository.findBruteForceIPs(); if (!bruteForceIPs.isEmpty()) { alerts.add(new RiskAlert ("BRUTE_FORCE" , "检测到可能的暴力破解攻击" )); } return alerts; } } @Data class SecurityDashboard { private long todayLogins; private long todayFailedLogins; private long activeUsers; private List<RiskAlert> riskAlerts; } @Data class RiskAlert { private String type; private String message; }
7. 总结 通过本文的实战指南,我们构建了一个完整的Spring Security安全防护体系:
核心架构 :深入理解Spring Security的认证流程和核心组件
认证策略 :对比JWT和OAuth2.0的适用场景,选择合适的认证方案
权限管理 :实现RBAC权限模型,支持细粒度的权限控制
安全防护 :实现CSRF、XSS、SQL注入等多种安全防护措施
多租户隔离 :构建多租户安全隔离机制
监控审计 :实现安全监控和审计日志,及时发现潜在风险
这个安全体系不仅能够保护应用免受常见的安全威胁,还具有良好的扩展性和可维护性。在实际应用中,还需要根据具体的安全需求和业务场景进行相应的调整和优化。
记住,安全是一个持续的过程,需要不断地监控、评估和改进。建议定期进行安全审计和渗透测试,及时发现和修复潜在的安全漏洞。