CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-apache-shiro--shiro-core

A powerful and flexible open-source Java security framework providing authentication, authorization, session management, and cryptographic services

Pending
Overview
Eval results
Files

security-annotations.mddocs/

Security Annotations

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.

Capabilities

Authentication Annotations

Annotations for controlling access based on authentication state.

/**
 * Annotation requiring the current user to be authenticated (not anonymous) in order to access the annotated class/instance/method.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresAuthentication {
}

/**
 * Annotation requiring the current user to be a "user", meaning they are either remembered from a previous session or authenticated in the current session.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresUser {
}

/**
 * Annotation requiring the current user to be a "guest", meaning they are not authenticated or remembered from a previous session.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresGuest {
}

Usage Examples:

public class UserController {
    
    @RequiresAuthentication
    public void updateProfile(String userId, UserProfile profile) {
        // This method requires the user to be authenticated
        // Anonymous users and remember-me users without explicit login will be denied
        userService.updateProfile(userId, profile);
    }
    
    @RequiresUser  
    public void viewDashboard() {
        // This method allows both authenticated users and remember-me users
        // Only truly anonymous users will be denied access
        dashboardService.loadUserDashboard();
    }
    
    @RequiresGuest
    public void showLoginPage() {
        // This method is only accessible to anonymous users
        // Authenticated or remembered users will be denied (typically redirected)
        return "login.jsp";
    }
}

Role-Based Authorization

Annotation for controlling access based on user roles.

/**
 * Annotation requiring the current Subject to have one or more roles in order to execute the annotated method.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)  
public @interface RequiresRoles {
    /**
     * The role identifiers required for the method execution to continue.
     * @return the role identifiers required for the method execution to continue
     */
    String[] value();

    /**
     * The logical operation for combining the required roles when multiple roles are specified.
     * @return the logical operation for combining the required roles
     */
    Logical logical() default Logical.AND;
}

/**
 * Enumeration for specifying logical operations when combining multiple authorization criteria.
 */
public enum Logical {
    /**
     * Indicates that ALL specified criteria must be met for authorization to succeed.
     */
    AND,
    
    /**
     * Indicates that AT LEAST ONE of the specified criteria must be met for authorization to succeed.
     */
    OR
}

Usage Examples:

public class AdminController {
    
    @RequiresRoles("admin")
    public void deleteUser(String userId) {
        // Method requires the "admin" role
        userService.deleteUser(userId);
    }
    
    @RequiresRoles({"admin", "manager"})  // Default is Logical.AND
    public void viewFinancialReports() {
        // Method requires BOTH "admin" AND "manager" roles
        reportService.getFinancialReports();
    }
    
    @RequiresRoles(value = {"admin", "manager"}, logical = Logical.OR)
    public void viewOperationalReports() {
        // Method requires EITHER "admin" OR "manager" role (or both)
        reportService.getOperationalReports();
    }
    
    @RequiresRoles({"admin", "support", "manager"})
    public void accessHelpDesk() {
        // Method requires ALL three roles: admin AND support AND manager
        helpdeskService.accessSystem();
    }
}

// Class-level annotation applies to all methods
@RequiresRoles("moderator")
public class ModerationService {
    
    public void moderateComment(String commentId) {
        // Inherits @RequiresRoles("moderator") from class level
    }
    
    @RequiresRoles(value = {"admin", "moderator"}, logical = Logical.OR)
    public void banUser(String userId) {
        // Method-level annotation overrides class-level
        // Requires admin OR moderator (method is more permissive than class)
    }
}

Permission-Based Authorization

Annotation for controlling access based on specific permissions.

/**
 * Annotation requiring the current Subject to have one or more permissions in order to execute the annotated method.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
    /**
     * The permission strings required for the method execution to continue.
     * @return the permission strings required for the method execution to continue
     */
    String[] value();

    /**
     * The logical operation for combining the required permissions when multiple permissions are specified.
     * @return the logical operation for combining the required permissions
     */
    Logical logical() default Logical.AND;
}

Usage Examples:

public class DocumentController {
    
    @RequiresPermissions("document:read")
    public Document getDocument(String docId) {
        // Method requires permission to read documents
        return documentService.getDocument(docId);
    }
    
    @RequiresPermissions("document:write")
    public void updateDocument(String docId, String content) {
        // Method requires permission to write/update documents
        documentService.updateDocument(docId, content);
    }
    
