or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdauthorization.mdindex.mdrealms.mdsecurity-annotations.mdsecurity-manager.mdsession-management.mdsubject-operations.mdutilities.md

security-annotations.mddocs/

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