Spring Security实战案例精解:从基础认证到OAuth2.0架构的完整落地
目录导读
- 案例背景与需求分析 - 为什么传统Filter无法满足现代安全需求
- 基础环境搭建 - Spring Boot + Spring Security 5.x 快速起步
- 核心案例一:基于内存用户的表单登录 - 手写配置类与密码加密
- 核心案例二:JWT无状态认证 - 前后端分离下的Token签发与验证
- 核心案例三:OAuth2.0社交登录 - 对接Github/微信的授权码模式
- 常见问题问答(FAQ) - 针对实战中的高频坑点解析
- 性能与安全优化建议 - 从CORS到CSRF的细节把控
案例背景与需求分析
现代Web应用面临的安全挑战早已超越简单的“登录+拦截”,一个典型的Spring Security案例需要解决三个核心痛点:认证(Authentication)、授权(Authorization) 和防护(Protection),传统Servlet Filter虽然能做拦截,但无法提供方法级安全、OAuth2集成、以及Session固定攻击防护等高级能力。

Spring Security的核心价值在于其过滤器链(Filter Chain) 设计——它通过一组有序的过滤器(如UsernamePasswordAuthenticationFilter、ExceptionTranslationFilter)解耦了认证流程,本案例采用Spring Boot 2.7 + Spring Security 5.8组合,因为该版本在稳定性与函数式API之间取得了最佳平衡。
基础环境搭建
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
启动后,你会得到一个默认用户user及随机密码(打印在控制台),但实际案例中,我们需自定义配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.formLogin().permitAll()
.and().logout().permitAll();
return http.build();
}
}
核心案例一:基于内存用户的表单登录
业务场景:小型内部管理系统,用户量少,采用配置文件硬编码。
@Bean
public UserDetailsService users() {
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("secret123"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
关键点:密码绝不能明文存储。BCryptPasswordEncoder每次加密结果不同(加盐),但matches()方法仍可校验。UserDetailsService负责从存储介质获取用户,但在内存版中需手动构造。
核心案例二:JWT无状态认证
业务场景:前后端分离,后端API无状态,需要支持跨域调用。
实现步骤:
- 自定义
JwtAuthenticationFilter,继承OncePerRequestFilter - 在
doFilterInternal中解析请求头Authorization: Bearer <token> - 使用
SecretKey签名验证token有效性 - 将封装的
UsernamePasswordAuthenticationToken设置到SecurityContextHolder
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
try {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
// 从数据库加载用户权限(此处简化为从claims取)
var auth = new UsernamePasswordAuthenticationToken(
username, null, List.of(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (JwtException e) {
// token无效,不设置认证对象
}
}
chain.doFilter(req, res);
}
}
然后在SecurityConfig中把该过滤器替换默认的表单登录过滤器:
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
核心案例三:OAuth2.0社交登录
业务场景:允许用户通过Github账号登录,免去注册流程。
spring:
security:
oauth2:
client:
registration:
github:
client-id: your-client-id
client-secret: your-client-secret
scope: read:user
配置中无需写代码,只需定义授权端点,登录后,默认的OAuth2UserService会将用户信息映射为OAuth2User,若需持久化用户,需实现OAuth2UserService重写loadUser方法。
注意:Github的client-id和secret需在Github开发者设置中申请,回调地址默认为/login/oauth2/code/github,此路径必须与Github后台配置一致。
常见问题问答(FAQ)
Q1:Spring Security 6相比5在配置上有何重大变化?
A:6.0将WebSecurityConfigurerAdapter废弃,强制使用组件式注册(即SecurityFilterChain Bean)。antMatchers替换为requestMatchers,and()链式调用改为lambda风格,若从5升级,需移除@EnableGlobalMethodSecurity旧写法,改用@EnableMethodSecurity。
Q2:如何解决AJAX请求返回302而不是401?
A:在SecurityConfig中配置http.exceptionHandling().authenticationEntryPoint((req, res, e) -> { res.sendError(HttpServletResponse.SC_UNAUTHORIZED); }),因为默认行为是重定向到登录页,对API不友好。
Q3:Session与JWT如何选择? A:原生Web应用建议使用Session(易于管理注销),前后端分离且需跨域共享认证时使用JWT,但JWT存在注销困难问题,需引入黑名单机制(如Redis存储失效token)。
Q4:CSRF防护为何在无状态API中被禁用?
A:CSRF攻击依赖浏览器自动携带Cookie,JWT通常存于LocalStorage,手动加入请求头,故不受其威胁,若使用Session,必须保留csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())。
性能与安全优化建议
- 使用
@PreAuthorize进行方法级控制:在Controller方法上加@PreAuthorize("hasRole('ADMIN')"),比在URL配置更精细。 - 密码策略:强制使用
Argon2PasswordEncoder(目前最安全的BCrypt增强版),并设置最小长度12位。 - CORS配置:当使用JWT且允许其他域名访问时,必须显式配置:
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
- Header安全:默认已包含
X-Frame-Options: DENY等,但建议额外配置Content-Security-Policy防止XSS。 - 监控:集成
spring-boot-starter-actuator,暴露/actuator/heapdump和/actuator/threaddump时需用@PreAuthorize限定IP。
以上案例覆盖了从传统表单到现代无状态认证的完整演进路径,实际项目中,通常组合使用:用户登录用OAuth2,内部调用用JWT,敏感操作再叠加动态验证码,Spring Security的过滤器链就像乐高积木,理解每个过滤器的定位,即可灵活拼装出符合业务的安全架构。