Hi,

As a few of you may know, I posted many, many months ago about trying to get 
Multi Factor Authentication working on a Shiro-based app. I think I have a plan 
that isn't totally crazy and doesn't involve stupid levels of custom code or 
changing any of the core Shiro libs. However, I'd like it run it by you guys to 
see what you think and poke holes in it before I turn it into prod-ready code. 
Maybe there's something here that folks can adapt in the future if they need to 
do something similar.

I started off with a pre-existing config with a DefaultWebSessionManager and a 
JDBC realm reading username / password and role info from a PG DB. Apart from a 
non-standard session manager and a non-standard cookie name, it's fairly 
bog-standard stuff.

What I'm doing now is to split the JDBC logic into two realms. The first is the 
same password stuff:

passwordJdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
passwordJdbcRealm.permissionsLookupEnabled = true
passwordJdbcRealm.authenticationQuery = select password from users where ...
passwordJdbcRealm.userRolesQuery = select ...
passwordJdbcRealm.dataSource = $dataSource
passwordJdbcRealm.credentialsMatcher = $passwordMatcher
passwordJdbcRealm.permissionsQuery = select ...

The second is for accessing one-time authentication tokens:

tokenJdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
tokenJdbcRealm.permissionsLookupEnabled = false
tokenJdbcRealm.authenticationQuery = select token from auth_tokens where ...
tokenJdbcRealm.userRolesQuery = select null where ? is not null and false (must 
be a neater way just to disable role lookups but this works)
tokenJdbcRealm.dataSource = $dataSource
tokenJdbcRealm.credentialsMatcher = $tokenMatcher

Both of these are now bound to the same old security manager:

securityManager.realms = $passwordJdbcRealm, $tokenJdbcRealm

I've now also got two form filters - one for the password and a new one for the 
one-time token:
passwordFormAuth =com.voxsmart.PasswordFormAuthenticationFilter
passwordFormAuth.loginUrl = /login.jsp

tokenFormAuth = com.voxsmart....TokenFormAuthenticationFilter
tokenFormAuth.loginUrl = /login2.jsp

login2.jsp redirects to login.jsp if the user isn't authenticated.

These form filters are wired up like so:

/login.jsp = passwordFormAuth
/login2.jsp = tokenFormAuth
/logout = logout
/** = tokenFormAuth

The first is basic except for sending out a token if the login succeeds:
@Override
public boolean onLoginSuccess(AuthenticationToken token, Subject subject, 
ServletRequest request, ServletResponse response) throws Exception {
                                // Generate a one-time token, write it to the 
DB and send it to the user via SMS / e-mail / carrier pigeon
}

The second checks that an MFA attribute is set on the session in order to allow 
access and sets it if / only if the token match is found:

private boolean isMfaOk(Object mfaStatus) {
                               return "ALLOWED".equals(mfaStatus) || 
"NOT_NEEDED".equals(mfaStatus);
                }

@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse 
response, Object mappedValue) {
Object mfaStatus = subject.getSession().getAttribute("MFA-STATUS");
return (subject.isAuthenticated() && isMfaOk(mfaStatus)) || // Additional check 
for MFA
(!isLoginRequest(request, response) && isPermissive(mappedValue)); // 
Copy-n-paste from AuthenticatingFilter
                }

@Override
                public boolean onLoginSuccess(AuthenticationToken token, 
Subject subject, ServletRequest request, ServletResponse response) throws 
Exception {
boolean ret = super.onLoginSuccess(token, subject, request, response);
subject.getSession().setAttribute("MFA-STATUS", "ALLOWED");
// write some audit log entries and other fluff
return ret;
}

Does this seem sane? Is there a better way to do it? Is there something I'm 
missing or forgotten that will cause this to blow up in my face at some later 
point in time? Is there something that could be cribbed and added to Shiro to 
make it easier in the future?

Regards,

Richard

Reply via email to