Interesting. We were looking into SMS, too but I don’t find it user friendly at all. You rely on a third party. SMS often don’t arrive within seconds and there is an additional cost in every sms you send.
I don’t get your difference in workflows. AllSuccessfulStrategy says all realms have to authenticate. Thats why we needed to create a AuthenticationToken that works on both realms. It has the username and password and the challenge from the token generator. First page collects username and password, second page creates token. Then submit them all. You can’t generate the token in advance because you need to find the matching user first. Have a look at U2F! It is a rather cheap hardware token generator. You can get one for less than 20 EUR. Use it on many sites and there are no additional license costs. Only downside is it works currently only Chrome and Firefox (with an extension). https://www.yubico.com/about/background/fido/ <https://www.yubico.com/about/background/fido/> http://www.howtogeek.com/232314/u2f-explained-how-google-microsoft-and-others-are-creating-universal-two-factor-authentication-tokens/ <http://www.howtogeek.com/232314/u2f-explained-how-google-microsoft-and-others-are-creating-universal-two-factor-authentication-tokens/> https://fidoalliance.org/specifications/overview/ <https://fidoalliance.org/specifications/overview/> > On 9 Jan 2017, at 01:43, Richard Wheeldon <[email protected]> > wrote: > > I think your idea seems equally reasonable. I considered something similar. > However, we’re using SMS as the 2nd factor, which is imperfect from a > security point of view but much better from a distribution and usability > point of view. I’m guessing you have some sort of token generator for the 2nd > factor. The key point being that there’s a difference in workflow: > User supplies username + password -> System sends token to > user -> User inputs token > vs. > User generates token -> User supplies username, password and > token > > Richard > > From: Björn Raupach [mailto:[email protected]] > Sent: Sunday, January 8, 2017 12:02 PM > To: [email protected] > Subject: Re: MFA - Possible Solution? > > 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] > <mailto:[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
