Hi Richard, I am currently working on an implementation for U2F with Apache Shiro. It is still not officially rolled out but it works and I am also not sure if I did it correct.
Like you I created two realms. Then however I changed the authentication strategy in the security manager. authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy securityManager.authenticator.authenticationStrategy = $authcStrategy securityManager.realms = $jdbcRealm, $u2fRealm I used U2FAuthenticationToken that extends UsernamePasswordToken that works on both realms. The custom U2FRealm overrides supports and checks that the token is an instance of U2FAuthenticationToken. > On 6 Jan 2017, at 19:27, Richard Wheeldon <[email protected]> > wrote: > > 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