    @RequiresPermissions("document:delete")
    public void deleteDocument(String docId) {
        // Method requires permission to delete documents
        documentService.deleteDocument(docId);
    }
    
    @RequiresPermissions({"document:read", "document:write"})  // Default is Logical.AND
    public void editDocument(String docId, String content) {
        // Method requires BOTH read AND write permissions
        Document doc = documentService.getDocument(docId);
        documentService.updateDocument(docId, content);
    }
    
    @RequiresPermissions(value = {"document:read", "document:admin"}, logical = Logical.OR)
    public void viewDocumentMetadata(String docId) {
        // Method requires EITHER read permission OR admin permission
        return documentService.getMetadata(docId);
    }
    
    @RequiresPermissions("document:read:${docId}")  // Dynamic permissions using method parameters
    public Document getDocumentSecure(String docId) {
        // Permission check includes the specific document ID
        // Enables instance-level security (e.g., "document:read:123")
        return documentService.getDocument(docId);
    }
}

// Complex permission examples
public class UserManagementController {
    
    @RequiresPermissions("user:create")
    public void createUser(User user) {
        userService.createUser(user);
    }
    
    @RequiresPermissions("user:read:${userId}")
    public User getUser(String userId) {
        // Instance-level permission: user can only read specific user data
        return userService.getUser(userId);
    }
    
    @RequiresPermissions({"user:read:${userId}", "user:write:${userId}"})
    public void updateUser(String userId, User userUpdates) {
        // Requires both read and write permissions for the specific user
        User existingUser = userService.getUser(userId);
        userService.updateUser(userId, userUpdates);
    }
    
    @RequiresPermissions(value = {"user:*", "admin:users"}, logical = Logical.OR)
    public void performUserOperation(String operation) {
        // Method requires EITHER all user permissions OR admin user management permission
        userService.performOperation(operation);
    }
}

Wildcard Permission Examples

Apache Shiro supports sophisticated wildcard permissions that can be used in annotations:

public class ResourceController {
    
    @RequiresPermissions("printer:print")
    public void printDocument() {
        // Basic permission: allows printing to any printer
    }
    
    @RequiresPermissions("printer:print,query")
    public void printAndQuery() {
        // Multiple actions: allows both printing and querying printers
    }
    
    @RequiresPermissions("printer:print:*")
    public void printToAnyPrinter() {
        // Wildcard instance: allows printing to any printer instance
    }
    
    @RequiresPermissions("printer:print:laserjet4400n")
    public void printToSpecificPrinter() {
        // Specific instance: allows printing only to specific printer
    }
    
    @RequiresPermissions("printer:*")
    public void doAnythingWithPrinters() {
        // Wildcard action: allows any action on printers
    }
    
    @RequiresPermissions("*:view")
    public void viewAnything() {
        // Wildcard domain: allows viewing any type of resource
    }
    
    @RequiresPermissions("newsletter:*:12345")
    public void manageNewsletter12345() {
        // Wildcard action for specific instance: any action on newsletter 12345
    }
    
    @RequiresPermissions("file:read:/documents/${department}/*")
    public void readDepartmentFiles(String department) {
        // Complex wildcard with parameter substitution
        // Allows reading any file in the specified department's directory
    }
}

Combining Annotations

Multiple annotations can be combined for complex security requirements:

public class SecureService {
    
    @RequiresUser
    @RequiresRoles("premium")
    public void accessPremiumFeature() {
        // Requires user to be remembered/authenticated AND have premium role
    }
    
    @RequiresAuthentication
    @RequiresPermissions({"feature:advanced", "account:active"})
    public void useAdvancedFeature() {
        // Requires explicit authentication AND both permissions
    }
    
    @RequiresRoles(value = {"admin", "support"}, logical = Logical.OR)
    @RequiresPermissions("system:maintenance")
    public void performMaintenance() {
        // Requires (admin OR support role) AND maintenance permission
    }
}

// Class-level + method-level annotation combinations
@RequiresAuthentication  // All methods require authentication
public class AuthenticatedService {
    
    @RequiresRoles("user")
    public void basicOperation() {
        // Requires authentication (from class) AND user role
    }
    
    @RequiresRoles("admin") 
    @RequiresPermissions("system:config")
    public void configureSystem() {
        // Requires authentication (from class) AND admin role AND config permission
    }
    
