0Pricing
Spring Boot 4 Complete Guide · 课时

使用 SpEL 与自定义投票器实现方法安全

使用 @PreAuthorize、SpEL 表达式和自定义授权逻辑实施细粒度访问控制。

使用 SpEL 与自定义投票器实现方法安全 是 CoddyKit 上的免费 Spring Boot 4 Complete Guide 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Spring Boot 4 Complete Guide 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Spring Boot 4 Complete Guide 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Why Method Security?

URL-based security (HttpSecurity matchers) guards entry points, but it cannot see the arguments a method receives or the object it returns. Method security closes that gap by enforcing rules right at the service layer.

  • Defense in depth — protection survives even if a controller forgets a check.
  • Fine-grained — decide based on parameters, return values, and the authenticated principal.
  • Reusable — the same secured service can be called from REST, GraphQL, or a message listener and stays protected.

In this lesson we enforce access with @PreAuthorize, SpEL expressions, and a custom authorization manager.

Enabling Method Security

In Spring Boot 4 / Spring Security 6, method security is opt-in. Add @EnableMethodSecurity to a configuration class. It activates the annotations through an AOP proxy.

  • prePostEnabled defaults to true — @PreAuthorize and @PostAuthorize work out of the box.
  • Set securedEnabled = true for the legacy @Secured, or jsr250Enabled = true for @RolesAllowed.

Note: the old @EnableGlobalMethodSecurity is removed — always use @EnableMethodSecurity.

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
    // prePostEnabled = true by default
    // @PreAuthorize / @PostAuthorize now active
}

@PreAuthorize with Roles and Authorities

@PreAuthorize evaluates a SpEL expression before the method runs. If it returns false, Spring throws AccessDeniedException and the body never executes.

  • hasRole('ADMIN') — checks the ROLE_ADMIN authority (the prefix is added for you).
  • hasAuthority('SCOPE_orders:write') — exact authority match, no prefix added.
  • hasAnyRole('ADMIN','MANAGER') and the boolean operators and / or / !.
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class AccountService {

    @PreAuthorize("hasRole('ADMIN')")
    public void closeAccount(Long accountId) {
        // only ROLE_ADMIN reaches here
    }

    @PreAuthorize("hasAnyRole('ADMIN','SUPPORT') or hasAuthority('SCOPE_accounts:write')")
    public void freezeAccount(Long accountId) {
        // ...
    }
}

Referencing Method Arguments with #

The real power of SpEL is reading method arguments. Prefix a parameter name with # to use it inside the expression. This lets you compare the principal's identity to the data being acted on.

  • authentication — the current Authentication object.
  • principal — the principal (often a UserDetails or JWT).
  • #username, #order.ownerId — method arguments and their properties.

Argument names require parameters compiled with -parameters (Spring Boot enables this by default).

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class ProfileService {

    // A user may edit only their own profile, unless they are an admin
    @PreAuthorize("#username == authentication.name or hasRole('ADMIN')")
    public void updateProfile(String username, ProfileDto dto) {
        // ...
    }
}

@PostAuthorize and returnObject

@PostAuthorize runs after the method returns and can inspect the result via returnObject. Use it when you must load the entity first to know who owns it.

  • Good for “you can read this record only if it belongs to you.”
  • The method body does execute, so avoid it for operations with side effects you must prevent.
  • On denial, the return value is discarded and AccessDeniedException is thrown.
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.stereotype.Service;

@Service
public class DocumentService {

    @PostAuthorize("returnObject.ownerUsername == authentication.name or hasRole('ADMIN')")
    public Document findById(Long id) {
        return repository.findById(id).orElseThrow();
    }
}

@PreFilter and @PostFilter on Collections

Filtering annotations prune collections element by element instead of throwing. They use a special variable filterObject bound to each element.

  • @PreFilter — strips disallowed elements from a collection argument before the method runs.
  • @PostFilter — strips disallowed elements from the returned collection.
  • Use filterTarget when a method has more than one collection parameter.

Caution: post-filtering large result sets in memory can be costly — prefer filtering in the query when possible.

import org.springframework.security.access.prepost.PostFilter;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class OrderService {

    // Caller sees only the orders they own (admins see all)
    @PostFilter("filterObject.ownerUsername == authentication.name or hasRole('ADMIN')")
    public List<Order> findRecentOrders() {
        return repository.findRecent();
    }
}

Calling a Bean from SpEL with @

When logic gets complex, push it into a Spring bean and call it from the expression using @beanName.method(...). This keeps annotations readable and the rule unit-testable.

  • The @ resolves a bean from the application context.
  • Pass authentication and method arguments straight into the bean method.
  • The method must return a boolean.
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

@Component("projectAccess")
public class ProjectAccessEvaluator {

    public boolean canEdit(Authentication auth, Long projectId) {
        String user = auth.getName();
        return membershipRepository.isEditor(user, projectId);
    }
}

// Usage on a service method:
// @PreAuthorize("@projectAccess.canEdit(authentication, #projectId)")
// public void rename(Long projectId, String name) { ... }

