Apache Shiro

JavaSecurity FrameworkAuthenticationAuthorizationSession ManagementCryptographyEnterprise

Library

Apache Shiro

Overview

Apache Shiro is a powerful and easy-to-use security framework for Java applications. As of 2025, it continues to be chosen by many companies and projects due to its design philosophy of "simplicity" and "ease of understanding." It provides an integrated solution for the "four cornerstones of application security": authentication, authorization, cryptography, and session management, enabling the implementation of complex security requirements through intuitive APIs. Compared to Spring Security, it has a lower learning cost and is lightweight while being capable of supporting a wide range of applications from mobile apps to enterprise applications. Its POJO-based design allows immediate use in any Java container, providing flexibility independent of frameworks.

Details

Apache Shiro 2025 implements the latest security best practices in line with the evolution of the Java ecosystem. For authentication, it supports multiple providers (LDAP, database, file, custom), for authorization it provides fine-grained permission control and role-based access control (RBAC), for session management it supports clustering and custom session storage, and for cryptography it provides standard support for industry-standard algorithms and key management. It can also integrate with Servlet filters in web environments and run standalone in non-web environments, making it applicable in diverse environments such as microservices, web applications, desktop applications, and IoT systems. It supports both configuration-based and annotation-based approaches, allowing implementation according to developer preferences.

Key Features

  • Four Cornerstones Integration: Complete integrated solution for authentication, authorization, session management, and cryptography
  • Simple API: Intuitive and easy-to-understand programming interface
  • Flexible Architecture: POJO-based, container-independent design
  • Multi-Environment Support: Support for web, desktop, mobile, and IoT
  • Rich Authentication Providers: LDAP, database, external system integration
  • Advanced Session Management: Clustering, distributed sessions, custom storage

Pros and Cons

Pros

  • Low learning cost due to simple and intuitive API, enabling rapid development
  • POJO-based design with no framework dependencies, easy integration into existing systems
  • Lightweight with high performance, minimizing resource usage
  • Rich documentation and active community providing comprehensive support
  • Proven track record and stability in diverse environments including web, desktop, and mobile
  • Support for both configuration-based and annotation-based approaches, enabling flexible implementation

Cons

  • Limited support for latest OAuth2 and OpenID Connect compared to Spring Security
  • Additional implementation required for complex requirements in large enterprise environments
  • Development pace is somewhat slower compared to other Apache projects
  • Advanced security features (multi-factor authentication, adaptive authentication) require separate implementation
  • Lack of features specialized for modern microservice architectures
  • Cannot be used outside Java language/platform

Reference Pages

Code Examples

Basic Setup

<!-- Maven Dependencies -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.13.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.13.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.13.0</version>
</dependency>
# shiro.ini - Basic Configuration File
[main]
# User definition (actually retrieved from database or LDAP)
myRealm = com.example.MyRealm

# Session management configuration
sessionManager = org.apache.shiro.session.mgt.DefaultSessionManager
sessionManager.sessionIdCookie.httpOnly = true
sessionManager.sessionIdCookie.secure = true
sessionManager.globalSessionTimeout = 1800000

# Encryption configuration
credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName = SHA-256
credentialsMatcher.hashIterations = 100000
credentialsMatcher.storedCredentialsHexEncoded = true

myRealm.credentialsMatcher = $credentialsMatcher

securityManager.realms = $myRealm
securityManager.sessionManager = $sessionManager

