当前位置: 首页 > 知识库问答 >
问题:

Apache Shiro无状态-无会话JWT令牌身份验证问题

楚乐逸
2023-03-14
[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
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;
        }

    }
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;
    }
}
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;
    }
}

一切工作都很正常,只是随机/偶尔地,尽管用户登录了,会话超时发生,系统注销了用户,尽管令牌有7天的有效期。

所以我决定尝试使系统没有任何会话而无状态。为此,我使用了以下命令:

securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false

根据禁用主题状态会话存储

然而,现在我根本无法登录。我得到了

java.lang.NullPointerException  at gr.histopath.platform.lib.MyRealm.doGetAuthenticationInfo(MyRealm.java:31)
[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

我还没有找到任何完整的会话少四郎的例子。对我的代码有什么建议让它工作吗??我一定是错过了什么,但我不知道是什么。

  • 为什么禁用会话后MyRealm无法从UsernamePasswordToken读取用户名??
  • 为什么在我的第一个实现中偶尔会出现会话超时。对此有什么想法吗??

暂时还没有答案

 类似资料:
  • 我正在构建一个API,并试图在多个上下文中解决身份验证问题。 API需要为我们创建和部署的客户端应用程序提供服务,并使用密码处理经过身份验证的请求。在每个请求中都发送密码并不是一个好主意,因此首先点击登录endpoint并获得会话ID更有意义。问题中的webapp是用AngularJS编写的,应该在localStorage中跟踪自己的会话,以减轻会话劫持并消除对cookie的依赖以跟踪会话。 we

  • 当应用程序依赖于无状态身份验证(使用类似HMAC的东西)时,是否有必要使用CSRF保护? 示例: > 我们有一个单页应用程序(否则我们必须在每个链接上添加令牌:。 此内标识将用于访问诸如之类的受限URL。 令牌将始终在HTTP头内传输。 没有Http会话,也没有cookie。

  • null 一些重要注意事项: 我们有传输安全性(HTTPS及其最好的朋友); 在窗帘后面,web应用程序代表当前用户将大量操作委托给外部服务(这些系统确实将Bob识别为其用户之一)--这意味着我们必须将Bob的凭据转发给它们。 现在,我们如何(在每个请求上)对Bob进行身份验证?哪一种方法会是实施这样的事情的合理方法呢? null null 非常感谢您花时间阅读以下内容:)

  • 我试图理解为什么JWT认证是无状态的。在有状态认证中,会有一个会话id。这里有一张JWT的代币,上面有签名。所以身份验证服务器发布JWT令牌,但是我可以说后续请求中JWT令牌的验证是由endpoint服务器(应用服务器)而不是身份验证服务器来完成的吗?我相信这是可能的,因为JWT是用截止日期(还有一些其他信息)签名的,并且认证服务器的公共证书对所有endpoint服务器都是可用的。 因此,认证服务

  • 我已经实现了一个无状态服务器,它使用JWT让用户访问api。我关心的是安全问题。之前也有同样的问题,但没有得到很好的回答:JWT、无状态身份验证和安全性 问题:使用一些因素,如JWE(加密令牌)或使用强秘密来签署令牌 如果秘密钥匙被盗怎么办?然后,无论我们使用了多少安全层,黑客都可以使用密钥生成有效令牌并访问所有API。是否有任何解决方案使其更安全?

  • 我在做一个全堆栈的web应用程序。我的前端由angular-cli组成,后端由node+Express构建。