SpEL과 사용자 지정 투표기를 활용한 메서드 보안
@PreAuthorize, SpEL 표현식 및 사용자 지정 권한 부여 로직으로 세밀한 접근 제어를 적용합니다.
SpEL과 사용자 지정 투표기를 활용한 메서드 보안은(는) CoddyKit의 무료 Spring Boot 4 Complete Guide 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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.
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.
자주 묻는 질문
“SpEL과 사용자 지정 투표기를 활용한 메서드 보안” 강의는 무료인가요?
네 — “SpEL과 사용자 지정 투표기를 활용한 메서드 보안” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Spring Boot 4 Complete Guide 강의 전체를 잠금 해제할 수 있습니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.
“SpEL과 사용자 지정 투표기를 활용한 메서드 보안”에서 뭘 배우나요?
@PreAuthorize, SpEL 표현식 및 사용자 지정 권한 부여 로직으로 세밀한 접근 제어를 적용합니다. 브라우저에서 직접 실행하는 실습 코드로 Spring Boot 4 Complete Guide을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Spring Boot 4 Complete Guide을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Spring Boot 4 Complete Guide은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“SpEL과 사용자 지정 투표기를 활용한 메서드 보안” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Spring Boot 4 Complete Guide 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Spring Boot 4 Complete Guide 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 리소스 서버 JWT 검증과 클레임
- OAuth2 클라이언트와 인증 코드 흐름
- SpEL과 사용자 지정 투표기를 활용한 메서드 보안
- 불투명 토큰 검사 및 토큰 교환