乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

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_ADMINROLE_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匿名✅ 200permitAll()
POST /api/articleswriter✅ 200hasRole("WRITER") 匹配
POST /api/articlesreader❌ 403无 WRITER 角色
DELETE /api/articles/1writer❌ 403无 ADMIN 角色
DELETE /api/articles/1admin✅ 200ADMIN 包含 WRITER(若配层级)
GET /api/secretadmin❌ 403anyRequest().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 的匹配过程:

  1. 检查 /** → 匹配 /admin/dashboard → permitAll() → 放行
  2. 规则匹配到第一个符合条件的就停止,后续 /admin/** 的规则被跳过
  3. 结果:匿名用户可以直接访问 /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:

  1. 检查 /admin/** → 匹配 → hasRole("ADMIN") → 检查角色
  2. 如果是 ADMIN → 放行;否则 → 403
  3. /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 和尾部 / 的匹配差异,避免安全问题。

上一页
AccessDecisionVoter
下一页
方法级别安全