在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的认证流程可以分为以下几个步骤:

  1. 用户提交认证信息:通过UsernamePasswordAuthenticationFilter接收用户名和密码
  2. 创建AuthenticationToken:将用户提交的信息封装为UsernamePasswordAuthenticationToken
  3. 调用AuthenticationManager:通过认证管理器进行认证
  4. ProviderManager认证:委托给具体的AuthenticationProvider
  5. UserDetailsService查询用户:从数据库中加载用户信息
  6. 权限信息封装:将用户信息和权限封装为完整的Authentication对象
  7. 存储到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() {
// 只有ADMIN角色且有USER:READ权限的用户才能访问
return ResponseEntity.ok(userService.getAllUsers());
}

@PostMapping("/users")
@RequirePermission(value = "USER:CREATE")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 只有ADMIN角色且有USER:CREATE权限的用户才能访问
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;

// 移除潜在的XSS攻击字符
value = value.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("\\(", "&#40;")
.replaceAll("\\)", "&#41;")
.replaceAll("'", "&#39;")
.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;

// 使用预编译语句防止SQL注入
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;
});
}

// 使用命名参数防止SQL注入
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 -> 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()
)

// HTTP Basic配置
.httpBasic(withDefaults())

// JWT配置
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

// 异常处理
.exceptionHandling(ex -> ex
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.sendError(HttpStatus.FORBIDDEN.value(), "Access denied");
})
)

// H2控制台配置
.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;

// getters and setters
}

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<>();

// 检测异常登录IP
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安全防护体系:

  1. 核心架构:深入理解Spring Security的认证流程和核心组件
  2. 认证策略:对比JWT和OAuth2.0的适用场景,选择合适的认证方案
  3. 权限管理:实现RBAC权限模型,支持细粒度的权限控制
  4. 安全防护:实现CSRF、XSS、SQL注入等多种安全防护措施
  5. 多租户隔离:构建多租户安全隔离机制
  6. 监控审计:实现安全监控和审计日志,及时发现潜在风险

这个安全体系不仅能够保护应用免受常见的安全威胁,还具有良好的扩展性和可维护性。在实际应用中,还需要根据具体的安全需求和业务场景进行相应的调整和优化。

记住,安全是一个持续的过程,需要不断地监控、评估和改进。建议定期进行安全审计和渗透测试,及时发现和修复潜在的安全漏洞。