[urls]
# URL-based access control
/login.html = anon
/logout = logout
/admin/** = authc, roles[admin]
/user/** = authc, roles[user]
/** = authc

Custom Realm Implementation

// MyRealm.java - Custom Authentication and Authorization Implementation
package com.example;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;

public class MyRealm extends AuthorizingRealm {
    private static final Logger logger = LoggerFactory.getLogger(MyRealm.class);
    
    private UserService userService;
    
    public MyRealm() {
        setName("MyRealm");
        setCredentialsMatcher(new HashedCredentialsMatcher("SHA-256"));
    }
    
    // Authentication processing
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
            throws AuthenticationException {
        
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }
        
        // Retrieve user information from database
        User user = userService.findByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("No account found for user [" + username + "]");
        }
        
        // Account status check
        if (user.isLocked()) {
            throw new LockedAccountException("Account [" + username + "] is locked.");
        }
        
        if (!user.isActive()) {
            throw new DisabledAccountException("Account [" + username + "] is disabled.");
        }
        
        // Return information for password hash verification
        return new SimpleAuthenticationInfo(
            user.getUsername(),
            user.getPasswordHash(),
            ByteSource.Util.bytes(user.getSalt()),
            getName()
        );
    }
    
    // Authorization processing
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) getAvailablePrincipal(principals);
        
        User user = userService.findByUsername(username);
        if (user == null) {
            return null;
        }
        
        SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
        
        // Role configuration
        Set<String> roles = userService.getUserRoles(username);
        authzInfo.setRoles(roles);
        
        // Permission configuration
        Set<String> permissions = userService.getUserPermissions(username);
        authzInfo.setStringPermissions(permissions);
        
        logger.debug("User [{}] has roles: {} and permissions: {}", 
                    username, roles, permissions);
        
        return authzInfo;
    }
    
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

// UserService.java - User Management Service
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RoleRepository roleRepository;
    
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    public Set<String> getUserRoles(String username) {
        User user = findByUsername(username);
        return user.getRoles().stream()
                  .map(Role::getName)
                  .collect(Collectors.toSet());
    }
    
    public Set<String> getUserPermissions(String username) {
        User user = findByUsername(username);
        return user.getRoles().stream()
                  .flatMap(role -> role.getPermissions().stream())
                  .map(Permission::getName)
                  .collect(Collectors.toSet());
    }
    
    public boolean createUser(String username, String password, Set<String> roles) {
        if (userRepository.existsByUsername(username)) {
            return false;
        }
        
        // Password hashing
        String salt = generateSalt();
        String hashedPassword = hashPassword(password, salt);
        
        User user = new User();
        user.setUsername(username);
        user.setPasswordHash(hashedPassword);
        user.setSalt(salt);
        user.setActive(true);
        user.setCreatedDate(new Date());
        
        // Role configuration
        Set<Role> userRoles = roles.stream()
                                  .map(roleRepository::findByName)
                                  .filter(Objects::nonNull)
                                  .collect(Collectors.toSet());
        user.setRoles(userRoles);
        
        userRepository.save(user);
        return true;
    }
    
    private String generateSalt() {
        return new SecureRandomNumberGenerator().nextBytes().toHex();
    }
    
    private String hashPassword(String password, String salt) {
        return new Sha256Hash(password, salt, 100000).toHex();
    }
}

Web Application Implementation

// ShiroConfig.java - Spring Boot Configuration
@Configuration
public class ShiroConfig {
    
    @Bean
    public MyRealm myRealm() {
        MyRealm realm = new MyRealm();
        realm.setUserService(userService());
        
        // Credentials matcher configuration
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA-256");
        matcher.setHashIterations(100000);
        matcher.setStoredCredentialsHexEncoded(true);
        realm.setCredentialsMatcher(matcher);
        
        // Cache configuration
        realm.setCachingEnabled(true);
        realm.setAuthenticationCachingEnabled(true);
        realm.setAuthorizationCachingEnabled(true);
        
        return realm;
    }
    
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }
    
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        
        // Session configuration
        sessionManager.setGlobalSessionTimeout(30 * 60 * 1000); // 30 minutes
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionValidationInterval(10 * 60 * 1000); // 10-minute intervals
        
        // Cookie configuration
        SimpleCookie sessionIdCookie = new SimpleCookie("JSESSIONID");
        sessionIdCookie.setHttpOnly(true);
        sessionIdCookie.setSecure(true); // HTTPS environment only
        sessionIdCookie.setMaxAge(-1); // Until browser close
        sessionManager.setSessionIdCookie(sessionIdCookie);
        
        return sessionManager;
    }
    
    @Bean
    public MemoryConstrainedCacheManager cacheManager() {
        return new MemoryConstrainedCacheManager();
    }
    
    @Bean
    public ShiroFilterFactoryBean shiroFilter() {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager());
        
        // Login page configuration
        shiroFilter.setLoginUrl("/login");
        shiroFilter.setSuccessUrl("/dashboard");
        shiroFilter.setUnauthorizedUrl("/unauthorized");
        
        // URL-based access control
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/register", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/api/public/**", "anon");
        
        filterChainDefinitionMap.put("/admin/**", "authc, roles[admin]");
        filterChainDefinitionMap.put("/api/admin/**", "authc, roles[admin]");
        filterChainDefinitionMap.put("/user/**", "authc, roles[user,admin]");
        filterChainDefinitionMap.put("/api/user/**", "authc, roles[user,admin]");
        
        filterChainDefinitionMap.put("/**", "authc");
        
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilter;
    }
    
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager());
        return advisor;
    }
    
    @Bean
    public UserService userService() {
        return new UserService();
    }
}

// LoginController.java - Authentication Controller
@RestController
@RequestMapping("/auth")
public class LoginController {
    
    @PostMapping("/login")
    public ResponseEntity<Map<String, Object>> login(
            @RequestBody LoginRequest loginRequest,
            HttpServletRequest request) {
        
        Subject currentUser = SecurityUtils.getSubject();
        Map<String, Object> response = new HashMap<>();
        
        if (currentUser.isAuthenticated()) {
            response.put("success", true);
            response.put("message", "Already logged in");
            response.put("user", getCurrentUserInfo(currentUser));
            return ResponseEntity.ok(response);
        }
        
        UsernamePasswordToken token = new UsernamePasswordToken(
            loginRequest.getUsername(), 
            loginRequest.getPassword()
        );
        token.setRememberMe(loginRequest.isRememberMe());
        
        try {
            currentUser.login(token);
            
            // Post-login processing
            response.put("success", true);
            response.put("message", "Login successful");
            response.put("user", getCurrentUserInfo(currentUser));
            response.put("sessionId", currentUser.getSession().getId());
            
            // Log login activity
            logLoginActivity(loginRequest.getUsername(), request.getRemoteAddr());
            
            return ResponseEntity.ok(response);
            
        } catch (UnknownAccountException uae) {
            response.put("success", false);
            response.put("message", "Invalid username or password");
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
            
        } catch (IncorrectCredentialsException ice) {
            response.put("success", false);
            response.put("message", "Invalid username or password");
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
            
        } catch (LockedAccountException lae) {
            response.put("success", false);
            response.put("message", "Account is locked");
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
            
        } catch (DisabledAccountException dae) {
            response.put("success", false);
            response.put("message", "Account is disabled");
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
            
        } catch (ExcessiveAttemptsException eae) {
            response.put("success", false);
            response.put("message", "Too many failed attempts");
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(response);
            
        } catch (AuthenticationException ae) {
            response.put("success", false);
            response.put("message", "Authentication failed");
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
        }
    }
    
    @PostMapping("/logout")
    public ResponseEntity<Map<String, Object>> logout() {
        Subject currentUser = SecurityUtils.getSubject();
        Map<String, Object> response = new HashMap<>();
        
        try {
            currentUser.logout();
            response.put("success", true);
            response.put("message", "Logout successful");
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            response.put("success", false);
            response.put("message", "Logout failed");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    
    private Map<String, Object> getCurrentUserInfo(Subject subject) {
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("username", subject.getPrincipal());
        userInfo.put("roles", subject.hasRole("admin") ? Arrays.asList("admin") : Arrays.asList("user"));
        userInfo.put("sessionId", subject.getSession().getId());
        userInfo.put("sessionTimeout", subject.getSession().getTimeout());
        return userInfo;
    }
    
    private void logLoginActivity(String username, String ipAddress) {
        // Log login history to database
        // Implementation omitted
    }
}

Annotation-based Authorization Control

// UserController.java - Annotation Authorization Example
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // Authentication required
    @RequiresAuthentication
    @GetMapping("/profile")
    public ResponseEntity<User> getProfile() {
        Subject currentUser = SecurityUtils.getSubject();
        String username = (String) currentUser.getPrincipal();
        User user = userService.findByUsername(username);
        return ResponseEntity.ok(user);
    }
    
    // User role required
    @RequiresRoles("user")
    @PutMapping("/profile")
    public ResponseEntity<Map<String, Object>> updateProfile(@RequestBody UserUpdateRequest request) {
        Subject currentUser = SecurityUtils.getSubject();
        String username = (String) currentUser.getPrincipal();
        
        boolean updated = userService.updateProfile(username, request);
        Map<String, Object> response = new HashMap<>();
        response.put("success", updated);
        
        return ResponseEntity.ok(response);
    }
    
    // Admin role required
    @RequiresRoles("admin")
    @GetMapping("/all")
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.findAllUsers();
        return ResponseEntity.ok(users);
    }
    
    // Specific permission required
    @RequiresPermissions("user:create")
    @PostMapping
    public ResponseEntity<Map<String, Object>> createUser(@RequestBody CreateUserRequest request) {
        boolean created = userService.createUser(
            request.getUsername(), 
            request.getPassword(), 
            request.getRoles()
        );
        
        Map<String, Object> response = new HashMap<>();
        response.put("success", created);
        
        return created ? 
            ResponseEntity.ok(response) : 
            ResponseEntity.badRequest().body(response);
    }
    
    // Multiple permission combination
    @RequiresPermissions({"user:read", "user:update"})
    @PutMapping("/{userId}")
    public ResponseEntity<Map<String, Object>> updateUser(
            @PathVariable Long userId, 
            @RequestBody UserUpdateRequest request) {
        
        boolean updated = userService.updateUser(userId, request);
        Map<String, Object> response = new HashMap<>();
        response.put("success", updated);
        
        return ResponseEntity.ok(response);
    }
    
    // Delete permission (dangerous operation)
    @RequiresPermissions("user:delete")
    @DeleteMapping("/{userId}")
    public ResponseEntity<Map<String, Object>> deleteUser(@PathVariable Long userId) {
        boolean deleted = userService.deleteUser(userId);
        Map<String, Object> response = new HashMap<>();
        response.put("success", deleted);
        
        return ResponseEntity.ok(response);
    }
}

Session Management and Security Enhancement

// SessionController.java - Session Management
@RestController
@RequestMapping("/api/session")
@RequiresAuthentication
public class SessionController {
    
    @GetMapping("/info")
    public ResponseEntity<Map<String, Object>> getSessionInfo() {
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession();
        
        Map<String, Object> sessionInfo = new HashMap<>();
        sessionInfo.put("sessionId", session.getId());
        sessionInfo.put("startTimestamp", session.getStartTimestamp());
        sessionInfo.put("lastAccessTime", session.getLastAccessTime());
        sessionInfo.put("timeout", session.getTimeout());
        sessionInfo.put("host", session.getHost());
        sessionInfo.put("attributeKeys", session.getAttributeKeys());
        
        return ResponseEntity.ok(sessionInfo);
    }
    
    @PostMapping("/extend")
    public ResponseEntity<Map<String, Object>> extendSession() {
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession();
        
        // Extend session (add 30 minutes)
        session.setTimeout(30 * 60 * 1000);
        
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("newTimeout", session.getTimeout());
        
        return ResponseEntity.ok(response);
    }
    
    @PostMapping("/invalidate")
    public ResponseEntity<Map<String, Object>> invalidateSession() {
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout(); // Invalidate session
        
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("message", "Session invalidated");
        
        return ResponseEntity.ok(response);
    }
}

// SecurityUtils.java - Security Utilities
public class SecurityUtilities {
    
    // Check if current user has specific role
    public static boolean hasRole(String role) {
        Subject currentUser = SecurityUtils.getSubject();
        return currentUser.hasRole(role);
    }
    
    // Check if current user has specific permission
    public static boolean hasPermission(String permission) {
        Subject currentUser = SecurityUtils.getSubject();
        return currentUser.isPermitted(permission);
    }
    
    // Get current username
    public static String getCurrentUsername() {
        Subject currentUser = SecurityUtils.getSubject();
        return (String) currentUser.getPrincipal();
    }
    
    // Set session attribute
    public static void setSessionAttribute(String key, Object value) {
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession();
        session.setAttribute(key, value);
    }
    
    // Get session attribute
    public static Object getSessionAttribute(String key) {
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession(false);
        return session != null ? session.getAttribute(key) : null;
    }
    
    // Require admin privileges
    public static void requireAdmin() {
        Subject currentUser = SecurityUtils.getSubject();
        if (!currentUser.hasRole("admin")) {
            throw new UnauthorizedException("Admin role required");
        }
    }
    
    // Allow access to self or admin only
    public static void requireSelfOrAdmin(String targetUsername) {
        Subject currentUser = SecurityUtils.getSubject();
        String currentUsername = (String) currentUser.getPrincipal();
        
        if (!currentUsername.equals(targetUsername) && !currentUser.hasRole("admin")) {
            throw new UnauthorizedException("Access denied");
        }
    }
}

Error Handling and Testing

// GlobalExceptionHandler.java - Unified Exception Handling
@ControllerAdvice
public class GlobalExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    @ExceptionHandler(UnauthenticatedException.class)
    public ResponseEntity<Map<String, Object>> handleUnauthenticated(UnauthenticatedException e) {
        Map<String, Object> response = new HashMap<>();
        response.put("error", "unauthenticated");
        response.put("message", "Authentication required");
        response.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
    }
    
    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<Map<String, Object>> handleUnauthorized(UnauthorizedException e) {
        Map<String, Object> response = new HashMap<>();
        response.put("error", "unauthorized");
        response.put("message", "Access denied");
        response.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
    }
    
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<Map<String, Object>> handleAuthenticationException(AuthenticationException e) {
        logger.warn("Authentication failed: {}", e.getMessage());
        
        Map<String, Object> response = new HashMap<>();
        response.put("error", "authentication_failed");
        response.put("message", "Authentication failed");
        response.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
    }
}

// ShiroTest.java - Unit Test Example
@ExtendWith(MockitoExtension.class)
class ShiroServiceTest {
    
    @Mock
    private UserService userService;
    
    @InjectMocks
    private MyRealm myRealm;
    
    @BeforeEach
    void setUp() {
        ThreadContext.bind(new DefaultSecurityManager(myRealm));
    }
    
    @AfterEach
    void tearDown() {
        ThreadContext.unbindSubject();
        ThreadContext.unbindSecurityManager();
    }
    
    @Test
    void testSuccessfulAuthentication() {
        // Prepare test user
        User testUser = new User();
        testUser.setUsername("testuser");
        testUser.setPasswordHash("hashedpassword");
        testUser.setSalt("salt");
        testUser.setActive(true);
        
        when(userService.findByUsername("testuser")).thenReturn(testUser);
        
        // Authentication test
        UsernamePasswordToken token = new UsernamePasswordToken("testuser", "password");
        Subject subject = SecurityUtils.getSubject();
        
        assertDoesNotThrow(() -> subject.login(token));
        assertTrue(subject.isAuthenticated());
        assertEquals("testuser", subject.getPrincipal());
    }
    
    @Test
    void testFailedAuthentication() {
        when(userService.findByUsername("invaliduser")).thenReturn(null);
        
        UsernamePasswordToken token = new UsernamePasswordToken("invaliduser", "password");
        Subject subject = SecurityUtils.getSubject();
        
        assertThrows(UnknownAccountException.class, () -> subject.login(token));
        assertFalse(subject.isAuthenticated());
    }
    
    @Test
    void testRoleBasedAuthorization() {
        // Authenticated user with configured roles
        Set<String> roles = Set.of("admin");
        when(userService.getUserRoles("admin")).thenReturn(roles);
        
        Subject subject = SecurityUtils.getSubject();
        // Simulate authenticated state
        
        assertTrue(subject.hasRole("admin"));
        assertFalse(subject.hasRole("user"));
    }
}