Understanding Apache Shiro: Authentication, Authorization, and Custom Realms

Apache Shiro is a lightweight yet powerful Java security framework that handles authentication, authorization, session management, cryptography, and more. Unlike Spring Security, Shiro is modular, easy to integrate, and does not require Spring.

Core Components

  • Subject: Represents the current user (or system entity) interacting with the application. All security operations are initiated through the Subject.
  • SecurityManager: The central coordinator in Shiro. It manages Subjects and coordinates with other internal components like Authenticator and Authorizer.
  • Realm: Acts as a bridge to your data source (e.g., database, INI file). Shiro uses Realms to fetch user credentials, roles, and permissions during authentication and authorization.

Basic Atuhentication Flow

When subject.login(token) is called:

  1. The request is delegated to the SecurityManager.
  2. The SecurityManager uses an Authenticator to validate credentials.
  3. The Authenticator queries one or more Realms to retrieve stored account data.
  4. Credentials are compared using a configured CredentialsMatcher.

Example 1: Simple Authentication with SimpleAccountRealm

public class BasicAuthTest {
    private SimpleAccountRealm realm = new SimpleAccountRealm();

    @BeforeEach
    void setup() {
        realm.addAccount("alice", "secret", "admin");
    }

    @Test
    void testAuthenticationAndAuthorization() {
        DefaultSecurityManager sm = new DefaultSecurityManager();
        sm.setRealm(realm);
        SecurityUtils.setSecurityManager(sm);

        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("alice", "secret");

        currentUser.login(token);
        assert currentUser.isAuthenticated();
        currentUser.checkRole("admin");
    }
}

Example 2: Using IniRealm with Configuration File

Create src/main/resources/shiro-users.ini:

[users]
alice=secret,admin

[roles]
admin=user:create,user:read

Java test code:

@Test
void testIniRealm() {
    IniRealm realm = new IniRealm("classpath:shiro-users.ini");

    DefaultSecurityManager sm = new DefaultSecurityManager();
    sm.setRealm(realm);
    SecurityUtils.setSecurityManager(sm);

    Subject subject = SecurityUtils.getSubject();
    subject.login(new UsernamePasswordToken("alice", "secret"));

    assertTrue(subject.isAuthenticated());
    subject.checkRole("admin");
    subject.checkPermission("user:read");
}

Example 3: Database-Backed Authentication with JdbcRealm

@Test
void testJdbcRealm() {
    DruidDataSource ds = new DruidDataSource();
    ds.setUrl("jdbc:mysql://localhost:3306/appdb");
    ds.setUsername("user");
    ds.setPassword("pass");

    JdbcRealm realm = new JdbcRealm();
    realm.setDataSource(ds);
    realm.setAuthenticationQuery("SELECT password FROM users WHERE username = ?");
    realm.setUserRolesQuery("SELECT role_name FROM user_roles WHERE username = ?");

    DefaultSecurityManager sm = new DefaultSecurityManager();
    sm.setRealm(realm);
    SecurityUtils.setSecurityManager(sm);

    Subject subject = SecurityUtils.getSubject();
    subject.login(new UsernamePasswordToken("bob", "password123"));
    assertTrue(subject.isAuthenticated());
    subject.checkRole("editor");
}

Example 4: Custom Realm with Salted Hashing

A custom realm implements both authentication and authorization logic:

public class CustomRealm extends AuthorizingRealm {

    private final Map<String, String> userStore = new HashMap<>();
    private final Set<String> roles = Set.of("admin", "user");
    private final Set<String> permissions = Set.of("user:*", "admin:view");

    public CustomRealm() {
        // Pre-hash password with salt
        String saltedHash = new Md5Hash("mypassword", "mySalt").toHex();
        userStore.put("charlie", saltedHash);
        setName("CustomRealm");
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String username = (String) token.getPrincipal();
        String storedHash = userStore.get(username);
        if (storedHash == null) return null;

        return new SimpleAuthenticationInfo(
            username,
            storedHash,
            ByteSource.Util.bytes("mySalt"),
            getName()
        );
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roles);
        info.setStringPermissions(permissions);
        return info;
    }
}

Test with hashed credentials matcher:

@Test
void testCustomRealmWithHashing() {
    CustomRealm realm = new CustomRealm();
    HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("md5");
    matcher.setHashIterations(1);
    realm.setCredentialsMatcher(matcher);

    DefaultSecurityManager sm = new DefaultSecurityManager();
    sm.setRealm(realm);
    SecurityUtils.setSecurityManager(sm);

    Subject subject = SecurityUtils.getSubject();
    subject.login(new UsernamePasswordToken("charlie", "mypassword"));

    assertTrue(subject.isAuthenticated());
    subject.checkRole("admin");
    subject.checkPermission("admin:view");
}

In this setup, the plaintext password "mypassword" is combined with the salt "mySalt", hashed using MD5, and compared against the stored hash. The salt ensures resistance against rainbow table attacks.

Tags: Apache Shiro Java Security Authentication Authorization Realm

Posted on Sat, 10 Oct 2026 16:19:28 +0000 by HEAD