1. 从零开始:认识你的安全管家WebSecurityConfigurerAdapter
如果你刚开始用SpringBoot做Web开发,大概率会遇到一个头疼的问题:怎么管好我网站的安全?比如,用户登录怎么弄?哪些页面谁都能看,哪些页面必须登录?后台管理页面是不是只有管理员能进?这些问题,说白了就是身份认证(你是谁)和授权(你能干什么)。Spring Security这个框架就是专门来解决这些问题的,但它本身配置起来有点复杂,就像给你一盒乐高零件,你得自己琢磨怎么拼。
这时候,WebSecurityConfigurerAdapter 这个类就登场了。你可以把它想象成Spring Security官方给你准备好的一个“安全配置样板间”。这个样板间里,基础的墙、门窗(也就是默认的安全规则)都已经给你搭好了。比如,它会默认要求所有请求都需要认证,给你一个简单的登录页面。但你的房子(也就是你的应用)肯定不能跟样板间一模一样,你想把门换个位置,或者给某个房间加把特殊的锁,这就需要“装修”了。
WebSecurityConfigurerAdapter 就是让你来“装修”这个安全样板间的。它本身是一个抽象类,你不能直接new它,而是需要你创建一个新的类去继承它。继承之后,你就拥有了重写(Override)它里面几个关键方法的能力,通过重写这些方法,你就能告诉Spring Security:“嘿,别全按你默认的来,这块儿我得按我的规矩办。”
我刚开始用的时候,觉得这名字又长又拗口,后来拆开看就明白了。WebSecurity是“网页安全”,Configurer是“配置器”,Adapter是“适配器”。合起来就是一个“用于配置网页安全的适配器”。它的核心使命,就是让你能以一种相对统一和方便的方式,去自定义整个应用的安全策略。你不用去直接操作那些底层复杂的安全过滤器链,而是通过重写几个清晰的方法来达到目的,这对我们开发者来说,友好太多了。
那么,具体我们能“装修”哪些部分呢?主要就是通过重写三个名字都叫 configure 的方法,但它们接受的参数不同,管的事儿也完全不一样。接下来,我们就一个个房间进去看看,到底怎么装修。
2. 核心装修指南:三大configure方法详解
当你创建了一个继承自 WebSecurityConfigurerAdapter 的配置类(通常我们会用 @Configuration 和 @EnableWebSecurity 注解来标记它),你的主要工作就是和这三个 configure 方法打交道。它们就像是控制安全系统的三个不同面板,每个面板负责一个独立的区域。
2.1 人员登记处:configure(AuthenticationManagerBuilder auth)
这个方法管的是 “身份认证”,也就是解决“你是谁”的问题。AuthenticationManagerBuilder 是一个工具类,专门用来构建认证管理器。你可以在这里告诉Spring Security:当用户输入用户名和密码时,去哪里查这个用户的信息(比如数据库),以及用什么规则去校验密码。
最常用、最推荐的方式是结合我们自己的用户服务(UserDetailsService)和密码编码器(PasswordEncoder)来配置。我举个例子,假设我们用户信息都存在数据库里:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService; // 这是你自己实现的从数据库加载用户的类
@Bean
public PasswordEncoder passwordEncoder() {
// 返回一个密码编码器,这里用的是BCrypt强哈希加密
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}
这段代码干了啥呢?它建立了一套认证流程:
- 用户登录时,Spring Security会调用我们提供的
userDetailsService去数据库根据用户名查找用户。 - 找到用户后,会用我们提供的
passwordEncoder(这里是BCryptPasswordEncoder)对用户输入的密码进行编码,然后和数据库里存储的已编码的密码进行比对。 - 如果匹配成功,就认为认证通过。
这里有个我踩过的坑:密码编码器必须配置,而且要和存储密码时用的编码器一致。如果你数据库里存的密码是明文,这里却配了BCryptPasswordEncoder,那永远也登录不成功。现在基本没人存明文密码了,所以一开始就统一用BCryptPasswordEncoder,它在注册用户时对密码加密,在登录时用它来校验,非常安全方便。
2.2 公共区域管理:configure(WebSecurity web)
这个方法管的是 “全局安全过滤”,它影响的是Spring Security安全过滤器链之前的层面。你可以把它理解为对静态资源或者一些完全不需要安全控制的请求路径开“绿色通道”。
WebSecurity 对象允许你配置一些“忽略”规则。被忽略的资源将完全绕过Spring Security的所有过滤器,包括认证、授权等,就像它们不存在于这个安全体系内一样。这能显著提升性能,因为静态文件(如图片、CSS、JS)就不需要经过复杂的安全检查了。
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/css/**", "/js/**", "/images/**", "/webjars/**", "/favicon.ico");
}
上面这段配置,就告诉Spring Security:所有以 /css/, /js/, /images/ 开头的请求,以及 /webjars/** 和 /favicon.ico 这个图标文件,统统放行,别管它们。这样,用户访问你的网站首页时,加载样式表和脚本的速度会快很多。
注意:这里用的是
antMatchers,它支持通配符。和下一个方法里的antMatchers作用类似,但语境不同。在这里配置的路径,完全不会经过安全过滤器。
2.3 精细权限控制室:configure(HttpSecurity http)
这是最核心、最常用的方法,负责配置基于请求的、精细化的安全策略。它定义了不同URL的访问权限、登录注销行为、防止跨站请求伪造(CSRF)等一大堆东西。HttpSecurity 提供了流式API(就是那种点.连接的方式),配置起来像搭积木。
我们来看一个比较完整的例子,它模拟了一个常见后台管理系统的安全需求:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 1. 授权配置:定义哪些路径需要什么权限
.authorizeRequests()
.antMatchers("/", "/home", "/api/public/**").permitAll() // 首页、公开API允许所有人访问
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 用户功能需要USER或ADMIN角色
.antMatchers("/admin/**").hasRole("ADMIN") // 后台管理仅限ADMIN角色
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // 更复杂的组合条件
.anyRequest().authenticated() // 其他所有请求都需要登录(不限角色)
.and()
// 2. 表单登录配置
.formLogin()
.loginPage("/login") // 自定义登录页面路径
.loginProcessingUrl("/doLogin") // 表单提交的路径
.defaultSuccessUrl("/dashboard") // 登录成功后默认跳转的路径
.permitAll() // 登录页面和登录处理URL允许所有人访问
.and()
// 3. 退出登录配置
.logout()
.logoutUrl("/logout") // 退出登录的URL
.logoutSuccessUrl("/login?logout") // 退出成功后跳转的页面
.permitAll()
.and()
// 4. 记住我功能
.rememberMe()
.tokenValiditySeconds(86400) // 记住我有效期为1天
.and()
// 5. 异常处理
.exceptionHandling()
.accessDeniedPage("/access-denied") // 权限不足时跳转的页面
.and()
// 6. 禁用CSRF(根据情况选择,API项目常禁用)
.csrf().disable();
}
我们来拆解一下这个配置:
authorizeRequests()开始了授权规则的配置。后面的antMatchers用来匹配URL模式,紧跟着的方法如permitAll(),hasRole()等则指定了访问该模式所需的权限。规则的顺序很重要,Spring Security会按你写的顺序从上到下匹配,一旦匹配上就不再继续往下看了。所以通常会把最具体的规则放前面,最通用的(如anyRequest())放最后。formLogin()配置了基于表单的登录。如果你不指定loginPage,Spring Security会提供一个默认的丑丑的登录页。指定后,当用户访问受保护的资源时,会被重定向到你自定义的/login页面。logout()配置退出。rememberMe()开启了“记住我”功能,用户关闭浏览器再打开,在一定时间内无需重新登录。exceptionHandling()用来处理认证或授权过程中出现的异常,比如AccessDeniedException(访问被拒绝)。csrf().disable()禁用了CSRF防护。对于前后端分离、使用JWT等无状态认证的API项目,通常需要禁用,否则POST等请求会失败。但对于传统的服务端渲染应用,建议保持开启以增加安全性。
这个方法的灵活性极高,几乎你能想到的所有HTTP请求层面的安全控制,都能在这里完成。
3. 实战进阶:打造一个JWT保护的后端API
现在很多项目都是前后端分离的,用Session-Cookie那套机制会有点麻烦,尤其是涉及到跨域的时候。这时候,JWT(JSON Web Token) 就成了一个非常流行的选择。结合 WebSecurityConfigurerAdapter,我们可以轻松实现一个基于JWT的无状态API安全方案。
这个方案的核心思路是:用户用用户名密码登录成功后,服务器生成一个JWT令牌返回给前端。前端后续的每次请求,都在HTTP头里带上这个令牌(通常是 Authorization: Bearer <token>)。服务器端则用一个自定义的过滤器来校验这个令牌,如果校验通过,就认为用户已认证。
3.1 第一步:创建JWT工具类
首先,我们需要一个生成和解析JWT的工具。这里我用 jjwt 库来举例。
@Component
public class JwtTokenUtil {
private final String secret = "your-secret-key"; // 密钥,要足够复杂且保密
private final long expiration = 86400000L; // 令牌有效期,这里设24小时
// 生成Token
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(username) // 主题,通常放用户名
.setIssuedAt(now) // 签发时间
.setExpiration(expiryDate) // 过期时间
.signWith(SignatureAlgorithm.HS512, secret) // 签名算法和密钥
.compact();
}
// 从Token中获取用户名
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
// 验证Token是否有效
public boolean validateToken(String token, String username) {
final String tokenUsername = getUsernameFromToken(token);
return (tokenUsername.equals(username) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
Date expiration = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getExpiration();
return expiration.before(new Date());
}
}
3.2 第二步:创建自定义的JWT认证过滤器
这个过滤器要放在Spring Security的过滤器链中,在用户认证之前执行。它负责从请求头中提取JWT,验证其有效性,如果有效,就创建一个 Authentication 对象并设置到安全上下文中。
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// 1. 从请求头获取Token
String authHeader = request.getHeader("Authorization");
String token = null;
String username = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7); // 去掉"Bearer "前缀
try {
username = jwtTokenUtil.getUsernameFromToken(token);
} catch (Exception e) {
logger.error("无法从JWT令牌中解析用户名", e);
}
}
// 2. 如果Token有效,且当前安全上下文没有认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 验证Token是否对该用户有效
if (jwtTokenUtil.validateToken(token, userDetails.getUsername())) {
// 3. 创建UsernamePasswordAuthenticationToken,这是Spring Security认识的认证对象
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 4. 将认证信息设置到安全上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// 5. 继续执行过滤器链
chain.doFilter(request, response);
}
}
3.3 第三步:配置SecurityConfig,集成JWT过滤器
最后,在我们的 SecurityConfig 类中,需要做几件事:
- 禁用Session(因为是无状态的)。
- 禁用CSRF(API项目通常不需要)。
- 将我们自定义的
JwtAuthenticationFilter添加到过滤器链中合适的位置(通常是在处理用户名密码登录的过滤器之前)。 - 配置哪些路径是公开的(如登录接口),哪些是受保护的。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 禁用CSRF和Session
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 授权规则
.authorizeRequests()
.antMatchers("/api/auth/login", "/api/auth/register").permitAll() // 登录注册公开
.antMatchers("/api/public/**").permitAll() // 公开API
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated() // 其他所有API都需要认证
.and()
// 添加JWT过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
// 可选:如果你需要直接使用AuthenticationManager(比如在登录Controller里),可以把它暴露为Bean
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
这样,一个基于JWT的API安全骨架就搭建好了。前端在登录接口获取到Token后,后续请求只需在Header中携带,后端就能自动识别用户身份和权限。这种方式清晰、无状态,非常适合现代分布式应用。
4. 避坑指南与最佳实践
用了这么多年 WebSecurityConfigurerAdapter,我也踩过不少坑,总结了一些经验,希望能帮你少走弯路。
配置顺序的坑:前面提过,HttpSecurity 中 authorizeRequests() 的规则是顺序敏感的。我曾经犯过一个错误,把 anyRequest().authenticated() 这行放在了最前面,结果导致后面定义的 permitAll() 规则全部失效,所有请求都要求登录。切记,具体的规则放前面,通用的规则放最后。
密码编码器的坑:这是新手最高频的问题。如果你在 configure(AuthenticationManagerBuilder auth) 方法里配置了 passwordEncoder,那么你在用户注册、修改密码时,存入数据库的密码也必须用同一个编码器进行加密。我建议在Service层里注入这个 PasswordEncoder Bean来用,保证全局一致。Spring Security 5之后,不配置编码器会报错,强制你考虑密码安全。
静态资源被拦截的坑:明明在 configure(WebSecurity web) 里忽略了 /static/**,但访问CSS文件还是跳转到登录页?检查一下你的路径。ignoring() 里的路径是相对于应用上下文的。更稳妥的做法是,如果你用了Spring Boot的默认静态资源位置(classpath:/static/, classpath:/public/等),也可以在 HttpSecurity 的 authorizeRequests() 里用 .antMatchers(HttpMethod.GET, "/**/*.css", "/**/*.js").permitAll() 来放行,但前者性能更好。
多个安全配置类的坑:一个项目里只能有一个继承 WebSecurityConfigurerAdapter 的配置类生效吗?不是的。你可以有多个,但你需要用 @Order 注解来指定它们的优先级(Order值越小,优先级越高)。高优先级的配置会先被应用,但要注意规则可能会被覆盖。对于大多数应用,一个配置类就足够了。如果你需要为不同的URL模式(比如 /api/** 和 /admin/**)配置完全不同的安全规则,才考虑拆分。
关于方法过时的说明:在Spring Security 5.7及以后版本,WebSecurityConfigurerAdapter 已经被标记为过时(Deprecated)。官方推荐使用基于组件的安全配置。但这并不意味着你现有的代码立刻不能用了,它仍然完全有效。新的方式更像是一种“函数式”的配置风格,用 SecurityFilterChain Bean来替代。如果你是新项目,可以尝试学习新的方式;如果是老项目维护,沿用 WebSecurityConfigurerAdapter 也完全没有问题,社区有大量的资料和案例。理解了这个适配器的核心思想——即如何配置认证、全局忽略和请求授权,再过渡到新方式会非常容易。
最后,安全无小事。在自定义安全策略时,一定要清楚每一条规则的目的。对于生产环境,除了基础的认证授权,还要考虑HTTPS、安全的HTTP头(如CSP, HSTS)、定期更新依赖以修复安全漏洞等更广泛的安全实践。WebSecurityConfigurerAdapter 是你构建应用安全防线的强大工具,用好它,你的SpringBoot应用就拥有了坚实的第一道门。
1万+



被折叠的 条评论
为什么被折叠?



