如何解决 Apache Shiro 的注销方法中抛出的 UnknownSessionException?

Posted

技术标签:

【中文标题】如何解决 Apache Shiro 的注销方法中抛出的 UnknownSessionException?【英文标题】:How to solve UnknownSessionException thrown in logout method in Apache Shiro? 【发布时间】:2017-09-04 02:11:53 【问题描述】:

大家下午好!我在 JSF 中有一个使用 Apache Shiro 身份验证的项目。 login 方法工作正常,但 logout 方法抛出 UnknownSessionException。

这是我对 pom.xml(Maven) 的依赖:

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.3.2</version>
        </dependency> 

堆栈跟踪:

abr 07, 2017 9:20:26 PM com.sun.faces.context.AjaxExceptionHandlerImpl handlePartialResponseError
GRAVE: java.lang.IllegalStateException: org.apache.shiro.session.UnknownSessionException: There is no session with id [e3b18152-09c3-4644-8815-f3bf7dd77513]
    at org.apache.shiro.web.servlet.ShiroHttpSession.getAttribute(ShiroHttpSession.java:133)
    at com.sun.faces.context.SessionMap.put(SessionMap.java:127)
    at com.sun.faces.context.SessionMap.put(SessionMap.java:61)
    at com.sun.faces.application.view.FaceletViewHandlingStrategy.getResponseEncoding(FaceletViewHandlingStrategy.java:1310)
    at com.sun.faces.application.view.FaceletViewHandlingStrategy.createResponseWriter(FaceletViewHandlingStrategy.java:1198)
    at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:405)
    at com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:134)
    at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:120)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:659)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
    at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
    at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
    at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1100)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:687)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1520)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1476)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Unknown Source)
Caused by: org.apache.shiro.session.UnknownSessionException: There is no session with id [e3b18152-09c3-4644-8815-f3bf7dd77513]
    at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170)
    at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSessionFromDataSource(DefaultSessionManager.java:236)
    at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSession(DefaultSessionManager.java:222)
    at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doGetSession(AbstractValidatingSessionManager.java:118)
    at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupSession(AbstractNativeSessionManager.java:148)
    at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupRequiredSession(AbstractNativeSessionManager.java:152)
    at org.apache.shiro.session.mgt.AbstractNativeSessionManager.getAttribute(AbstractNativeSessionManager.java:249)
    at org.apache.shiro.session.mgt.DelegatingSession.getAttribute(DelegatingSession.java:141)
    at org.apache.shiro.session.ProxiedSession.getAttribute(ProxiedSession.java:121)
    at org.apache.shiro.web.servlet.ShiroHttpSession.getAttribute(ShiroHttpSession.java:131)
    ... 49 more

Shiro.ini:

# =======================
# Shiro INI configuration
# =======================

[main]

# =============================== 
# =============================== 
# Session Manager SHIRO NATIVE (WEB) 
# =============================== 
# =============================== 

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

shiro.loginUrl = /faces/paginalogin.xhtml
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher

# ===============================
# ===============================
# DATABASE SQL
# ===============================
# ===============================
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.permissionsLookupEnabled = false
jdbcRealm.authenticationCachingEnabled = false
jdbcRealm.authenticationQuery = SELECT senha FROM usuario WHERE email = ?
jdbcRealm.userRolesQuery = SELECT if(nivel='A','admin','normal') FROM usuario WHERE email = ?


# ===============================
# mysql
# ===============================
dbs=com.mysql.jdbc.jdbc2.optional.MysqlDataSource
#dbs.driverClass = com.mysql.jdbc.Driver
dbs.user=root
dbs.databaseName=shiroexemplo
dbs.serverName=localhost
dbs.portNumber=3306


# ===============================
# ===============================
# DATABASE INSTANCE
# ===============================
# ===============================
jdbcRealm.dataSource=$dbs

[users]
# The 'users' section is for simple deployments
# when you only need a small number of statically-defined
# set of User accounts.

