URL 级别授权
一句话定义:在 Spring Security 过滤器链中,通过
HttpSecurity配置 URL 路径与所需权限的映射规则,实现 Web 请求级别的粗粒度访问控制。
定义与作用
URL 级别授权是 Spring Security 最常用、最外层的授权防线。请求进入 Servlet 容器后,在 FilterSecurityInterceptor(Spring Security 5.x)或 AuthorizationFilter(6.x)中,根据请求的 URL 路径和 HTTP 方法匹配配置规则,决定放行或拒绝。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor {
@Override
public void invoke(FilterInvocation fi) throws IOException, ServletException {
// 1. 获取当前请求所需的权限配置
Collection<ConfigAttribute> attributes =
this.obtainSecurityMetadataSource().getAttributes(fi);
// 2. 调用 AccessDecisionManager 决策
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
accessDecisionManager.decide(authentication, fi, attributes);
// 3. 决策通过,继续过滤器链
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
}
核心原理
URL 匹配与授权规则链
配置规则匹配顺序:规则按声明顺序从上到下匹配,第一个匹配的规则生效,后续规则被忽略。
http.authorizeRequests(auth -> auth
.antMatchers("/admin/**").hasRole("ADMIN") // ① 先匹配
.antMatchers("/user/**").hasRole("USER") // ② 再匹配
.antMatchers("/public/**").permitAll() // ③ 最后匹配
.anyRequest().authenticated() // ④ 兜底规则
);
授权表达式速查
| 表达式 | 说明 | 内部 ConfigAttribute |
|---|---|---|
permitAll() | 允许所有人(含匿名) | permitAll |
denyAll() | 拒绝所有人 | denyAll |
authenticated() | 需要已认证 | authenticated |
fullyAuthenticated() | 需要完整登录(非 RememberMe) | fullyAuthenticated |
anonymous() | 只允许匿名 | anonymous |
rememberMe() | 允许 RememberMe 或完整登录 | rememberMe |
hasRole("ADMIN") | 拥有 ROLE_ADMIN | ROLE_ADMIN |
hasAnyRole("ADMIN", "USER") | 拥有任一角色 | ROLE_ADMIN 或 ROLE_USER |
hasAuthority("SCOPE_read") | 拥有指定权限 | SCOPE_read |
hasAnyAuthority("A", "B") | 拥有任一权限 | A 或 B |
access("hasRole('ADMIN') and hasIpAddress('127.0.0.1')") | 自定义 SpEL | 表达式字符串 |
示例一:REST API 的 URL 授权配置
场景说明
构建一个博客系统的 REST API,不同端点需要不同的权限:
GET /api/articles:公开读取POST /api/articles:需要WRITER角色DELETE /api/articles/{id}:需要ADMIN角色GET /api/users/profile:已登录即可/api/admin/**:仅ADMIN可访问
操作前配置
@Configuration
@EnableWebSecurity
public class BlogApiSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("{noop}admin")
.roles("ADMIN", "WRITER")
.and()
.withUser("writer")
.password("{noop}writer")
.roles("WRITER")
.and()
.withUser("reader")
.password("{noop}reader")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 纯 API,关闭 CSRF
.authorizeRequests(auth -> auth
// ① 管理后台最严格
.antMatchers("/api/admin/**").hasRole("ADMIN")
// ② 写文章需要 WRITER
.antMatchers(HttpMethod.POST, "/api/articles").hasRole("WRITER")
// ③ 删文章需要 ADMIN
.antMatchers(HttpMethod.DELETE, "/api/articles/**").hasRole("ADMIN")
// ④ 公开读取
.antMatchers(HttpMethod.GET, "/api/articles/**").permitAll()
// ⑤ 用户资料需登录
.antMatchers("/api/users/profile").authenticated()
// ⑥ 兜底
.anyRequest().authenticated()
)
.httpBasic(withDefaults()); // API 使用 Basic 认证
}
}
操作后配置(使用更安全的请求匹配器)
@Configuration
@EnableWebSecurity
public class BlogApiSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests(auth -> auth
// 使用 mvcMatchers 更精确匹配 Spring MVC 路径
.mvcMatchers("/api/admin/**").hasRole("ADMIN")
.mvcMatchers(HttpMethod.POST, "/api/articles").hasRole("WRITER")
.mvcMatchers(HttpMethod.DELETE, "/api/articles/{id}")
.hasRole("ADMIN")
.mvcMatchers(HttpMethod.GET, "/api/articles", "/api/articles/{id}")
.permitAll()
.mvcMatchers("/api/users/profile").authenticated()
.anyRequest().denyAll() // 未明确配置的路径全部拒绝
)
.httpBasic(withDefaults());
}
}
结果分析
| 请求 | 用户 | 结果 | 原因 |
|---|---|---|---|
GET /api/articles | 匿名 | ✅ 200 | permitAll() |
POST /api/articles | writer | ✅ 200 | hasRole("WRITER") 匹配 |
POST /api/articles | reader | ❌ 403 | 无 WRITER 角色 |
DELETE /api/articles/1 | writer | ❌ 403 | 无 ADMIN 角色 |
DELETE /api/articles/1 | admin | ✅ 200 | ADMIN 包含 WRITER(若配层级) |
GET /api/secret | admin | ❌ 403 | anyRequest().denyAll() 兜底拒绝 |
示例二:URL 规则顺序错误导致的权限绕过
场景说明
开发者配置 URL 授权规则时,将宽泛规则放在前面,精确规则放在后面,导致精确规则永远不生效。
操作前配置(错误顺序)
@Configuration
@EnableWebSecurity
public class WrongOrderConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
// ❌ 错误:宽泛规则放在前面
.antMatchers("/**").permitAll() // ① 匹配所有路径
.antMatchers("/admin/**").hasRole("ADMIN") // ② 永远不会执行!
.antMatchers("/api/**").authenticated() // ③ 永远不会执行!
)
.formLogin(withDefaults());
}
}
结果分析(错误)
请求 GET /admin/dashboard 的匹配过程:
- 检查
/**→ 匹配/admin/dashboard→permitAll()→ 放行 - 规则匹配到第一个符合条件的就停止,后续
/admin/**的规则被跳过 - 结果:匿名用户可以直接访问
/admin/dashboard,严重安全漏洞!
操作后配置(修正顺序)
@Configuration
@EnableWebSecurity
public class CorrectOrderConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
// ✅ 正确:精确规则在前,宽泛规则在后
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").authenticated()
.antMatchers("/public/**").permitAll()
.antMatchers("/login", "/register").permitAll()
// 兜底:最宽泛的放在最后
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
}
修正后的结果分析
请求 GET /admin/dashboard:
- 检查
/admin/**→ 匹配 →hasRole("ADMIN")→ 检查角色 - 如果是 ADMIN → 放行;否则 → 403
/admin/dashboard永远不会被后面的permitAll()匹配到
易错场景:authenticated() 与 hasRole() 混用导致越权
场景:开发者认为只要配置了 authenticated() 就是"安全的",在管理员接口上只要求登录。
// 危险配置
.antMatchers("/admin/**").authenticated() // 只要求登录,不要求角色!
问题:任何已登录用户(包括普通用户)都能访问 /admin/**。authenticated() 只验证 SecurityContext 中存在非匿名 Authentication,不检查任何权限。
正确做法:
// 安全配置:明确指定角色
.antMatchers("/admin/**").hasRole("ADMIN")
// 如果需要多角色任选
.antMatchers("/admin/**").hasAnyRole("ADMIN", "SUPER_ADMIN")
// 如果需要角色 + 权限组合
.antMatchers("/admin/finance/**")
.access("hasRole('ADMIN') and hasAuthority('PERM_FINANCE')")
面试考点:
问:
permitAll()、anonymous()、authenticated()有什么区别?为什么/admin/**不能配authenticated()? 答:permitAll()允许任何人包括匿名用户;anonymous()只允许未登录的匿名用户;authenticated()只允许已登录用户(非匿名),但不检查角色。三者都不能替代hasRole()。在/admin/**这种敏感路径上,必须使用hasRole()或hasAnyRole()限制具体角色,否则任何注册用户的登录都能访问管理员功能,造成水平越权。
URL 匹配方式对比
| 匹配器 | 示例 | 特点 | 推荐场景 |
|---|---|---|---|
antMatchers() | antMatchers("/api/**") | Ant 风格路径匹配 | 简单静态路径 |
mvcMatchers() | mvcMatchers("/api/users/{id}") | 感知 Spring MVC 路径变量 | Spring MVC 应用(推荐) |
regexMatchers() | regexMatchers("/api/v\\d+/.*") | 正则表达式匹配 | 复杂版本号路径 |
requestMatchers() | requestMatchers(new AntPathRequestMatcher("/api/**")) | 显式 RequestMatcher | 需要自定义匹配逻辑 |
Spring Security 5.x 推荐:使用
antMatchers或mvcMatchers。mvcMatchers能正确处理 Spring MVC 的@PathVariable和尾部/的匹配差异,避免安全问题。