0
# Method Security Configuration
1
2
Spring Security Config provides comprehensive method-level security configuration through annotations and configuration classes. Method security enables fine-grained access control at the method level using annotations like @PreAuthorize, @PostAuthorize, @Secured, and JSR-250 annotations.
3
4
## Method Security Annotations
5
6
### @EnableMethodSecurity
7
8
Modern method-level security configuration annotation (Spring Security 5.6+).
9
10
```java { .api }
11
@Target(ElementType.TYPE)
12
@Retention(RetentionPolicy.RUNTIME)
13
@Documented
14
@Import(MethodSecurityConfiguration.class)
15
public @interface EnableMethodSecurity {
16
/**
17
* Determines if Spring Security's pre/post annotations should be enabled.
18
* @return true if pre/post annotations are enabled, false otherwise. Default is true.
19
*/
20
boolean prePostEnabled() default true;
21
22
/**
23
* Determines if Spring Security's @Secured annotation should be enabled.
24
* @return true if @Secured is enabled, false otherwise. Default is false.
25
*/
26
boolean securedEnabled() default false;
27
28
/**
29
* Determines if JSR-250 annotations should be enabled.
30
* @return true if JSR-250 annotations are enabled, false otherwise. Default is false.
31
*/
32
boolean jsr250Enabled() default false;
33
34
/**
35
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
36
* to standard Java interface-based proxies.
37
* @return true to use CGLIB proxies, false for JDK proxies. Default is false.
38
*/
39
boolean proxyTargetClass() default false;
40
41
/**
42
* Indicate how security advice should be applied.
43
* @return the advice mode. Default is PROXY.
44
*/
45
AdviceMode mode() default AdviceMode.PROXY;
46
47
/**
48
* Indicate the offset in the order for SecurityMethodInterceptor.
49
* @return the offset value. Default is 0.
50
*/
51
int offset() default 0;
52
}
53
```
54
55
**Usage Example:**
56
57
```java
58
@Configuration
59
@EnableMethodSecurity(
60
prePostEnabled = true,
61
securedEnabled = true,
62
jsr250Enabled = true
63
)
64
public class MethodSecurityConfig {
65
66
@Bean
67
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
68
DefaultMethodSecurityExpressionHandler expressionHandler =
69
new DefaultMethodSecurityExpressionHandler();
70
expressionHandler.setPermissionEvaluator(customPermissionEvaluator());
71
return expressionHandler;
72
}
73
}
74
```
75
76
### @EnableGlobalMethodSecurity (Deprecated)
77
78
Legacy method-level security configuration annotation.
79
80
```java { .api }
81
@Target(ElementType.TYPE)
82
@Retention(RetentionPolicy.RUNTIME)
83
@Documented
84
@Import(GlobalMethodSecurityConfiguration.class)
85
@Deprecated
86
public @interface EnableGlobalMethodSecurity {
87
boolean prePostEnabled() default false;
88
boolean securedEnabled() default false;
89
boolean jsr250Enabled() default false;
90
boolean proxyTargetClass() default false;
91
AdviceMode mode() default AdviceMode.PROXY;
92
int order() default Ordered.LOWEST_PRECEDENCE;
93
}
94
```
95
96
### @EnableReactiveMethodSecurity
97
98
Reactive method security configuration for WebFlux applications.
99
100
```java { .api }
101
@Target(ElementType.TYPE)
102
@Retention(RetentionPolicy.RUNTIME)
103
@Documented
104
@Import(ReactiveMethodSecurityConfiguration.class)
105
public @interface EnableReactiveMethodSecurity {
106
/**
107
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
108
* to standard Java interface-based proxies.
109
* @return true to use CGLIB proxies, false for JDK proxies. Default is false.
110
*/
111
boolean proxyTargetClass() default false;
112
113
/**
114
* Indicate how security advice should be applied.
115
* @return the advice mode. Default is PROXY.
116
*/
117
AdviceMode mode() default AdviceMode.PROXY;
118
119
/**
120
* Indicate the order in which the SecurityMethodInterceptor should be applied.
121
* @return the order value. Default is LOWEST_PRECEDENCE.
122
*/
123
int order() default Ordered.LOWEST_PRECEDENCE;
124
125
/**
126
* Indicate whether to use AuthorizationManager for reactive method security.
127
* @return true to use AuthorizationManager, false for legacy approach. Default is true.
128
*/
129
boolean useAuthorizationManager() default true;
130
}
131
```
132
133
## Method Security Configuration Classes
134
135
### GlobalMethodSecurityConfiguration
136
137
Base class for method security configuration (legacy approach).
138
139
```java { .api }
140
public abstract class GlobalMethodSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
141
142
// Configuration Methods
143
protected void configure(AuthenticationManagerBuilder auth) throws Exception {}
144
145
// Access Decision Manager
146
protected AccessDecisionManager accessDecisionManager() {
147
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
148
ExpressionBasedPreInvocationAdvice expressionAdvice =
149
new ExpressionBasedPreInvocationAdvice();
150
expressionAdvice.setExpressionHandler(getExpressionHandler());
151
152
if (prePostEnabled()) {
153
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
154
}
155
if (jsr250Enabled()) {
156
decisionVoters.add(new Jsr250Voter());
157
}
158
159
return new AffirmativeBased(decisionVoters);
160
}
161
162
// Expression Handler
163
protected MethodSecurityExpressionHandler createExpressionHandler() {
164
DefaultMethodSecurityExpressionHandler expressionHandler =
165
new DefaultMethodSecurityExpressionHandler();
166
if (authenticationTrustResolver != null) {
167
expressionHandler.setTrustResolver(authenticationTrustResolver);
168
}
169
if (roleHierarchy != null) {
170
expressionHandler.setRoleHierarchy(roleHierarchy);
171
}
172
if (permissionEvaluator != null) {
173
expressionHandler.setPermissionEvaluator(permissionEvaluator);
174
}
175
return expressionHandler;
176
}
177
178
// Configuration Properties
179
protected boolean prePostEnabled() { return false; }
180
protected boolean securedEnabled() { return false; }
181
protected boolean jsr250Enabled() { return false; }
182
183
// Authentication Manager
184
protected AuthenticationManager authenticationManager() throws Exception {
185
return authenticationManagerBuilder.build();
186
}
187
188
// Run As Manager
189
protected RunAsManager runAsManager() { return null; }
190
191
// Permission Evaluator
192
protected PermissionEvaluator permissionEvaluator() { return null; }
193
194
// Role Hierarchy
195
protected RoleHierarchy roleHierarchy() { return null; }
196
197
// Authentication Trust Resolver
198
protected AuthenticationTrustResolver authenticationTrustResolver() { return null; }
199
}
200
```
201
202
**Usage Example:**
203
204
```java
205
@Configuration
206
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
207
public class CustomMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
208
209
@Override
210
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
211
auth.userDetailsService(customUserDetailsService())
212
.passwordEncoder(passwordEncoder());
213
}
214
215
@Override
216
protected MethodSecurityExpressionHandler createExpressionHandler() {
217
DefaultMethodSecurityExpressionHandler expressionHandler =
218
new DefaultMethodSecurityExpressionHandler();
219
expressionHandler.setPermissionEvaluator(customPermissionEvaluator());
220
expressionHandler.setRoleHierarchy(roleHierarchy());
221
return expressionHandler;
222
}
223
224
@Override
225
protected AccessDecisionManager accessDecisionManager() {
226
List<AccessDecisionVoter<?>> decisionVoters = Arrays.asList(
227
new RoleVoter(),
228
new AuthenticatedVoter(),
229
new WebExpressionVoter()
230
);
231
return new AffirmativeBased(decisionVoters);
232
}
233
234
@Bean
235
public RoleHierarchy roleHierarchy() {
236
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
237
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_GUEST");
238
return roleHierarchy;
239
}
240
241
@Bean
242
public PermissionEvaluator customPermissionEvaluator() {
243
return new CustomPermissionEvaluator();
244
}
245
}
246
```
247
248
### ReactiveMethodSecurityConfiguration
249
250
Configuration class for reactive method security.
251
252
```java { .api }
253
@Configuration
254
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
255
public class ReactiveMethodSecurityConfiguration {
256
257
@Bean
258
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
259
public ReactiveMethodSecurityInterceptor methodSecurityInterceptor(
260
ReactiveAuthorizationManager<MethodInvocation> authorizationManager) {
261
ReactiveMethodSecurityInterceptor interceptor =
262
new ReactiveMethodSecurityInterceptor();
263
interceptor.setAuthorizationManager(authorizationManager);
264
return interceptor;
265
}
266
267
@Bean
268
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
269
public ReactiveAuthorizationManager<MethodInvocation> methodAuthorizationManager() {
270
return new PrePostAdviceReactiveMethodInterceptor();
271
}
272
}
273
```
274
275
## Method Security Infrastructure
276
277
### MethodSecurityMetadataSourceAdvisorRegistrar
278
279
Registrar for method security metadata source advisors.
280
281
```java { .api }
282
public final class MethodSecurityMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
283
284
@Override
285
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
286
BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
287
288
// Register method security advisor
289
BeanDefinition advisor = BeanDefinitionBuilder
290
.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class)
291
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
292
.getBeanDefinition();
293
294
registry.registerBeanDefinition("methodSecurityAdvisor", advisor);
295
}
296
}
297
```
298
299
### AuthorizationProxyConfiguration
300
301
Configuration for authorization proxies in method security.
302
303
```java { .api }
304
@Configuration
305
@ConditionalOnClass(AuthorizationManager.class)
306
public class AuthorizationProxyConfiguration {
307
308
@Bean
309
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
310
public AuthorizationAdvisor authorizationAdvisor(
311
AuthorizationManager<MethodInvocation> authorizationManager) {
312
return new AuthorizationAdvisor(authorizationManager);
313
}
314
315
@Bean
316
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
317
public AuthorizationManager<MethodInvocation> methodAuthorizationManager() {
318
return new PrePostAdviceAuthorizationManager();
319
}
320
}
321
```
322
323
## Security Expressions and Evaluation
324
325
### MethodSecurityExpressionHandler
326
327
Interface for handling method security expressions.
328
329
```java { .api }
330
public interface MethodSecurityExpressionHandler extends SecurityExpressionHandler<MethodInvocation> {
331
332
/**
333
* Filter the collection or array returned from a method invocation.
334
* @param filterObject the collection or array to filter
335
* @param expression the expression to evaluate for each element
336
* @param ctx the evaluation context
337
* @return the filtered collection or array
338
*/
339
Object filter(Object filterObject, Expression expression, EvaluationContext ctx);
340
341
/**
342
* Create an evaluation context for method invocation.
343
* @param auth the current authentication
344
* @param mi the method invocation
345
* @return the evaluation context
346
*/
347
EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi);
348
349
/**
350
* Set the permission evaluator for object-level security.
351
* @param permissionEvaluator the permission evaluator
352
*/
353
void setPermissionEvaluator(PermissionEvaluator permissionEvaluator);
354
355
/**
356
* Set the role hierarchy for inherited roles.
357
* @param roleHierarchy the role hierarchy
358
*/
359
void setRoleHierarchy(RoleHierarchy roleHierarchy);
360
361
/**
362
* Set the trust resolver for authentication state evaluation.
363
* @param trustResolver the authentication trust resolver
364
*/
365
void setTrustResolver(AuthenticationTrustResolver trustResolver);
366
}
367
```
368
369
### DefaultMethodSecurityExpressionHandler
370
371
Default implementation of method security expression handler.
372
373
```java { .api }
374
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
375
implements MethodSecurityExpressionHandler, BeanFactoryAware {
376
377
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
378
private RoleHierarchy roleHierarchy;
379
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
380
381
@Override
382
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
383
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
384
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(auth);
385
root.setPermissionEvaluator(permissionEvaluator);
386
root.setTrustResolver(trustResolver);
387
root.setRoleHierarchy(roleHierarchy);
388
ctx.setRootObject(root);
389
return ctx;
390
}
391
392
@Override
393
public Object filter(Object filterObject, Expression expression, EvaluationContext ctx) {
394
if (filterObject instanceof Collection) {
395
return filterCollection((Collection<?>) filterObject, expression, ctx);
396
} else if (filterObject.getClass().isArray()) {
397
return filterArray((Object[]) filterObject, expression, ctx);
398
}
399
return filterObject;
400
}
401
402
// Setter methods
403
public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
404
this.permissionEvaluator = permissionEvaluator;
405
}
406
407
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
408
this.roleHierarchy = roleHierarchy;
409
}
410
411
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
412
this.trustResolver = trustResolver;
413
}
414
}
415
```
416
417
## Method Security Annotations Usage
418
419
### Pre/Post Authorization Annotations
420
421
Security annotations for method-level access control.
422
423
```java { .api }
424
// Pre-authorization annotation
425
@Target({ElementType.METHOD, ElementType.TYPE})
426
@Retention(RetentionPolicy.RUNTIME)
427
@Inherited
428
@Documented
429
public @interface PreAuthorize {
430
/**
431
* Spring Expression Language (SpEL) expression used to determine
432
* if a method invocation is allowed.
433
* @return the SpEL expression
434
*/
435
String value();
436
}
437
438
// Post-authorization annotation
439
@Target({ElementType.METHOD, ElementType.TYPE})
440
@Retention(RetentionPolicy.RUNTIME)
441
@Inherited
442
@Documented
443
public @interface PostAuthorize {
444
/**
445
* Spring Expression Language (SpEL) expression used to determine
446
* if a method return value should be returned to the caller.
447
* @return the SpEL expression
448
*/
449
String value();
450
}
451
452
// Pre-filtering annotation
453
@Target({ElementType.METHOD, ElementType.TYPE})
454
@Retention(RetentionPolicy.RUNTIME)
455
@Inherited
456
@Documented
457
public @interface PreFilter {
458
/**
459
* Spring Expression Language (SpEL) expression used to filter
460
* method arguments before method invocation.
461
* @return the SpEL expression
462
*/
463
String value();
464
465
/**
466
* Name of the argument to filter when there are multiple arguments.
467
* @return the argument name
468
*/
469
String filterTarget() default "";
470
}
471
472
// Post-filtering annotation
473
@Target({ElementType.METHOD, ElementType.TYPE})
474
@Retention(RetentionPolicy.RUNTIME)
475
@Inherited
476
@Documented
477
public @interface PostFilter {
478
/**
479
* Spring Expression Language (SpEL) expression used to filter
480
* the method return value.
481
* @return the SpEL expression
482
*/
483
String value();
484
}
485
486
// Secured annotation
487
@Target({ElementType.METHOD, ElementType.TYPE})
488
@Retention(RetentionPolicy.RUNTIME)
489
@Inherited
490
@Documented
491
public @interface Secured {
492
/**
493
* List of roles/authorities required to invoke the secured method.
494
* @return the required roles/authorities
495
*/
496
String[] value();
497
}
498
```
499
500
**Usage Examples:**
501
502
```java
503
@Service
504
public class DocumentService {
505
506
// Pre-authorization: Check role before method execution
507
@PreAuthorize("hasRole('ADMIN')")
508
public void deleteDocument(Long documentId) {
509
// Implementation
510
}
511
512
// Pre-authorization: Check ownership
513
@PreAuthorize("@documentService.isOwner(authentication.name, #documentId)")
514
public Document getDocument(Long documentId) {
515
// Implementation
516
}
517
518
// Post-authorization: Filter return value based on ownership
519
@PostAuthorize("returnObject.owner == authentication.name or hasRole('ADMIN')")
520
public Document loadDocument(Long documentId) {
521
// Implementation
522
}
523
524
// Pre-filtering: Filter collection arguments
525
@PreFilter("hasPermission(filterObject, 'READ')")
526
public List<Document> processDocuments(List<Document> documents) {
527
// Process only documents the user can read
528
return documents;
529
}
530
531
// Post-filtering: Filter collection return values
532
@PostFilter("hasPermission(filterObject, 'READ')")
533
public List<Document> findAllDocuments() {
534
// Return only documents the user can read
535
return documentRepository.findAll();
536
}
537
538
// Secured annotation: Simple role-based access
539
@Secured({"ROLE_USER", "ROLE_ADMIN"})
540
public List<Document> getUserDocuments() {
541
// Implementation
542
}
543
544
// JSR-250 annotations
545
@RolesAllowed("ADMIN")
546
public void adminOnlyMethod() {
547
// Implementation
548
}
549
550
@PermitAll
551
public String publicMethod() {
552
return "Public access";
553
}
554
555
@DenyAll
556
public void restrictedMethod() {
557
// This method is never accessible
558
}
559
}
560
```
561
562
## Custom Permission Evaluation
563
564
### PermissionEvaluator Interface
565
566
Interface for custom permission evaluation logic.
567
568
```java { .api }
569
public interface PermissionEvaluator {
570
/**
571
* Determine whether the user has the given permission for the domain object.
572
* @param authentication the user authentication
573
* @param targetDomainObject the domain object
574
* @param permission the permission to check
575
* @return true if access is granted, false otherwise
576
*/
577
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);
578
579
/**
580
* Determine whether the user has the given permission for the domain object
581
* identified by its identifier.
582
* @param authentication the user authentication
583
* @param targetId the identifier of the domain object
584
* @param targetType the type of the domain object
585
* @param permission the permission to check
586
* @return true if access is granted, false otherwise
587
*/
588
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);
589
}
590
```
591
592
**Custom Permission Evaluator Example:**
593
594
```java
595
@Component
596
public class CustomPermissionEvaluator implements PermissionEvaluator {
597
598
@Autowired
599
private PermissionService permissionService;
600
601
@Override
602
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
603
if (authentication == null || targetDomainObject == null || !(permission instanceof String)) {
604
return false;
605
}
606
607
String username = authentication.getName();
608
String permissionName = (String) permission;
609
610
if (targetDomainObject instanceof Document) {
611
Document document = (Document) targetDomainObject;
612
return permissionService.hasDocumentPermission(username, document.getId(), permissionName);
613
}
614
615
return false;
616
}
617
618
@Override
619
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
620
if (authentication == null || targetId == null || targetType == null || !(permission instanceof String)) {
621
return false;
622
}
623
624
String username = authentication.getName();
625
String permissionName = (String) permission;
626
627
if ("Document".equals(targetType)) {
628
return permissionService.hasDocumentPermission(username, (Long) targetId, permissionName);
629
}
630
631
return false;
632
}
633
}
634
635
// Usage in service methods
636
@Service
637
public class DocumentService {
638
639
@PreAuthorize("hasPermission(#documentId, 'Document', 'READ')")
640
public Document getDocument(Long documentId) {
641
return documentRepository.findById(documentId);
642
}
643
644
@PreAuthorize("hasPermission(#document, 'WRITE')")
645
public Document updateDocument(Document document) {
646
return documentRepository.save(document);
647
}
648
}
649
```
650
651
## Advanced Method Security Patterns
652
653
### Class-Level Security
654
655
Apply security annotations at the class level for all methods:
656
657
```java
658
@Service
659
@PreAuthorize("hasRole('USER')")
660
public class UserService {
661
662
// All methods require USER role by default
663
public List<User> getAllUsers() {
664
return userRepository.findAll();
665
}
666
667
// Override class-level security
668
@PreAuthorize("hasRole('ADMIN')")
669
public void deleteUser(Long userId) {
670
userRepository.deleteById(userId);
671
}
672
673
// No additional security beyond class level
674
public User getCurrentUser(Authentication auth) {
675
return userRepository.findByUsername(auth.getName());
676
}
677
}
678
```
679
680
### Security with Complex Expressions
681
682
Use complex SpEL expressions for sophisticated authorization logic:
683
684
```java
685
@Service
686
public class ProjectService {
687
688
@PreAuthorize("hasRole('ADMIN') or " +
689
"(hasRole('PROJECT_MANAGER') and @projectService.isProjectManager(authentication.name, #projectId)) or " +
690
"(hasRole('DEVELOPER') and @projectService.isProjectMember(authentication.name, #projectId))")
691
public Project getProject(Long projectId) {
692
return projectRepository.findById(projectId);
693
}
694
695
@PreAuthorize("@projectService.canEditProject(authentication, #project)")
696
public Project updateProject(Project project) {
697
return projectRepository.save(project);
698
}
699
700
public boolean canEditProject(Authentication auth, Project project) {
701
String username = auth.getName();
702
return hasRole(auth, "ADMIN") ||
703
(hasRole(auth, "PROJECT_MANAGER") && isProjectManager(username, project.getId())) ||
704
(hasRole(auth, "DEVELOPER") && isProjectLead(username, project.getId()));
705
}
706
}
707
```
708
709
### Reactive Method Security
710
711
Method security for reactive applications:
712
713
```java
714
@Service
715
public class ReactiveDocumentService {
716
717
@PreAuthorize("hasRole('USER')")
718
public Mono<Document> getDocument(String documentId) {
719
return documentRepository.findById(documentId);
720
}
721
722
@PreAuthorize("hasPermission(#document, 'WRITE')")
723
public Mono<Document> saveDocument(Document document) {
724
return documentRepository.save(document);
725
}
726
727
@PostFilter("hasPermission(filterObject, 'READ')")
728
public Flux<Document> findAllDocuments() {
729
return documentRepository.findAll();
730
}
731
}
732
```