[roles]
admin=*
normal=*

[urls]
/faces/admin/*= authc, roles[admin]
/faces/normal/*=authc, roles[normal]
/faces/paginalogin.xhtml = anon

Java 类:

package com.mycompany.shiroexemplo.bean;

import java.io.Serializable;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.servlet.http.HttpSession;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mycompany.shiroexemplo.dao.UsuarioDAO;
import com.mycompany.shiroexemplo.model.Usuario;

@ManagedBean
@SessionScoped
public class LoginController implements Serializable 

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String username;
    private String senha;

    public String getUsername() 
        return username;
    

    public void setUsername(String username) 
        this.username = username;
    

    public String getSenha() 
        return senha;
    

    public void setSenha(String senha) 
        this.senha = senha;
    

    public Usuario getUsuario() 
        return usuario;
    

    public void setUsuario(Usuario usuario) 
        this.usuario = usuario;
    

    public Usuario usuario;
    private static final Logger log = LoggerFactory.getLogger(LoginController.class);

    public String autenticate() 

        SimpleHash hash = new SimpleHash("md5", senha);
        UsernamePasswordToken token = new UsernamePasswordToken(username, hash.toHex());

        token.setRememberMe(true);
        Subject currentUser = SecurityUtils.getSubject();
        log.info("logando usando o email [" + username + "] e senha [" + hash.toHex() + "]");

        try 
            if (!currentUser.isAuthenticated()) 
                currentUser.login(token);

                UsuarioDAO ud = new UsuarioDAO();
                usuario = ud.getUsuario(username);
            

            if (currentUser.hasRole("admin")) 
                return "Admin";
             else 
                return "Normal";
            
         catch (AuthenticationException e) 
            e.printStackTrace();
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erro: ", "Usuário ou senha incorretos"));
            return "paginalogin";
        

    

    public String logout(ActionEvent ev) 
        Subject currentUser = SecurityUtils.getSubject();
        try 
            if(currentUser.isAuthenticated())
                currentUser.logout();
            
         catch (Exception e) 
            e.printStackTrace();
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erro: ", e.getMessage()));
            return null;
        
        return "paginalogin";

    


【问题讨论】:

【参考方案1】:

您可以在注销后发出重定向吗?自从我对 Faces 做任何事情以来已经有一段时间了,所以我不确定您所看到的请求流。基本上,您希望注销请求是在重定向之前发生的最后件事。

【讨论】:

【参考方案2】:

感谢您回答我。做一些研究,我可以解决这个问题。首先,我在我的 Shiro.ini 中注释了这些行:

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

之后,我实现了 javax.servlet.http.HttpSession 来获取当前会话,如下所示:

HttpSession session = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(false);

最后,我在页面中做了一个 session.invalidate() 和一个重定向,像这样:

session.invalidate();
FacesContext.getCurrentInstance().getExternalContext.redirect("faces/paginaLogin.xhtml");

注销方法的最终版本:

  public void logout()
        try
          Subject currentUser = SecurityUtils.getSubject();
          HttpSession session=(HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(false);
          if(currentUser.isAuthenticated())
              session.invalidate();
 FacesContext.getCurrentInstance().getExternalContext().redirect("faces/paginalogin.xhtml"); 

        
    catch(Exception e)
            e.printStackTrace();
                    FacesContext.getCurrentInstance().addMessage(null,
                            new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erro: ", e.getMessage()));
        
    

【讨论】:

以上是关于如何解决 Apache Shiro 的注销方法中抛出的 UnknownSessionException?的主要内容,如果未能解决你的问题,请参考以下文章

如何更正 Shiro 注销代码(执行注销后用户仍然可以访问页面)?

如何限制grails shiro security中的操作

如何在 Shiro 中更新已验证主题的主体

如何关闭 shiro 会话

如何解决 org.apache.shiro.util.unknownclassexception

让Apache Shiro保护你的应用