title: "How to Monitor Apache Shiro Security Framework Health with Vigilmon" published: false description: Apache Shiro authentication failures, session store outages, and realm connectivity issues are silent until users report login errors. Learn how to monitor Shiro session health, realm availability, and security gate liveness with Vigilmon HTTP probes and heartbeat monitors. tags: shiro, security, authentication, monitoring, java, devops cover_image:
Apache Shiro is a powerful Java security framework that handles authentication, authorization, cryptography, and session management for web and standalone applications. Shiro fronts every protected request — when the session store is unreachable, all authenticated users are immediately logged out; when a realm fails, login attempts silently return "incorrect credentials" even for valid users; when the SecurityManager is misconfigured after a deployment, every protected route returns 401. These are production incidents disguised as authentication errors.
Vigilmon gives you external visibility into Shiro's security layer through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.
Why Shiro Needs External Monitoring
Shiro's failure modes are subtle and high-impact:
- Realm failure masquerading as auth failure: if an LDAP or JDBC realm's backend is unreachable, Shiro throws an
AuthenticationExceptionthat is indistinguishable from a wrong-password error — users see a login failure, not a service outage - Session store disconnects: Shiro's default in-memory session manager survives pod restarts poorly; distributed session stores (Redis, Ehcache, Hazelcast) can disconnect silently and cause
UnknownSessionExceptionon every authenticated request - Cache backend failures: Shiro caches authentication and authorization info — a cache backend failure forces every request to hit the realm directly, degrading latency and potentially overloading an LDAP server
- Crypto key misconfiguration: Shiro uses
RememberMecookies encrypted with a configured secret key; a key rotation that forgets to redeploy all nodes causesCryptoExceptionon every cross-node request - Filter chain misconfigurations: a bad Shiro filter chain can silently open protected routes to unauthenticated users after a deployment — the application returns 200 for routes that should return 401
External monitoring with Vigilmon adds:
- Proactive alerting when realm health endpoints report connectivity failures
- Session store availability monitoring independent of application logic
- Heartbeat monitoring so you know when authentication flows stop completing successfully
- Multi-region probe consensus that filters transient blips from genuine security layer failures
Step 1: Build a Shiro Health Endpoint
Shiro does not ship with a built-in health endpoint. Add one via Spring Boot Actuator, a custom controller, or a lightweight sidecar.
Spring Boot Actuator Health Indicator
// ShiroHealthIndicator.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class ShiroHealthIndicator implements HealthIndicator {
private final SessionDAO sessionDAO;
public ShiroHealthIndicator(SessionDAO sessionDAO) {
this.sessionDAO = sessionDAO;
}
@Override
public Health health() {
try {
SecurityManager sm = SecurityUtils.getSecurityManager();
if (sm == null) {
return Health.down()
.withDetail("reason", "security_manager_null")
.build();
}
long activeSessions = sessionDAO.getActiveSessions().size();
return Health.up()
.withDetail("security_manager", sm.getClass().getSimpleName())
.withDetail("active_sessions", activeSessions)
.build();
} catch (Exception e) {
return Health.down()
.withDetail("reason", "session_store_unavailable")
.withDetail("error", e.getMessage())
.build();
}
}
}
This exposes /actuator/health/shiro automatically via Spring Boot Actuator.
Custom Realm Connectivity Check
// ShiroHealthController.java
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.springframework.web.bind.annotation.*;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.*;
@RestController
@RequestMapping("/health")
public class ShiroHealthController {
private final Collection<Realm> realms;
public ShiroHealthController(Collection<Realm> realms) {
this.realms = realms;
}
@GetMapping("/shiro")
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> response = new LinkedHashMap<>();
List<Map<String, Object>> realmStatus = new ArrayList<>();
boolean anyFailed = false;
for (Realm realm : realms) {
Map<String, Object> status = new LinkedHashMap<>();
status.put("name", realm.getName());
status.put("type", realm.getClass().getSimpleName());
try {
if (realm instanceof JdbcRealm jdbcRealm) {
DataSource ds = getDataSource(jdbcRealm);
try (Connection conn = ds.getConnection()) {
conn.isValid(2);
status.put("status", "ok");
}
} else {
status.put("status", "ok");
}
} catch (Exception e) {
status.put("status", "down");
status.put("error", e.getMessage());
anyFailed = true;
}
realmStatus.add(status);
}
response.put("realms", realmStatus);
response.put("status", anyFailed ? "degraded" : "ok");
return anyFailed
? ResponseEntity.status(503).body(response)
: ResponseEntity.ok(response);
}
private DataSource getDataSource(JdbcRealm realm) throws Exception {
var field = JdbcRealm.class.getDeclaredField("dataSource");
field.setAccessible(true);
return (DataSource) field.get(realm);
}
}
Node.js Sidecar
// health/shiro.js
const express = require('express');
const http = require('http');
const app = express();
const APP_HOST = process.env.APP_HOST || 'localhost';
const APP_PORT = parseInt(process.env.APP_PORT || '8080');
const HEALTH_PATH = process.env.SHIRO_HEALTH_PATH || '/actuator/health/shiro';
function probeActuator() {
return new Promise((resolve, reject) => {
const req = http.get(
{ host: APP_HOST, port: APP_PORT, path: HEALTH_PATH, timeout: 5000 },
res => {
let body = '';
res.on('data', chunk => { body += chunk; });
res.on('end', () => resolve({ status: res.statusCode, body }));
}
);
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
});
}
app.get('/health/shiro', async (req, res) => {
try {
const result = await probeActuator();
const up = result.status === 200 &&
(result.body.includes('"status":"UP"') || result.body.includes('"status":"ok"'));
if (!up) {
return res.status(503).json({
status: 'down',
actuator_status: result.status,
body: result.body.substring(0, 500),
});
}
return res.status(200).json({ status: 'ok' });
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
app.listen(3011);
Python Health Sidecar
import os, requests
from flask import Flask, jsonify
app = Flask(__name__)
APP_BASE = f"http://{os.environ.get('APP_HOST', 'localhost')}:{os.environ.get('APP_PORT', '8080')}"
HEALTH_PATH = os.environ.get('SHIRO_HEALTH_PATH', '/actuator/health/shiro')
@app.route('/health/shiro')
def health():
try:
resp = requests.get(f'{APP_BASE}{HEALTH_PATH}', timeout=5)
if resp.status_code != 200:
return jsonify({'status': 'down', 'actuator_status': resp.status_code}), 503
data = resp.json()
if data.get('status') not in ('UP', 'ok'):
return jsonify({'status': 'degraded', 'detail': data}), 503
return jsonify({'status': 'ok',
'active_sessions': data.get('details', {}).get('active_sessions', 'unknown')})
except Exception as e:
return jsonify({'status': 'down', 'error': str(e)}), 503
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3011)
Step 2: Configure Vigilmon HTTP Monitor for Shiro
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Shiro health endpoint:
https://your-app.example.com/actuator/health/shiro - Set the check interval to 1 minute
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"UP"(Actuator) or"status":"ok"(custom endpoint) - Response time threshold:
3000ms
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a second monitor for the realm connectivity check:
- URL:
https://your-app.example.com/health/shiro - Expected:
200, body contains:"status":"ok" - Interval: 2 minutes
- Alert channel: P1 pager — realm failure means no users can authenticate
Vigilmon's multi-region probe consensus filters transient network blips from genuine Shiro security layer failures.
Step 3: Heartbeat Monitoring for Authentication Flow Success
Health endpoint monitoring catches Shiro subsystem failures, but not end-to-end authentication flow problems. A Shiro realm can return "healthy" while a misconfigured filter chain silently bypasses authentication, or while rememberMe token validation fails for every returning user.
Vigilmon heartbeat monitors catch these: your application pings Vigilmon after each successful authentication event.
Set Up the Heartbeat Monitor
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
shiro-auth-flow - Set the expected interval: 10 minutes
- Set the grace period: 20 minutes
- Save — copy the heartbeat URL, e.g.
https://vigilmon.online/heartbeat/abc123xyz
Wire It Into a Shiro AuthenticationListener (Spring Boot)
// VigilmonAuthListener.java
import org.apache.shiro.authc.*;
import org.springframework.stereotype.Component;
import java.net.http.*;
import java.net.URI;
import java.util.concurrent.atomic.*;
@Component
public class VigilmonAuthListener implements AuthenticationListener {
private final HttpClient http = HttpClient.newHttpClient();
private final String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");
private final AtomicLong lastPingMs = new AtomicLong(0);
private static final long PING_INTERVAL_MS = 300_000;
@Override
public void onSuccess(AuthenticationToken token, AuthenticationInfo info) {
long now = System.currentTimeMillis();
if (now - lastPingMs.get() > PING_INTERVAL_MS) {
lastPingMs.set(now);
pingHeartbeat();
}
}
@Override
public void onFailure(AuthenticationToken token, AuthenticationException ae) {
// Suppress heartbeat on realm backend failures, not user errors
if (!(ae instanceof IncorrectCredentialsException)
&& !(ae instanceof UnknownAccountException)) {
// Realm backend failure — let heartbeat lapse naturally
}
}
@Override
public void onLogout(PrincipalCollection principals) {}
private void pingHeartbeat() {
if (heartbeatUrl == null) return;
try {
http.sendAsync(
HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
HttpResponse.BodyHandlers.discarding()
);
} catch (Exception ignored) {}
}
}
Register the listener in your Shiro configuration:
// ShiroConfig.java
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager securityManager(
Collection<Realm> realms,
VigilmonAuthListener authListener) {
DefaultWebSecurityManager sm = new DefaultWebSecurityManager(realms);
ModularRealmAuthenticator auth = new ModularRealmAuthenticator();
auth.setAuthenticationListeners(List.of(authListener));
sm.setAuthenticator(auth);
return sm;
}
}
Step 4: Alert Routing for Shiro Failures
Shiro failures range from a single realm being unreachable to the SecurityManager being null after a failed deployment. Route alerts by severity:
| Monitor | Alert Channel | Priority |
|---|---|---|
| HTTP: /actuator/health/shiro | Slack + PagerDuty | P1 |
| HTTP: /health/shiro realm check | Slack + PagerDuty | P1 |
| Heartbeat: auth flow success | Slack + PagerDuty | P1 |
| Heartbeat: session cleanup job | Slack + email | P2 |
Set response time thresholds as early warning signals:
- Alert at
500msfor the Shiro health endpoint (slow responses indicate session store latency) - Alert at
2000msfor the realm connectivity check (LDAP/JDBC slow responses precede full failure) - Alert at
30 secondsgap in auth flow heartbeat during peak hours
For production applications, set up a status page in Vigilmon showing Shiro session health and realm availability — this lets on-call engineers identify security layer failures instantly rather than inferring them from user reports of login errors.
Summary
Shiro failures present as authentication errors, not infrastructure failures — users see "invalid credentials" while the real cause is an LDAP timeout or a session store disconnect. External monitoring catches these before they become an incident:
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on /actuator/health/shiro | SecurityManager state, session store connectivity |
| HTTP monitor on /health/shiro | Individual realm connectivity (JDBC, LDAP, custom) |
| Heartbeat monitor (auth flow) | End-to-end authentication success |
| Heartbeat monitor (session cleanup) | Background session management liveness |
Get started free at vigilmon.online — your first Shiro security monitor is running in under two minutes.