    @RequiresGuest  // This will conflict with class-level @RequiresAuthentication
    public void guestMethod() {
        // This method will always fail authorization due to conflicting requirements
        // Demonstrates importance of understanding annotation interactions
    }
}

AOP Integration Components

The underlying components that make annotation-based security work:

/**
 * Base class for method interceptors that perform authorization based on method annotations.
 */
public abstract class AnnotationMethodInterceptor implements MethodInterceptor {
    /**
     * Returns the annotation class that this interceptor handles.
     * @return the annotation class handled by this interceptor
     */
    public abstract Class<? extends Annotation> getAnnotationClass();

    /**
     * Performs the interception logic before method execution.
     * @param mi the method invocation
     * @return the result of method execution
     * @throws Throwable if an error occurs during interception or method execution
     */
    public Object invoke(MethodInvocation mi) throws Throwable;
}

/**
 * Method interceptor that processes @RequiresAuthentication annotations.
 */
public class AuthenticatedAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
    public AuthenticatedAnnotationMethodInterceptor();
    
    public Class<? extends Annotation> getAnnotationClass();
    protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
}

/**
 * Method interceptor that processes @RequiresUser annotations.
 */
public class UserAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
    public UserAnnotationMethodInterceptor();
    
    public Class<? extends Annotation> getAnnotationClass();
    protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
}

/**
 * Method interceptor that processes @RequiresGuest annotations.
 */
public class GuestAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
    public GuestAnnotationMethodInterceptor();
    
    public Class<? extends Annotation> getAnnotationClass();
    protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
}

/**
 * Method interceptor that processes @RequiresRoles annotations.
 */
public class RoleAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
    public RoleAnnotationMethodInterceptor();
    
    public Class<? extends Annotation> getAnnotationClass();
    protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
}

/**
 * Method interceptor that processes @RequiresPermissions annotations.
 */
public class PermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
    public PermissionAnnotationMethodInterceptor();
    
    public Class<? extends Annotation> getAnnotationClass();
    protected void assertAuthorized(MethodInvocation mi) throws AuthorizationException;
}

Framework Integration

These annotations are typically integrated with AOP frameworks:

Spring AOP Integration Example:

@Configuration
@EnableAspectJAutoProxy
public class ShiroConfig {
    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}

AspectJ Integration Example:

@Aspect
public class ShiroSecurityAspect {
    
    @Around("@annotation(requiresAuthentication)")
    public Object checkAuthentication(ProceedingJoinPoint joinPoint, RequiresAuthentication requiresAuthentication) throws Throwable {
        Subject subject = SecurityUtils.getSubject();
        if (!subject.isAuthenticated()) {
            throw new UnauthenticatedException("User must be authenticated");
        }
        return joinPoint.proceed();
    }
    
    @Around("@annotation(requiresRoles)")
    public Object checkRoles(ProceedingJoinPoint joinPoint, RequiresRoles requiresRoles) throws Throwable {
        Subject subject = SecurityUtils.getSubject();
        String[] roles = requiresRoles.value();
        
        if (requiresRoles.logical() == Logical.AND) {
            subject.checkRoles(Arrays.asList(roles));
        } else {
            boolean hasAnyRole = Arrays.stream(roles).anyMatch(subject::hasRole);
            if (!hasAnyRole) {
                throw new UnauthorizedException("User must have at least one of the specified roles");
            }
        }
        
        return joinPoint.proceed();
    }
}

Best Practices

  1. Class vs Method Level: Use class-level annotations for common requirements and method-level for specific overrides
  2. Logical Operations: Understand when to use AND vs OR for multiple roles/permissions
  3. Permission Granularity: Design permissions with appropriate granularity (not too broad, not too specific)
  4. Parameter Substitution: Use method parameter substitution for instance-level permissions
  5. Testing: Always test annotation-based security with both positive and negative test cases
  6. Performance: Consider caching authorization results for frequently accessed methods
  7. Error Handling: Implement proper exception handling for authorization failures

Install with Tessl CLI

npx tessl i tessl/maven-org-apache-shiro--shiro-core

docs

authentication.md

authorization.md

index.md

realms.md

security-annotations.md

security-manager.md

session-management.md

subject-operations.md

utilities.md

tile.json