PermissionEvaluator and hasPermission

SpEL exposes hasPermission(target, permission), which delegates to a PermissionEvaluator bean. It is the canonical hook for domain-object (ACL-style) authorization without inlining logic in every annotation.

  • hasPermission(#doc, 'WRITE') — passes the object and a permission key.
  • hasPermission(#id, 'com.app.Document', 'READ') — passes an id plus the type.
  • You register exactly one PermissionEvaluator via a MethodSecurityExpressionHandler.
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import java.io.Serializable;

public class DocumentPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication auth, Object target, Object permission) {
        if (target instanceof Document doc) {
            return "WRITE".equals(permission)
                ? doc.getOwnerUsername().equals(auth.getName())
                : true; // READ allowed for all in this example
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication auth, Serializable id,
                                 String type, Object permission) {
        return false; // resolve by id+type if needed
    }
}

Registering a Custom Expression Handler

To wire your PermissionEvaluator into SpEL, expose a DefaultMethodSecurityExpressionHandler bean and set the evaluator on it. Spring Security picks it up for all method annotations.

  • The bean name is not important; the type is.
  • You can also attach a custom RoleHierarchy here so hasRole respects inheritance.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity
public class ExpressionHandlerConfig {

    @Bean
    static DefaultMethodSecurityExpressionHandler expressionHandler() {
        var handler = new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(new DocumentPermissionEvaluator());
        return handler;
    }
}

Custom AuthorizationManager (the New Voter)

Spring Security 6 replaced the legacy AccessDecisionVoter with the simpler AuthorizationManager<T>. For method security the type parameter is MethodInvocation. Implement check to return an AuthorizationDecision.

  • Return new AuthorizationDecision(true|false) — or null to abstain and let other managers decide.
  • Register it with @EnableMethodSecurity(prePostEnabled = false) plus an advisor, or combine managers with AuthorizationManagers.allOf(...).
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import java.util.function.Supplier;

public class BusinessHoursAuthorizationManager
        implements AuthorizationManager<MethodInvocation> {

    @Override
    public AuthorizationDecision check(Supplier<Authentication> auth,
                                       MethodInvocation invocation) {
        int hour = java.time.LocalTime.now().getHour();
        boolean withinHours = hour >= 9 && hour < 18;
        return new AuthorizationDecision(withinHours);
    }
}

Pre vs Post: Choosing Correctly

Picking the wrong annotation either leaks data or blocks valid calls. A quick decision guide:

  • Rule depends only on arguments + principal → @PreAuthorize (fast, no side effects).
  • Rule depends on the loaded entity's ownership → @PostAuthorize.
  • Trimming a collection per-element → @PostFilter (or filter in the query).
  • Reusable domain-object rule → hasPermission + PermissionEvaluator.

Remember: @PostAuthorize and @PostFilter run the method body, so never rely on them to stop a mutating operation.

Quick Check

You have a method Document findById(Long id) that loads a document, and access should be granted only if the returned document's ownerUsername equals the caller, or the caller is an admin. Which annotation expresses this correctly?

Recap

You now enforce fine-grained access at the method layer:

  • @EnableMethodSecurity turns on annotation-driven checks (no more @EnableGlobalMethodSecurity).
  • @PreAuthorize guards before execution using SpEL: hasRole, hasAuthority, #args, and authentication.
  • @PostAuthorize inspects returnObject; @PreFilter/@PostFilter prune collections via filterObject.
  • Push complex rules into a bean (@beanName.method(...)) or a PermissionEvaluator behind hasPermission.
  • For cross-cutting policy, implement AuthorizationManager<MethodInvocation> — the modern replacement for voters.

Rule of thumb: prefer pre-checks for speed and safety; reach for post-checks only when the decision needs the loaded data.

常见问题解答

「使用 SpEL 与自定义投票器实现方法安全」课时是免费的吗?

是的 — 「使用 SpEL 与自定义投票器实现方法安全」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Spring Boot 4 Complete Guide 课程的其余内容,请升级到 CoddyKit PRO。 Spring Boot 4 Complete Guide 课程共包含 4 节课。

「使用 SpEL 与自定义投票器实现方法安全」这节课中我会学到什么?

使用 @PreAuthorize、SpEL 表达式和自定义授权逻辑实施细粒度访问控制。 你通过在浏览器中直接运行的动手代码来练习 Spring Boot 4 Complete Guide,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Spring Boot 4 Complete Guide 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Spring Boot 4 Complete Guide 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「使用 SpEL 与自定义投票器实现方法安全」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Spring Boot 4 Complete Guide 课中编写并运行代码吗?

能。每节 Spring Boot 4 Complete Guide 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 资源服务器 JWT 验证与声明
  2. OAuth2 客户端与授权码流程
  3. 使用 SpEL 与自定义投票器实现方法安全
  4. 不透明令牌自省与令牌交换
← 返回 Spring Boot 4 Complete Guide