1
votes

I am implementing an online platform using Java Restful Jersey with Apache Shiro for Authentication Authorization. My security implementation was based on article JSON Web Token with Apache Shiro. Following is my shiro.ini and implemented Classes.

shiro.ini

[main]
jwtg = gr.histopath.platform.lib.JWTGuard
jwtv =  gr.histopath.platform.lib.JWTVerifyingFilter

ds = com.mysql.cj.jdbc.MysqlDataSource
ds.serverName = 127.0.0.1
ds.port = 3306
ds.user = histopathUser
ds.password = H1s+0p@+h.U$er
ds.databaseName = histopath

jdbcRealm = gr.histopath.platform.lib.MyRealm
jdbcRealm.dataSource = $ds


credentialsMatcher = org.apache.shiro.authc.credential.Sha512CredentialsMatcher
credentialsMatcher.hashIterations = 50000
credentialsMatcher.hashSalted = true
credentialsMatcher.storedCredentialsHexEncoded = false
jdbcRealm.credentialsMatcher = $credentialsMatcher

jdbcRealm.permissionsLookupEnabled = false

shiro.loginUrl = /authentication/login

#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.globalSessionTimeout = 172800000

# ssl.enabled = false

securityManager.realms = $jdbcRealm

[users]

[roles]

[urls]

/authentication/login = authc
# /authentication/logout = logout

