Apache Shiro authorization controls access through roles (coarse-grained) and permissions (fine-grained string-based checks).

Permission Model

Permissions follow a resource:action convention:

  order:create
order:read
order:update
order:delete
user:*
*:*
  

Wildcards:

  • order:* — all actions on orders
  • *:* — all permissions (admin)

Checking Permissions

  Subject subject = SecurityUtils.getSubject();

// Single permission
if (subject.isPermitted("order:create")) { createOrder(); }

// All required
if (subject.isPermittedAll("order:read", "order:update")) { updateOrder(); }

// Any one sufficient
if (subject.isPermitted("order:delete") || subject.hasRole("admin")) { deleteOrder(); }

// Role check
if (subject.hasRole("admin")) { /* admin action */ }
if (subject.hasAllRoles("admin", "manager")) { /* ... */ }
  

Annotation-Based Authorization

  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.13.0</version>
</dependency>
  
  @RequiresAuthentication
public class OrderController {

    @RequiresPermissions("order:read")
    @GetMapping("/orders/{id}")
    public Order getOrder(@PathVariable Long id) { return orderService.findById(id); }

    @RequiresPermissions("order:create")
    @PostMapping("/orders")
    public Order createOrder(@RequestBody CreateOrderRequest request) {
        return orderService.create(request);
    }

    @RequiresRoles("admin")
    @DeleteMapping("/orders/{id}")
    public void deleteOrder(@PathVariable Long id) { orderService.delete(id); }

    @RequiresPermissions(value = {"order:read", "order:update"}, logical = Logical.AND)
    @PutMapping("/orders/{id}")
    public Order updateOrder(@PathVariable Long id, @RequestBody UpdateOrderRequest req) {
        return orderService.update(id, req);
    }
}
  

Enable AOP:

  @Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
    creator.setProxyTargetClass(true);
    return creator;
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAdvisor(SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;
}
  

URL-Based Authorization

In shiro.ini:

  [urls]
/admin/** = roles[admin]
/api/orders/** = perms[order:read]
/api/orders/create = perms[order:create]
/api/** = authc
/** = anon
  

Filter order matters — first match wins.

Dynamic Permission Resolution

  public class WildcardPermissionResolver implements PermissionResolver {
    @Override
    public Permission resolvePermission(String permissionString) {
        return new WildcardPermission(permissionString);
    }
}
  

Best Practices

  • Use permissions over roles for fine-grained access control
  • Apply @RequiresAuthentication at class level, specific permissions at method level
  • Define a consistent permission naming convention across the application
  • Cache authorization info in the Realm to avoid repeated DB lookups
  • Test authorization with Shiro’s Subject.Builder in unit tests