Shiro - 会话超时后重定向到登录页面

Shiro - redirect to login page after session timeout

当会话超时时,我需要将我的网络应用程序 (Java + angular) 重定向到登录页面。 shiro 是自动重定向还是应该由代码处理?

我尝试了几种破解方法,但对我没有用。 我尝试将会话超时添加到我的 web.xml 但它根本没有生效。 我尝试更新 shiro.ini,但无法再登录我的应用程序!

能否请您帮忙找出这些文件中的错误或建议在这种情况下应该做什么。

提前致谢。

web.xml:

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>ResourceManager</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- session-config>
    <session-timeout>1</session-timeout>
  </session-config>

  <error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/Login.html</location>
  </error-page-->
  
  <listener>
    <listener-class>
     com.<our customized package>.logging.AppLifeCircleListener
     </listener-class>
  </listener>
  <listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
  </listener>
  <filter>
    <filter-name>shiro-filter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>shiro-filter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
  </filter-mapping>
  <servlet>
    <servlet-name>login</servlet-name>
    <jsp-file>/Login.jsp</jsp-file>
  </servlet>
  <servlet>
    <servlet-name>rest</servlet-name>
    <servlet-class>
     org.springframework.web.servlet.DispatcherServlet
   </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/rest-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>rest</servlet-name>
    <url-pattern>/management/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>rest</servlet-name>
    <url-pattern>/privilege/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>rest</servlet-name>
    <url-pattern>/file-explorer/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>login</servlet-name>
    <url-pattern>/Login.html</url-pattern>
  </servlet-mapping>
</web-app>

shiro.ini :

[main]

# LDAP Settinng
contextFactory = com.ibm.datafabrication.web.server.security.JndiLdapContextFactoryExt
contextFactory.url = ldap://<host>:389
contextFactory.systemUsername = <...>
contextFactory.keyPath = <>
contextFactory.encodedSystemPassword = ...

ldapRealm = com.<>.ActiveDirectoryRealm
ldapRealm.ldapContextFactory = $contextFactory
ldapRealm.userSearchFilter = (sAMAccountName={0})
ldapRealm.groupNameAttribute = cn
ldapRealm.searchBase = DC=<...>,DC=<...>,DC=com
ldapRealm.groupRolesMap = dfp_users:user,dfp_admins:admin

# SQLite Setting
ds = org.sqlite.SQLiteDataSource
ds.url = jdbc:sqlite:<>.db
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource = $ds
jdbcRealm.permissionsLookupEnabled = false
jdbcRealm.authenticationQuery = SELECT Password FROM Users WHERE Name=?
jdbcRealm.userRolesQuery = WITH uname(name) AS (VALUES(?)) SELECT 'user' FROM Users, uname WHERE Users.Name=uname.name UNION ALL SELECT 'admin' FROM (SELECT Id FROM Users, uname WHERE Users.Name=uname.name) LEFT JOIN Admins on Id = Admins.UserId WHERE UserId NOT NULL
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
credentialsMatcher.storedCredentialsHexEncoded = true
jdbcRealm.credentialsMatcher = $credentialsMatcher

authc.loginUrl = /Login.html
securityManager.realms = $jdbcRealm

authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy

>>>>>>>>>>>>>>>>>>>>> What I tired

# session timeout
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionMode=native
securityManager.sessionManager.globalSessionTimeout = 60000
securityManager.sessionManager = $sessionManager

sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
sessionValidationScheduler.interval = 30000
securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

[urls]
/logout = logout
/css/** = anon
/images/** = anon
/plugins/** = anon
/favicon.ico = anon
/tdf.ico = anon
/Login.html = authc
...
/** = authc, roles[user]

您可以保留 web.xml 原样,因为 Shiro 与此文件中的配置无关。您的 servlet 的 HttpServletRequest 和 Shiro Session 使用的 HttpSession 有所不同。它们是完全不同的东西。所以不要被 servlet 的会话配置和 Shiro 的会话配置混淆。在这种情况下,我们让 Shiro 处理您的会话。

你的shiro.ini
有问题 您正在覆盖写入 DefaultWebSessionManager:

的设置
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager // Define a sessionManager variable
securityManager.sessionMode=native
securityManager.sessionManager.globalSessionTimeout = 60000 // set a securityManager.sessionManager property
securityManager.sessionManager = $sessionManager // override the property by setting the object

您必须先设置对象:

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
securityManager.sessionMode=native
securityManager.sessionManager.globalSessionTimeout = 60000

您不需要 ExecutorServiceSessionValidationScheduler,因为 DefaultWebSessionManager 已经提供了验证程序。一个工作示例如下所示:

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
# Session timeout token_ttl_ms = 14 days
sessionManager.globalSessionTimeout = 1209600000
# Session valdiation = 15 minutes
sessionManager.sessionValidationInterval = 900000

securityManager = org.apache.shiro.web.mgt.DefaultWebSecurityManager
securityManager.sessionManager = $sessionManager

DefaultWebSessionManager

DefaultWebSessionManager provides all the functionality you are looking for out of the box (basically). When a session expires the DefaultWebSessionManager calls onExpiration on its super which notifys all its listeners about the expiration in the SessionManager. You simply could register a SessionListener 到您的 SessionManager 并在其 onExpiration 方法处触发重新加载或重定向。也许这种方法更适合 jsp/jspx 应用程序。
无论如何,请阅读如何在官方文档中注册会话侦听器 here

替代方法

另一种方法可能是让您的客户端管理会话状态,并让它通过调用您的服务不断检查过期时间。这可以通过计时器或在 JavaScript.

中的循环中完成

备注

org.apache.shiro.authc.credential.Sha256CredentialsMatcher 自 2010 年起已弃用。
直接使用 HashedCredentialsMatcher 并设置它的 hashAlgorithmName 属性.

最后,我在客户端使用@ng-idle/core angular 包实现了一个超时解决方案:

https://www.npmjs.com/package/@ng-idle/core

https://hackedbychinese.github.io/ng2-idle/

我也使用@ng-idle/keepalive 连续 ping 服务器并保持它的活动状态:

handleIdle(){
  // sets an idle timeout of x seconds, for testing purposes.
 this.idle.setIdle(/*Config.SESSION_IDLE*/ this.sessionIdle);
 // sets a timeout period of y seconds. after x+y seconds of inactivity, the user will be considered timed out.
 this.idle.setTimeout(/*Config.SESSION_TIMEOUT*/ this.sessionTimeout);
 // sets the default interrupts, in this case, things like clicks, scrolls, touches to the document
 this.idle.setInterrupts(DEFAULT_INTERRUPTSOURCES);

 this.idle.onIdleEnd.subscribe(() => this.idleState = 'No longer idle.');
 this.idle.onTimeout.subscribe(() => {
   this.idleState = 'Timed out!';
   this.timedOut = true;
 });
 this.idle.onIdleStart.subscribe(() => {
         this.idleState = 'You\'ve gone idle!'
         this.idleModal.open();
     }    
 );
 this.idle.onTimeoutWarning.subscribe((countdown) => {
     this.idleState = 'You\'ve gone idle! Your session will be timed out in ' + countdown + ' seconds!';

     if (countdown == 1){
         window.location.href = "../logout";
     }
 });

 // sets the ping interval to z seconds
 this.keepalive.interval(/*Config.KEEP_ALIVE_INTERVAL*/this.keepAliveInterval);

 this.keepalive.onPing.subscribe(() => {
     this.lastPing = new Date();
     this.resourcesService.ping();
 });

 this.reset(); }