Bezpieczeństwo metod z SpEL i niestandardowymi voterami
Wymuszanie szczegółowej kontroli dostępu za pomocą @PreAuthorize, wyrażeń SpEL i niestandardowej logiki autoryzacji
Bezpieczeństwo metod z SpEL i niestandardowymi voterami to bezpłatna lekcja Spring Boot 4 Complete Guide na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Spring Boot 4 Complete Guide, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Spring Boot 4 Complete Guide zawiera 4 lekcji w sumie.
Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.
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.
prePostEnableddefaults to true —@PreAuthorizeand@PostAuthorizework out of the box.- Set
securedEnabled = truefor the legacy@Secured, orjsr250Enabled = truefor@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 theROLE_ADMINauthority (the prefix is added for you).hasAuthority('SCOPE_orders:write')— exact authority match, no prefix added.hasAnyRole('ADMIN','MANAGER')and the boolean operatorsand/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 currentAuthenticationobject.principal— the principal (often aUserDetailsor 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
AccessDeniedExceptionis 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
filterTargetwhen 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
authenticationand 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
PermissionEvaluatorvia aMethodSecurityExpressionHandler.
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
RoleHierarchyhere sohasRolerespects 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)— ornullto abstain and let other managers decide. - Register it with
@EnableMethodSecurity(prePostEnabled = false)plus an advisor, or combine managers withAuthorizationManagers.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:
@EnableMethodSecurityturns on annotation-driven checks (no more@EnableGlobalMethodSecurity).@PreAuthorizeguards before execution using SpEL:hasRole,hasAuthority,#args, andauthentication.@PostAuthorizeinspectsreturnObject;@PreFilter/@PostFilterprune collections viafilterObject.- Push complex rules into a bean (
@beanName.method(...)) or aPermissionEvaluatorbehindhasPermission. - 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.
Często zadawane pytania
Czy lekcja „Bezpieczeństwo metod z SpEL i niestandardowymi voterami” jest bezpłatna?
Tak — pełny tekst „Bezpieczeństwo metod z SpEL i niestandardowymi voterami” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Spring Boot 4 Complete Guide, przejdź na CoddyKit PRO. Kurs Spring Boot 4 Complete Guide zawiera 4 lekcji w sumie.
Co nauczysz się w „Bezpieczeństwo metod z SpEL i niestandardowymi voterami”?
Wymuszanie szczegółowej kontroli dostępu za pomocą @PreAuthorize, wyrażeń SpEL i niestandardowej logiki autoryzacji Ćwiczysz Spring Boot 4 Complete Guide z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Spring Boot 4 Complete Guide?
Nie wymagamy żadnego doświadczenia. Spring Boot 4 Complete Guide w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Bezpieczeństwo metod z SpEL i niestandardowymi voterami”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Spring Boot 4 Complete Guide?
Tak. Każda lekcja Spring Boot 4 Complete Guide zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Weryfikacja JWT serwera zasobów i roszczeń
- Klient OAuth2 i przepływ kodu autoryzacji
- Bezpieczeństwo metod z SpEL i niestandardowymi voterami
- Introspekcja nieprzezroczystych tokenów i wymiana tokenów