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

Reply via email to