A powerful and flexible open-source Java security framework providing authentication, authorization, session management, and cryptographic services
—
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.
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";
}
}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)
}
}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);
}
}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
}
}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
}
}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;
}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();
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-apache-shiro--shiro-core