/search/* = noSessionCreation, jwtv
/statistics/* = noSessionCreation, jwtv
/clinics/* = noSessionCreation, jwtv
/patients/* = noSessionCreation, jwtv
/incidents/* = noSessionCreation, jwtv
/doctors/* = noSessionCreation, jwtv

/users/new = noSessionCreation, anon
/users/details/* = noSessionCreation, anon
/users/* = noSessionCreation, jwtv

/* = anon

MyRealm.java

package gr.histopath.platform.lib;

import gr.histopath.platform.model.DAO.UserDAO;
import gr.histopath.platform.model.TransferObjects.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;

    public class  MyRealm extends JdbcRealm {

        private UserDAO userDAO;
        private User user;
        private String password;
        private ByteSource salt;


        public MyRealm() {
            this.userDAO = new UserDAO();
            setSaltStyle(SaltStyle.COLUMN);
        }

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // identify account to log to
            UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
            String username = userPassToken.getUsername();

            System.out.println("GMOTO: " + userPassToken.getUsername());

            if (username.equals(null)) {
                System.out.println("Username is null.");
                return null;
            }

            // read password hash and salt from db
    //        System.out.println("Username: " + username);

            if(!userDAO.isOpen()){
                userDAO = new UserDAO();
            }

            this.user = userDAO.getByUsername(username);
            this.userDAO.closeEntityManager();
            System.out.println("user's email: " + this.user.getUsername());

            if (this.user == null) {
                System.out.println("No account found for user [" + username + "]");
                return null;
            }
            this.password = this.user.getPassword();
            this.salt = ByteSource.Util.bytes(Base64.decode(this.user.getSalt()));

            SaltedAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, salt, getName());

            return info;
        }

    }

MY JWT Verifying Filter:

package gr.histopath.platform.lib;

import gr.histopath.platform.model.TransferObjects.User;
import io.jsonwebtoken.*;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;

public class JWTVerifyingFilter extends AccessControlFilter {

    private static final Logger logger = LoggerFactory.getLogger(JWTVerifyingFilter.class);

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
        logger.debug("Verifying Filter Execution");

        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        String jwt = httpRequest.getHeader("Authorization");
        logger.debug("JWT Found");

        if (jwt == null || !jwt.startsWith("Bearer ")) {
//            System.out.println("DEn  Brika Tipota: ");
            logger.debug("No Token Found...");
//            servletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        jwt = jwt.substring(jwt.indexOf(" "));
        Subject subject = SecurityUtils.getSubject();

//        System.out.println("Token Found");
//        System.out.println("JWT: " + jwt);
//        System.out.println("Authenticated? " + subject.isAuthenticated());
//        System.out.println(" session " + subject.getSession().getId());
//        System.out.println(" salt " + ((User) subject.getPrincipal()).getSalt());
//        System.out.println(" who-is " + ((User) subject.getPrincipal()).getUsername());

        User user = null;
        if (subject.isAuthenticated()) {

            user = (User) subject.getPrincipal();
            String username = null;


            try {
                Jws<Claims> claimsJws = Jwts.parser()
                        .setSigningKey(DatatypeConverter.parseBase64Binary(user.getSalt()))
                        .parseClaimsJws(jwt);

//                System.out.println("Claims: " + claimsJws);
                logger.debug("Expiration: " + claimsJws.getBody().getExpiration());
                username = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(user.getSalt()))
                        .parseClaimsJws(jwt).getBody().getSubject();
            } catch (ExpiredJwtException expiredException) {
                logger.debug("Token Is Expired....");
                logger.debug(expiredException.getMessage(), expiredException);
//                System.out.println("Token IS Expired.....");
//                expiredException.printStackTrace();
                logger.debug("Logging out the user...");
//                System.out.println("Logging out the user...");
                SecurityUtils.getSubject().logout();
//                System.out.println("mmmnnnnn: " + SecurityUtils.getSubject().isAuthenticated());
                return false;
//                throw expiredException;
            } catch (SignatureException signatureException) {
                logger.debug(signatureException.getMessage(), signatureException);
//                signatureException.printStackTrace();
                return false;
            } catch (Exception e) {
                logger.debug(e.getMessage(), e);
//                e.printStackTrace();
                return false;
            }
//            System.out.println("Subject: " + user.getUsername());

            return username.equals(user.getUsername());

        }
//        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        return false;
    }
}

And JWT Guard

package gr.histopath.platform.lib;

import org.apache.shiro.web.filter.authc.AuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

public class JWTGuard extends AuthenticationFilter {
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
//        System.out.println("JWT GUARD FIRED!!!!!");
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
    }
}

Everything was working almost fine, except that randomly/occasionally, despite the user was logged in, a session timeout occurred and the system logged out the user despite the fact, the token has 7 day expiration period.

So i decided to try and make the system stateless without any sessions. To do so i used the command:

securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false

according to Disabling Subject State Session Storage

However, now i can't login at all. I get

java.lang.NullPointerException  at gr.histopath.platform.lib.MyRealm.doGetAuthenticationInfo(MyRealm.java:31)

i.e. String username = userPassToken.getUsername(); //This is null

Now my shiri.ini looks as following:

Changed shiro.ini

[main]
jwtg = gr.histopath.platform.lib.JWTGuard
jwtv =  gr.histopath.platform.lib.JWTVerifyingFilter

ds = com.mysql.cj.jdbc.MysqlDataSource
ds.serverName = 127.0.0.1
ds.port = 3306
ds.user = histopathUser
ds.password = H1s+0p@+h.U$er
ds.databaseName = histopath

jdbcRealm = gr.histopath.platform.lib.MyRealm
jdbcRealm.dataSource = $ds


credentialsMatcher = org.apache.shiro.authc.credential.Sha512CredentialsMatcher
credentialsMatcher.hashIterations = 50000
credentialsMatcher.hashSalted = true
credentialsMatcher.storedCredentialsHexEncoded = false
jdbcRealm.credentialsMatcher = $credentialsMatcher

jdbcRealm.permissionsLookupEnabled = false

shiro.loginUrl = /authentication/login

#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager

#sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
#securityManager.sessionManager = $sessionManager
#securityManager.sessionManager.globalSessionTimeout = 172800000

securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false

# ssl.enabled = false

securityManager.realms = $jdbcRealm

[users]

[roles]

[urls]

/authentication/login = authc
# /authentication/logout = logout

/search/* = noSessionCreation, jwtv
/statistics/* = noSessionCreation, jwtv
/clinics/* = noSessionCreation, jwtv
/patients/* = noSessionCreation, jwtv
/incidents/* = noSessionCreation, jwtv
/doctors/* = noSessionCreation, jwtv

/users/new = noSessionCreation, anon
/users/details/* = noSessionCreation, anon
/users/* = noSessionCreation, jwtv

/* = anon

I haven't found any complete example of session less shiro. Any suggestions for my code to make it work?? I must be missing something but i don't know what.

  • Why after disabling session MyRealm cannot read username from UsernamePasswordToken??
  • Why in my first implementation a Session Timeout occasionally occurred. Any ideas on this??
1

1 Answers

0
votes

Have you tried the noSessionCreation?

Do you any code (or calling any code) that is requesting a session?