Spring Security 安全架构与最佳实践教程
第一章:Security Filter Chain 架构
1.1 过滤器链执行顺序
请求 → ChannelProcessingFilter (HTTPS)
→ SecurityContextPersistenceFilter (加载 SecurityContext)
→ ConcurrentSessionFilter (会话并发)
→ UsernamePasswordAuthenticationFilter (表单登录)
→ BasicAuthenticationFilter (HTTP Basic)
→ CsrfFilter (CSRF 防护)
→ ExceptionTranslationFilter (异常转换)
→ FilterSecurityInterceptor (授权决策)
→ 业务 Controller
1.2 自定义过滤器插入位置
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(myFilter, CsrfFilter.class);
http.addFilterAt(myFilter, UsernamePasswordAuthenticationFilter.class);
第二章:认证(Authentication)
2.1 认证流程
UsernamePasswordAuthenticationToken (未认证)
→ AuthenticationManager
→ AuthenticationProvider (多个)
→ UserDetailsService.loadUserByUsername()
→ PasswordEncoder.matches()
→ 返回已认证的 Authentication
→ SecurityContextHolder 保存
2.2 自定义 UserDetailsService
@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("用户不存在: " + username));
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().toArray(new String[0]))
.accountLocked(!user.isActive())
.build();
}
}
2.3 多种认证方式组合
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth -> oauth
.loginPage("/login")
.defaultSuccessUrl("/dashboard"))
.formLogin(form -> form
.loginPage("/login"))
.httpBasic(Customizer.withDefaults());
return http.build();
}
第三章:授权(Authorization)
3.1 基于角色的访问控制
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers(HttpMethod.POST, "/api/products").hasRole("EDITOR")
);
@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() { }
@PreAuthorize("hasRole('USER') and #userId == authentication.name")
public void updateOwnProfile(Long userId) { }
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) { }
3.2 自定义权限评估器
@Component("authz")
public class CustomAuthorization {
public boolean isOwner(Authentication auth, Long resourceId) {
return true;
}
}
@PreAuthorize("@authz.isOwner(authentication, #id)")
public void updateResource(Long id) { }
第四章:常见安全漏洞与防护
4.1 CSRF
http.csrf(AbstractHttpConfigurer::disable);
4.2 CORS 配置
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://frontend.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
4.3 密码安全
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
第五章:OAuth2 与 JWT 最佳实践
5.1 Token 有效期策略
Access Token:15-30 分钟(短期)
Refresh Token:7-30 天(长期,存服务端)
5.2 Token 刷新机制
@PostMapping("/api/auth/refresh")
public Map<String, String> refresh(@RequestBody RefreshRequest req) {
String newToken = jwtUtils.generateToken(username, role);
return Map.of("accessToken", newToken);
}
5.3 黑名单机制
redisTemplate.opsForValue()
.set("blacklist:" + token, "1", expiration, TimeUnit.MILLISECONDS);
if (redisTemplate.hasKey("blacklist:" + token)) {
throw new AccessDeniedException("Token 已失效");
}
思考题
- SecurityContextHolder 中的 SecurityContext 在线程间如何传递?
- 如何设计一个支持多租户的权限系统?
- JWT vs Session 认证各自的优缺点和适用场景?
- 如何防止暴力破解和撞库攻击?