0
# Security Annotations
1
2
Apache Shiro provides method-level security annotations for declarative authorization and authentication requirements. These annotations enable aspect-oriented security by allowing you to secure methods simply by annotating them, rather than writing programmatic security checks.
3
4
## Capabilities
5
6
### Authentication Annotations
7
8
Annotations for controlling access based on authentication state.
9
10
```java { .api }
11
/**
12
* Annotation requiring the current user to be authenticated (not anonymous) in order to access the annotated class/instance/method.
13
*/
14
@Target({ElementType.TYPE, ElementType.METHOD})
15
@Retention(RetentionPolicy.RUNTIME)
16
public @interface RequiresAuthentication {
17
}
18
19
/**
20
* Annotation requiring the current user to be a "user", meaning they are either remembered from a previous session or authenticated in the current session.
21
*/
22
@Target({ElementType.TYPE, ElementType.METHOD})
23
@Retention(RetentionPolicy.RUNTIME)
24
public @interface RequiresUser {
25
}
26
27
/**
28
* Annotation requiring the current user to be a "guest", meaning they are not authenticated or remembered from a previous session.
29
*/
30
@Target({ElementType.TYPE, ElementType.METHOD})
31
@Retention(RetentionPolicy.RUNTIME)
32
public @interface RequiresGuest {
33
}
34
```
35
36
**Usage Examples:**
37
38
```java
39
public class UserController {
40
41
@RequiresAuthentication
42
public void updateProfile(String userId, UserProfile profile) {
43
// This method requires the user to be authenticated
44
// Anonymous users and remember-me users without explicit login will be denied
45
userService.updateProfile(userId, profile);
46
}
47
48
@RequiresUser
49
public void viewDashboard() {
50
// This method allows both authenticated users and remember-me users
51
// Only truly anonymous users will be denied access
52
dashboardService.loadUserDashboard();
53
}
54
55
@RequiresGuest
56
public void showLoginPage() {
57
// This method is only accessible to anonymous users
58
// Authenticated or remembered users will be denied (typically redirected)
59
return "login.jsp";
60
}
61
}
62
```
63
64
### Role-Based Authorization
65
66
Annotation for controlling access based on user roles.
67
68
```java { .api }
69
/**
70
* Annotation requiring the current Subject to have one or more roles in order to execute the annotated method.
71
*/
72
@Target({ElementType.TYPE, ElementType.METHOD})
73
@Retention(RetentionPolicy.RUNTIME)
74
public @interface RequiresRoles {
75
/**
76
* The role identifiers required for the method execution to continue.
77
* @return the role identifiers required for the method execution to continue
78
*/
79
String[] value();
80
81
/**
82
* The logical operation for combining the required roles when multiple roles are specified.
83
* @return the logical operation for combining the required roles
84
*/
85
Logical logical() default Logical.AND;
86
}
87
88
/**
89
* Enumeration for specifying logical operations when combining multiple authorization criteria.
90
*/
91
public enum Logical {
92
/**
93
* Indicates that ALL specified criteria must be met for authorization to succeed.
94
*/
95
AND,
96
97
/**
98
* Indicates that AT LEAST ONE of the specified criteria must be met for authorization to succeed.
99
*/
100
OR
101
}
102
```
103
104
**Usage Examples:**
105
106
```java
107
public class AdminController {
108
109
@RequiresRoles("admin")
110
public void deleteUser(String userId) {
111
// Method requires the "admin" role
112
userService.deleteUser(userId);
113
}
114
115
@RequiresRoles({"admin", "manager"}) // Default is Logical.AND
116
public void viewFinancialReports() {
117
// Method requires BOTH "admin" AND "manager" roles
118
reportService.getFinancialReports();
119
}
120
121
@RequiresRoles(value = {"admin", "manager"}, logical = Logical.OR)
122
public void viewOperationalReports() {
123
// Method requires EITHER "admin" OR "manager" role (or both)
124
reportService.getOperationalReports();
125
}
126
127
@RequiresRoles({"admin", "support", "manager"})
128
public void accessHelpDesk() {
129
// Method requires ALL three roles: admin AND support AND manager
130
helpdeskService.accessSystem();
131
}
132
}
133
134
// Class-level annotation applies to all methods
135
@RequiresRoles("moderator")
136
public class ModerationService {
137
138
public void moderateComment(String commentId) {
139
// Inherits @RequiresRoles("moderator") from class level
140
}
141
142
@RequiresRoles(value = {"admin", "moderator"}, logical = Logical.OR)
143
public void banUser(String userId) {
144
// Method-level annotation overrides class-level
145
// Requires admin OR moderator (method is more permissive than class)
146
}
147
}
148
```
149
150
### Permission-Based Authorization
151
152
Annotation for controlling access based on specific permissions.
153
154
```java { .api }
155
/**
156
* Annotation requiring the current Subject to have one or more permissions in order to execute the annotated method.
157
*/
158
@Target({ElementType.TYPE, ElementType.METHOD})
159
@Retention(RetentionPolicy.RUNTIME)
160
public @interface RequiresPermissions {
161
/**
162
* The permission strings required for the method execution to continue.
163
* @return the permission strings required for the method execution to continue
164
*/
165
String[] value();
166
167
/**
168
* The logical operation for combining the required permissions when multiple permissions are specified.
169
* @return the logical operation for combining the required permissions
170
*/
171
Logical logical() default Logical.AND;
172
}
173
```
174
175
**Usage Examples:**
176
177
```java
178
public class DocumentController {
179
180
@RequiresPermissions("document:read")
181
public Document getDocument(String docId) {
182
// Method requires permission to read documents
183
return documentService.getDocument(docId);
184
}
185
186
@RequiresPermissions("document:write")
187
public void updateDocument(String docId, String content) {
188
// Method requires permission to write/update documents
189
documentService.updateDocument(docId, content);
190
}
191
192
@RequiresPermissions("document:delete")
193
public void deleteDocument(String docId) {
194
// Method requires permission to delete documents
195
documentService.deleteDocument(docId);
196
}
197
198
@RequiresPermissions({"document:read", "document:write"}) // Default is Logical.AND
199
public void editDocument(String docId, String content) {
200
// Method requires BOTH read AND write permissions
201
Document doc = documentService.getDocument(docId);
202
documentService.updateDocument(docId, content);
203
}
204
205
@RequiresPermissions(value = {"document:read", "document:admin"}, logical = Logical.OR)
206
public void viewDocumentMetadata(String docId) {
207
// Method requires EITHER read permission OR admin permission
208
return documentService.getMetadata(docId);
209
}
210
211
@RequiresPermissions("document:read:${docId}") // Dynamic permissions using method parameters
212
public Document getDocumentSecure(String docId) {
213
// Permission check includes the specific document ID
214
// Enables instance-level security (e.g., "document:read:123")
215
return documentService.getDocument(docId);
216
}
217
}
218
219
// Complex permission examples
220
public class UserManagementController {
221
222
@RequiresPermissions("user:create")
223
public void createUser(User user) {
224
userService.createUser(user);
225
}
226
227
@RequiresPermissions("user:read:${userId}")
228
public User getUser(String userId) {
229
// Instance-level permission: user can only read specific user data
230
return userService.getUser(userId);
231
}
232
233
@RequiresPermissions({"user:read:${userId}", "user:write:${userId}"})
234
public void updateUser(String userId, User userUpdates) {
235
// Requires both read and write permissions for the specific user
236
User existingUser = userService.getUser(userId);
237
userService.updateUser(userId, userUpdates);
238
}
239
240
@RequiresPermissions(value = {"user:*", "admin:users"}, logical = Logical.OR)
241
public void performUserOperation(String operation) {
242
// Method requires EITHER all user permissions OR admin user management permission
243
userService.performOperation(operation);
244
}
245
}
246
```
247
248
### Wildcard Permission Examples
249
250
Apache Shiro supports sophisticated wildcard permissions that can be used in annotations:
251
252
```java
253
public class ResourceController {
254
255
@RequiresPermissions("printer:print")
256
public void printDocument() {
257
// Basic permission: allows printing to any printer
258
}
259
260
@RequiresPermissions("printer:print,query")
261
public void printAndQuery() {
262
// Multiple actions: allows both printing and querying printers
263
}
264
265
@RequiresPermissions("printer:print:*")
266
public void printToAnyPrinter() {
267
// Wildcard instance: allows printing to any printer instance
268
}
269
270
@RequiresPermissions("printer:print:laserjet4400n")
271
public void printToSpecificPrinter() {
272
// Specific instance: allows printing only to specific printer
273
}
274
275
@RequiresPermissions("printer:*")
276
public void doAnythingWithPrinters() {
277
// Wildcard action: allows any action on printers
278
}
279
280
@RequiresPermissions("*:view")
281
public void viewAnything() {
282
// Wildcard domain: allows viewing any type of resource
283
}
284
285
@RequiresPermissions("newsletter:*:12345")
286
public void manageNewsletter12345() {
287
// Wildcard action for specific instance: any action on newsletter 12345
288
}
289
290
@RequiresPermissions("file:read:/documents/${department}/*")
291
public void readDepartmentFiles(String department) {
292
// Complex wildcard with parameter substitution
293
// Allows reading any file in the specified department's directory
294
}
295
}
296
```
297
298
### Combining Annotations
299
300
Multiple annotations can be combined for complex security requirements:
301
302
```java
303
public class SecureService {
304
305
@RequiresUser
306
@RequiresRoles("premium")
307
public void accessPremiumFeature() {
308
// Requires user to be remembered/authenticated AND have premium role
309
}
310
311
@RequiresAuthentication
312
@RequiresPermissions({"feature:advanced", "account:active"})
313
public void useAdvancedFeature() {
314
// Requires explicit authentication AND both permissions
315
}
316
317
@RequiresRoles(value = {"admin", "support"}, logical = Logical.OR)
318
@RequiresPermissions("system:maintenance")
319
public void performMaintenance() {
320
// Requires (admin OR support role) AND maintenance permission
321
}
322
}
323
324
// Class-level + method-level annotation combinations
325
@RequiresAuthentication // All methods require authentication
326
public class AuthenticatedService {
327
328
@RequiresRoles("user")
329
public void basicOperation() {
330
// Requires authentication (from class) AND user role
331
}
332
333
@RequiresRoles("admin")
334
@RequiresPermissions("system:config")
335
public void configureSystem() {
336
// Requires authentication (from class) AND admin role AND config permission
337
}
338
339
@RequiresGuest // This will conflict with class-level @RequiresAuthentication
340
public void guestMethod() {
341
// This method will always fail authorization due to conflicting requirements
342
// Demonstrates importance of understanding annotation interactions
343
}
344
}
345
```
346
347
### AOP Integration Components
348
349
The underlying components that make annotation-based security work:
350
351
```java { .api }
352
/**
353
* Base class for method interceptors that perform authorization based on method annotations.
354
*/
355
public abstract class AnnotationMethodInterceptor implements MethodInterceptor {
356
/**
357
* Returns the annotation class that this interceptor handles.
358
* @return the annotation class handled by this interceptor
359
*/
360
public abstract Class<? extends Annotation> getAnnotationClass();
361
362
/**
363
* Performs the interception logic before method execution.
364
* @param mi the method invocation
365
* @return the result of method execution
366
* @throws Throwable if an error occurs during interception or method execution
367
*/
368
public Object invoke(MethodInvocation mi) throws Throwable;
369
}
370
371
/**
372
* Method interceptor that processes @RequiresAuthentication annotations.
373
*/
374
public class AuthenticatedAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
375
public AuthenticatedAnnotationMethodInterceptor();
376
377
public Class<? extends Annotation> getAnnotationClass();
378
protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
379
}
380
381
/**
382
* Method interceptor that processes @RequiresUser annotations.
383
*/
384
public class UserAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
385
public UserAnnotationMethodInterceptor();
386
387
public Class<? extends Annotation> getAnnotationClass();
388
protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
389
}
390
391
/**
392
* Method interceptor that processes @RequiresGuest annotations.
393
*/
394
public class GuestAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
395
public GuestAnnotationMethodInterceptor();
396
397
public Class<? extends Annotation> getAnnotationClass();
398
protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
399
}
400
401
/**
402
* Method interceptor that processes @RequiresRoles annotations.
403
*/
404
public class RoleAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
405
public RoleAnnotationMethodInterceptor();
406
407
public Class<? extends Annotation> getAnnotationClass();
408
protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
409
}
410
411
/**
412
* Method interceptor that processes @RequiresPermissions annotations.
413
*/
414
public class PermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
415
public PermissionAnnotationMethodInterceptor();
416
417
public Class<? extends Annotation> getAnnotationClass();
418
protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
419
}
420
```
421
422
### Framework Integration
423
424
These annotations are typically integrated with AOP frameworks:
425
426
**Spring AOP Integration Example:**
427
428
```java
429
@Configuration
430
@EnableAspectJAutoProxy
431
public class ShiroConfig {
432
433
@Bean
434
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
435
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
436
advisor.setSecurityManager(securityManager);
437
return advisor;
438
}
439
440
@Bean
441
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
442
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
443
creator.setProxyTargetClass(true);
444
return creator;
445
}
446
}
447
```
448
449
**AspectJ Integration Example:**
450
451
```java
452
@Aspect
453
public class ShiroSecurityAspect {
454
455
@Around("@annotation(requiresAuthentication)")
456
public Object checkAuthentication(ProceedingJoinPoint joinPoint, RequiresAuthentication requiresAuthentication) throws Throwable {
457
Subject subject = SecurityUtils.getSubject();
458
if (!subject.isAuthenticated()) {
459
throw new UnauthenticatedException("User must be authenticated");
460
}
461
return joinPoint.proceed();
462
}
463
464
@Around("@annotation(requiresRoles)")
465
public Object checkRoles(ProceedingJoinPoint joinPoint, RequiresRoles requiresRoles) throws Throwable {
466
Subject subject = SecurityUtils.getSubject();
467
String[] roles = requiresRoles.value();
468
469
if (requiresRoles.logical() == Logical.AND) {
470
subject.checkRoles(Arrays.asList(roles));
471
} else {
472
boolean hasAnyRole = Arrays.stream(roles).anyMatch(subject::hasRole);
473
if (!hasAnyRole) {
474
throw new UnauthorizedException("User must have at least one of the specified roles");
475
}
476
}
477
478
return joinPoint.proceed();
479
}
480
}
481
```
482
483
## Best Practices
484
485
1. **Class vs Method Level**: Use class-level annotations for common requirements and method-level for specific overrides
486
2. **Logical Operations**: Understand when to use AND vs OR for multiple roles/permissions
487
3. **Permission Granularity**: Design permissions with appropriate granularity (not too broad, not too specific)
488
4. **Parameter Substitution**: Use method parameter substitution for instance-level permissions
489
5. **Testing**: Always test annotation-based security with both positive and negative test cases
490
6. **Performance**: Consider caching authorization results for frequently accessed methods
491
7. **Error Handling**: Implement proper exception handling for authorization failures