Information Security 18 min read

Understanding Apache Shiro Architecture, Realm Implementation, and Spring MVC Integration

This article explains Apache Shiro's security architecture, demonstrates how to create authentication tokens, configure realms, use caching and hashing for password protection, and integrate Shiro with Spring MVC through XML and Java code examples.

Top Architect
Top Architect
Top Architect
Understanding Apache Shiro Architecture, Realm Implementation, and Spring MVC Integration

To learn Shiro you must first understand its architecture; as a security framework it works without any container and can be used in Java SE or Java EE environments. The login process is illustrated with a user token example.

UsernamePasswordToken token = new UsernamePasswordToken(username, password);

The token represents the user's credentials, and Shiro validates the token against the configured security manager.

SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); subject.login(token);

Shiro's core component is SecurityManager , which handles authentication and authorization. The Subject object abstracts the current project (or session) that needs protection.

Two key objects are AuthenticationInfo (user role information) and AuthorizationInfo (role permission information). Realms provide these objects by querying the database.

The article introduces the cache mechanism using Ehcache. A sample ehcache.xml configuration is shown:

<?xml version="1.0" encoding="UTF-8"?> <ehcache name="shirocache"> <diskStore path="java.io.tmpdir" /> <cache name="passwordRetryCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="0" overflowToDisk="false" statistics="true" /> </ehcache>

Hashing is performed with MD5. The PasswordHelper class adds a random salt and hashes the password:

public class PasswordHelper { private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator(); private String algorithmName = "md5"; private final int hashIterations = 2; public void encryptPassword(User user) { user.setSalt(randomNumberGenerator.nextBytes().toHex()); String newPassword = new SimpleHash(algorithmName, user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex(); user.setPassword(newPassword); } }

A custom credentials matcher RetryLimitHashedCredentialsMatcher limits consecutive login failures using the cache:

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { private Cache passwordRetryCache; public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) { passwordRetryCache = cacheManager.getCache("passwordRetryCache"); } @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String username = (String) token.getPrincipal(); AtomicInteger retryCount = passwordRetryCache.get(username); if (retryCount == null) { retryCount = new AtomicInteger(0); passwordRetryCache.put(username, retryCount); } if (retryCount.incrementAndGet() > 5) { throw new ExcessiveAttemptsException(); } boolean match = super.doCredentialsMatch(token, info); if (match) { passwordRetryCache.remove(username); } return match; } }

The core UserRealm extends AuthorizingRealm and implements both authorization and authentication methods:

public class UserRealm extends AuthorizingRealm { private UserService userService = new UserServiceImpl(); @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set roles = userService.findRoles(username); Set roleNames = new HashSet<>(); for (Role r : roles) roleNames.add(r.getRole()); info.setRoles(roleNames); Set perms = userService.findPermissions(username); Set permNames = new HashSet<>(); for (Permission p : perms) permNames.add(p.getPermission()); info.setStringPermissions(permNames); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.findByUsername(username); if (user == null) throw new UnknownAccountException(); if (user.getLocked() == 0) throw new LockedAccountException(); return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName()); } }

The database must contain five tables: users, roles, permissions, user_role (junction), and role_permission (junction).

Shiro also manages sessions; each login creates a Shiro session that can be used instead of the servlet container's session.

Integration with Spring MVC requires configuring the filter in web.xml and defining Shiro beans in spring-shiro-web.xml . Important beans include the cache manager, credentials matcher, realm, security manager, and ShiroFilterFactoryBean with a filter chain such as:

/authc/admin = roles[admin] /authc/** = authc /** = anon

The login controller demonstrates how to create a token, perform subject.login(token) , handle exceptions, and store the user in the Shiro session:

@Controller public class LoginController { @Autowired private UserService userService; @RequestMapping("login") public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password) { UsernamePasswordToken token = new UsernamePasswordToken(username, password); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); } catch (IncorrectCredentialsException ice) { ModelAndView mv = new ModelAndView("error"); mv.addObject("message", "password error!"); return mv; } catch (UnknownAccountException uae) { ModelAndView mv = new ModelAndView("error"); mv.addObject("message", "username error!"); return mv; } catch (ExcessiveAttemptsException eae) { ModelAndView mv = new ModelAndView("error"); mv.addObject("message", "times error"); return mv; } User user = userService.findByUsername(username); subject.getSession().setAttribute("user", user); return new ModelAndView("success"); } }

An example AuthcController shows how to access the stored user from the Shiro session and protect endpoints with the filter chain definitions.

In summary, Shiro provides a comprehensive security solution with flexible realm implementation, caching, hashing, session management, and seamless Spring MVC integration, but mastering its configuration and code requires careful study.

JavasecurityAuthenticationSpring MVCauthorizationRealmEhcacheshiro
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.