Apache Shiro
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"));
}
}