Spring Security 实战
核心架构
过滤器链
Request
│
├─ SecurityContextPersistenceFilter ← 从 Session 恢复 SecurityContext
├─ LogoutFilter ← 登出处理
├─ UsernamePasswordAuthenticationFilter ← 表单登录认证
├─ DefaultLoginPageGeneratingFilter ← 生成默认登录页
├─ BasicAuthenticationFilter ← HTTP Basic 认证
├─ RequestCacheAwareFilter ← 请求缓存(登录后跳回原页面)
├─ SecurityContextHolderAwareFilter ← 注入 SecurityContext
├─ AnonymousAuthenticationFilter ← 匿名用户填充
├─ SessionManagementFilter ← Session 管理
├─ ExceptionTranslationFilter ← 异常转 HTTP 状态码
└─ FilterSecurityInterceptor ← 权限判决
│
└─ AccessDecisionManager
└─ Vote → 允许/拒绝三大核心组件
| 组件 | 职责 |
|---|---|
| SecurityContextHolder | 存储当前认证用户信息(ThreadLocal) |
| AuthenticationManager | 认证入口,委托给 Provider |
| AccessDecisionManager | 授权判决,基于投票器 |
配置方式
Java Config(推荐)
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").authenticated()
.anyRequest().authenticated()
)
.formLogin(login -> login
.loginPage("/login")
.defaultSuccessUrl("/home")
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
)
.csrf(csrf -> csrf
.csrfTokenRepository(
CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}认证方式
1. 表单登录(默认)
yaml
# 自动配置用户(开发环境)
spring.security.user.name=admin
spring.security.user.password=admin123
spring.security.user.roles=ADMIN2. JDBC / JPA 认证
java
@Bean
public UserDetailsService userDetailsService(UserRepository repo) {
return username -> repo.findByUsername(username)
.map(user -> User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().toArray(String[]::new))
.build()
)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
}3. JWT 认证
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && validateToken(token)) {
Authentication auth = new UsernamePasswordAuthenticationToken(
getUsername(token), null, getAuthorities(token));
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}授权模型
注解式授权
java
@RestController
@RequestMapping("/api")
public class UserController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public List<User> listUsers() { ... }
@PreAuthorize("#userId == authentication.principal.id")
@GetMapping("/user/{userId}")
public User getUser(@PathVariable Long userId) { ... }
@PostAuthorize("returnObject.owner == authentication.principal.username")
@GetMapping("/doc/{id}")
public Document getDoc(@PathVariable Long id) { ... }
}方法安全配置
java
@Configuration
@EnableMethodSecurity // Spring Security 6+(替代 @EnableGlobalMethodSecurity)
public class MethodSecurityConfig {
// 默认启用 @PreAuthorize / @PostAuthorize / @Secured
}常见场景
| 场景 | 配置要点 |
|---|---|
| REST API 无状态 | SessionCreationPolicy.STATELESS + JWT |
| 前后端分离 | 禁用 CSRF + 返回 401 JSON |
| 多租户 | 请求头提取租户 ID,动态切换数据源 |
| OAuth2 客户端 | oauth2Login() + OAuth2AuthorizedClientManager |
| CORS | cors().configurationSource() |