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

    • 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
    • 最佳实践

方法级别安全

一句话定义:通过 Spring AOP 拦截机制,在方法调用前后执行权限校验,实现比 URL 级别更细粒度的业务逻辑层访问控制。


定义与作用

URL 级别授权只能控制"某个路径谁能访问",但无法应对同一端点内部不同业务方法的差异化权限需求。方法级别安全(Method Security)通过在 Service 层或 Controller 层的方法上添加注解,由 Spring AOP 生成的代理拦截方法调用,在调用前后执行授权决策。


核心原理

启用方法安全的开关

Spring Security 5.x 使用 @EnableGlobalMethodSecurity 注解启用方法级别安全:

@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true,   // 启用 @PreAuthorize / @PostAuthorize / @PreFilter / @PostFilter
    securedEnabled = true,    // 启用 @Secured
    jsr250Enabled = true      // 启用 @RolesAllowed
)
public class MethodSecurityConfig { }

三个开关的含义:

开关启用注解依赖特点
prePostEnabled = true@PreAuthorize, @PostAuthorize, @PreFilter, @PostFilterSpring Security 自带支持 SpEL 表达式,最灵活
securedEnabled = true@SecuredSpring Security 自带简单角色检查,无 SpEL
jsr250Enabled = true@RolesAllowed, @PermitAll, @DenyAllJSR-250 标准跨框架兼容

方法安全拦截器链


示例一:三层架构中的方法安全配置

场景说明

一个订单管理系统,Controller 层负责接收请求,Service 层处理业务逻辑。URL 级别只要求登录,具体业务操作权限在 Service 层通过注解控制。

操作前配置(仅 URL 级别,不安全)

@Configuration
@EnableWebSecurity
public class UnsafeConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .antMatchers("/api/orders/**").authenticated()  // 只要登录就能调用所有订单 API
                .anyRequest().permitAll()
            )
            .httpBasic(withDefaults());
    }
}

@Service
public class OrderService {
    // 没有任何权限控制,任何登录用户都能调用
    public void createOrder(Order order) { ... }
    public void deleteOrder(Long id) { ... }  // 危险!
    public void updateOrder(Order order) { ... }
    public List<Order> getAllOrders() { ... }  // 返回所有订单!
}

漏洞:普通登录用户可以调用 deleteOrder 删除任意订单,可以调用 getAllOrders 查看所有用户的订单。

操作后配置(URL + 方法级别联合防护)

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 开启方法安全
public class SafeConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .antMatchers("/api/orders/**").authenticated()
                .anyRequest().permitAll()
            )
            .httpBasic(withDefaults());
    }
}

@Service
public class OrderService {

    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    public void createOrder(Order order) { ... }

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(Long id) { ... }

    @PreAuthorize("hasRole('ADMIN') or #order.owner == authentication.name")
    public void updateOrder(Order order) { ... }

    @PreAuthorize("hasRole('ADMIN')")
    @PostFilter("filterObject.owner == authentication.name")
    public List<Order> getAllOrders() { ... }
}

结果分析

方法普通用户调用Admin 调用安全效果
createOrder✅ 通过✅ 通过所有已登录用户可创建
deleteOrder❌ 403✅ 通过仅 Admin 可删除
updateOrder✅ 自己的订单✅ 任何订单普通用户只能改自己的
getAllOrders❌ 403✅ 自己的订单Admin 也只能看到自己的(@PostFilter)

示例二:@EnableGlobalMethodSecurity 开关对比

场景说明

对比三种开关配置方式,理解不同注解的适用场景。

操作前配置(仅启用 prePostEnabled)

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class PrePostConfig { }

@Service
public class DocumentService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteDocument(Long id) { ... }

    @Secured("ROLE_ADMIN")  // ❌ 不生效!securedEnabled 未开启
    public void archiveDocument(Long id) { ... }
}

操作后配置(同时启用三种)

@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true,
    securedEnabled = true,
    jsr250Enabled = true
)
public class FullMethodSecurityConfig { }

@Service
public class DocumentService {

    // 方式一:SpEL 表达式(最灵活)
    @PreAuthorize("hasRole('ADMIN') or #id == 0")
    public void deleteDocument(Long id) { ... }

    // 方式二:简单角色(无 SpEL)
    @Secured("ROLE_ADMIN")
    public void archiveDocument(Long id) { ... }

    // 方式三:JSR-250 标准(跨框架兼容)
    @RolesAllowed("ADMIN")
    public void publishDocument(Long id) { ... }

    @PermitAll
    public Document getDocument(Long id) { ... }
}

结果分析

注解来源支持 SpEL支持参数/返回值适用场景
@PreAuthorizeSpring Security✅✅复杂权限表达式(推荐)
@SecuredSpring Security❌❌简单角色检查,代码简洁
@RolesAllowedJSR-250❌❌需要跨框架/标准兼容
@PermitAllJSR-250——显式允许所有人
@DenyAllJSR-250——显式拒绝所有人

易错场景:方法安全注解不生效的五大原因

场景:在方法上添加了 @PreAuthorize("hasRole('ADMIN')"),但测试时发现任何用户都能调用方法,注解似乎被忽略了。

常见原因排查清单:

1. 未开启 @EnableGlobalMethodSecurity

// 错误:只有 @EnableWebSecurity,没有方法安全开关
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { }

// 正确:必须显式开启
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter { }

2. 方法不是 Spring 管理的 Bean

// 错误:普通 POJO,不在 Spring 容器中
public class OrderService {  // 缺少 @Service
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(Long id) { ... }
}

// 正确:必须是 Spring Bean
@Service
public class OrderService { ... }

3. 同类内部调用绕过代理

@Service
public class OrderService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(Long id) { ... }

    public void batchDelete(List<Long> ids) {
        for (Long id : ids) {
            this.deleteOrder(id);  // ❌ 内部调用不走 AOP 代理!
        }
    }
}

正确做法:

@Service
public class OrderService {

    @Autowired
    private ApplicationContext context;

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(Long id) { ... }

    public void batchDelete(List<Long> ids) {
        OrderService proxy = context.getBean(OrderService.class);
        for (Long id : ids) {
            proxy.deleteOrder(id);  // ✅ 通过代理调用,触发 AOP 拦截
        }
    }
}

4. 方法非 public

@Service
public class OrderService {
    @PreAuthorize("hasRole('ADMIN')")
    private void deleteOrder(Long id) { ... }  // ❌ 非 public 方法无法被 Spring AOP 代理拦截
}

5. 使用了 JDK 动态代理但没有接口

// 类没有实现接口,Spring 使用 CGLIB 代理
@Service
public class OrderService { ... }  // ✅ CGLIB 代理可以拦截类方法

// 如果有接口,Spring 默认使用 JDK 动态代理(基于接口)
@Service
public class OrderServiceImpl implements OrderService { ... }
// 此时注入时应使用接口类型:
@Autowired
private OrderService orderService;  // ✅ 接口类型
@Autowired
private OrderServiceImpl orderService;  // ❌ 可能注入原始对象而非代理

面试考点:

问:@PreAuthorize 注解不生效,可能有哪些原因? 答:① 未开启 @EnableGlobalMethodSecurity(prePostEnabled = true);② 目标类不是 Spring Bean(缺少 @Service 等);③ 同类内部调用 this.method() 绕过 AOP 代理;④ 方法不是 public;⑤ 注入了实现类而非接口(JDK 代理场景)。最隐蔽的是第 3 点——内部调用不经过代理,导致权限注解被完全跳过,这是生产环境常见的安全漏洞。


方法级别安全架构速查

上一页
URL 级别授权
下一页
@PreAuthorize