This is an automated email from the ASF dual-hosted git repository.

pradeepagrawal8184 pushed a commit to branch ranger-2.10
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/ranger-2.10 by this push:
     new eac1031be RANGER-5749: Limit concurrent Ranger Admin UI sessions per 
user (#1235)
eac1031be is described below

commit eac1031bef83818221d0dff99cb26566c1333986
Author: eoinmcdonnell113 <[email protected]>
AuthorDate: Thu Oct 15 05:53:43 2026 -0400

    RANGER-5749: Limit concurrent Ranger Admin UI sessions per user (#1235)
    
    Cherry-pick of 1641c7d75 (#1200) from master.
    
    https://issues.apache.org/jira/browse/RANGER-5749
    
    (cherry picked from commit 1641c7d754130d99d4017f1cd6f4f2cc523a9f1e)
    
    Co-authored-by: eoinmcdonnell113 <[email protected]>
---
 .../java/org/apache/ranger/biz/SessionMgr.java     | 210 ++++++++++-
 .../web/filter/RangerKRBAuthenticationFilter.java  |  47 ++-
 .../conf.dist/ranger-admin-default-site.xml        |  13 +
 security-admin/src/main/webapp/WEB-INF/web.xml     |   3 +
 .../java/org/apache/ranger/biz/TestSessionMgr.java | 408 +++++++++++++++++++++
 .../filter/TestRangerKRBAuthenticationFilter.java  | 126 +++++++
 6 files changed, 803 insertions(+), 4 deletions(-)

diff --git a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java 
b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
index 56f405a9d..c31ed4e51 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
@@ -21,10 +21,13 @@
 
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 
@@ -75,6 +78,14 @@ public class SessionMgr {
 
        static final Logger logger = LoggerFactory.getLogger(SessionMgr.class);
 
+       public static final String PROP_SESSION_LIMIT_CONCURRENCY      = 
"ranger.session.limit.concurrency";
+       public static final String SESSION_ATTR_CONCURRENT_EXPIRED     = 
"RANGER_CONCURRENT_SESSION_EXPIRED";
+       public static final String SESSION_ATTR_CONCURRENT_EXPIRED_SSO = 
"RANGER_CONCURRENT_SESSION_EXPIRED_SSO";
+       public static final String SESSION_ATTR_DOWNLOAD_ONLY          = 
"RANGER_SESSION_DOWNLOAD_ONLY";
+       public static final String SESSION_ATTR_NON_UI                 = 
"RANGER_SESSION_NON_UI";
+       private static final String DEFAULT_BROWSER_USER_AGENTS        = 
"Mozilla,Opera,Chrome";
+       private static final ConcurrentHashMap<String, Object> 
CONCURRENT_SESSION_LOCKS = new ConcurrentHashMap<>();
+
        @Autowired
        RESTErrorUtil restErrorUtil;
        
@@ -158,7 +169,7 @@ public UserSessionBase processSuccessLogin(int authType, 
String userAgent,
                                                                gjAuthSession = 
storeAuthSession(gjAuthSession);
                                                                
session.setAttribute("auditLoginId", gjAuthSession.getId());
                                                        }
-                                                       else if 
(!StringUtils.isEmpty(httpRequest.getRequestURI()) && 
!(httpRequest.getRequestURI().contains("/secure/policies/download/") || 
httpRequest.getRequestURI().contains("/secure/download/"))){
+                                                       else if 
(!StringUtils.isEmpty(httpRequest.getRequestURI()) && 
!isPluginOrSecureDownloadRequest(httpRequest.getRequestURI())){
                                                                gjAuthSession = 
storeAuthSession(gjAuthSession);
                                                                
session.setAttribute("auditLoginId", gjAuthSession.getId());
                                                        }else if 
(StringUtils.isEmpty(httpRequest.getRequestURI())){
@@ -211,6 +222,24 @@ else if (!StringUtils.isEmpty(httpRequest.getRequestURI()) 
&& !(httpRequest.getR
                                }
                        }
 
+                       if (session != null) {
+                               if 
(isPluginOrSecureDownloadRequest(httpRequest.getRequestURI())) {
+                                       try {
+                                               
session.setAttribute(SESSION_ATTR_DOWNLOAD_ONLY, Boolean.TRUE);
+                                       } catch (IllegalStateException e) {
+                                               logger.debug("Could not mark 
download-only session", e);
+                                       }
+                               } else if 
(!isBrowserUserAgent(resolveUserAgent(userAgent, httpRequest))) {
+                                       try {
+                                               
session.setAttribute(SESSION_ATTR_NON_UI, Boolean.TRUE);
+                                       } catch (IllegalStateException e) {
+                                               logger.debug("Could not mark 
non-UI session", e);
+                                       }
+                               } else {
+                                       
enforceConcurrentSessionLimit(currentLoginId, session);
+                               }
+                       }
+
                }
 
                return userSession;
@@ -383,6 +412,185 @@ public Date getLastSuccessLoginAuthTimeByUserId(String 
loginId) {
         return null;
     }
 
+       public static boolean isConcurrentSessionExpired(HttpSession session) {
+               if (session == null) {
+                       return false;
+               }
+
+               try {
+                       return 
Boolean.TRUE.equals(session.getAttribute(SESSION_ATTR_CONCURRENT_EXPIRED));
+               } catch (IllegalStateException e) {
+                       return false;
+               }
+       }
+
+       public static boolean isConcurrentSessionExpiredSso(HttpSession 
session) {
+               if (session == null) {
+                       return false;
+               }
+
+               try {
+                       return 
Boolean.TRUE.equals(session.getAttribute(SESSION_ATTR_CONCURRENT_EXPIRED_SSO));
+               } catch (IllegalStateException e) {
+                       return false;
+               }
+       }
+
+       /**
+        * When {@code ranger.session.limit.concurrency} is exceeded, expire 
the oldest UI sessions
+        * so the new login succeeds. SSO sessions are marked expired for Knox 
logout redirect.
+        * The count is taken from this JVM's in-memory session list, not 
cluster-wide.
+        * Find-and-expire is serialized per loginId so two concurrent UI 
logins for the same user
+        * cannot both observe a count under the limit.
+        */
+       protected void enforceConcurrentSessionLimit(String loginId, 
HttpSession currentSession) {
+               int limit = 
PropertiesUtil.getIntProperty(PROP_SESSION_LIMIT_CONCURRENCY, 0);
+
+               if (limit <= 0 || StringUtils.isBlank(loginId) || 
currentSession == null) {
+                       return;
+               }
+
+               Object lock = 
CONCURRENT_SESSION_LOCKS.computeIfAbsent(loginId.toLowerCase(Locale.ROOT), id 
-> new Object());
+
+               synchronized (lock) {
+                       List<HttpSession> otherSessions = 
findActiveUiSessionsForUser(loginId, currentSession);
+
+                       if (otherSessions.size() < limit) {
+                               return;
+                       }
+
+                       otherSessions.sort(Comparator.comparingLong(session -> {
+                               try {
+                                       return session.getCreationTime();
+                               } catch (IllegalStateException e) {
+                                       return 0L;
+                               }
+                       }));
+
+                       int toExpire = otherSessions.size() - limit + 1;
+
+                       logger.info("Concurrent session limit {} exceeded for 
user {}; expiring {} older session(s)", limit, loginId, toExpire);
+
+                       for (int i = 0; i < toExpire; i++) {
+                               expireConcurrentSession(otherSessions.get(i));
+                       }
+               }
+       }
+
+       /**
+        * Plugin and secure download URLs. Used both to skip x_auth_sess rows 
(unless
+        * {@code ranger.downloadpolicy.session.log.enabled} is true) and to 
exclude
+        * those sessions from the UI concurrent-session quota.
+        */
+       static boolean isPluginOrSecureDownloadRequest(String uri) {
+               if (StringUtils.isEmpty(uri)) {
+                       return false;
+               }
+
+               return uri.contains("/secure/policies/download/")
+                               || uri.contains("/secure/download/")
+                               || uri.contains("/plugins/policies/download/")
+                               || uri.contains("/tags/download/")
+                               || uri.contains("/roles/download/")
+                               || uri.contains("/xusers/download/")
+                               || uri.contains("/gds/download/");
+       }
+
+       static boolean isBrowserUserAgent(String userAgent) {
+               if (StringUtils.isBlank(userAgent)) {
+                       return false;
+               }
+
+               String agents = 
PropertiesUtil.getProperty("ranger.krb.browser-useragents-regex", 
DEFAULT_BROWSER_USER_AGENTS);
+
+               if (StringUtils.isBlank(agents)) {
+                       agents = DEFAULT_BROWSER_USER_AGENTS;
+               }
+
+               String userAgentLower = userAgent.toLowerCase(Locale.ROOT);
+
+               for (String agentPrefix : agents.split(",")) {
+                       if (StringUtils.isNotBlank(agentPrefix) && 
userAgentLower.startsWith(agentPrefix.trim().toLowerCase(Locale.ROOT))) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       private static String resolveUserAgent(String userAgent, 
HttpServletRequest httpRequest) {
+               if (StringUtils.isNotBlank(userAgent)) {
+                       return userAgent;
+               }
+
+               return httpRequest != null ? 
httpRequest.getHeader(HTTPUtil.USER_AGENT) : null;
+       }
+
+       private List<HttpSession> findActiveUiSessionsForUser(String loginId, 
HttpSession currentSession) {
+               CopyOnWriteArrayList<HttpSession> activeHttpSessions = 
RangerHttpSessionListener.getActiveSessionOnServer();
+               List<HttpSession>                 matching           = new 
ArrayList<>();
+
+               if (CollectionUtils.isEmpty(activeHttpSessions)) {
+                       return matching;
+               }
+
+               for (HttpSession httpSession : activeHttpSessions) {
+                       if (httpSession == null || httpSession == 
currentSession) {
+                               continue;
+                       }
+
+                       try {
+                               if 
(Boolean.TRUE.equals(httpSession.getAttribute(SESSION_ATTR_CONCURRENT_EXPIRED)))
 {
+                                       continue;
+                               }
+
+                               if 
(Boolean.TRUE.equals(httpSession.getAttribute(SESSION_ATTR_DOWNLOAD_ONLY))
+                                               || 
Boolean.TRUE.equals(httpSession.getAttribute(SESSION_ATTR_NON_UI))) {
+                                       continue;
+                               }
+
+                               if 
(httpSession.getAttribute(RangerSecurityContextFormationFilter.AKA_SC_SESSION_KEY)
 == null) {
+                                       continue;
+                               }
+
+                               RangerSecurityContext securityContext = 
(RangerSecurityContext) 
httpSession.getAttribute(RangerSecurityContextFormationFilter.AKA_SC_SESSION_KEY);
+                               UserSessionBase       userSession     = 
securityContext != null ? securityContext.getUserSession() : null;
+
+                               if (userSession != null && 
loginId.equalsIgnoreCase(userSession.getLoginId())) {
+                                       matching.add(httpSession);
+                               }
+                       } catch (IllegalStateException e) {
+                               logger.debug("Skipping invalidated session 
while counting concurrent sessions", e);
+                       }
+               }
+
+               return matching;
+       }
+
+       private void expireConcurrentSession(HttpSession httpSession) {
+               try {
+                       RangerSecurityContext context     = 
(RangerSecurityContext) 
httpSession.getAttribute(RangerSecurityContextFormationFilter.AKA_SC_SESSION_KEY);
+                       UserSessionBase       userSession = context != null ? 
context.getUserSession() : null;
+                       boolean               ssoOrProxy  = userSession != null
+                                       && 
(Boolean.TRUE.equals(userSession.isSSOEnabled()) || 
Boolean.TRUE.equals(userSession.isSpnegoEnabled()));
+
+                       
httpSession.setAttribute(SESSION_ATTR_CONCURRENT_EXPIRED, Boolean.TRUE);
+                       
httpSession.setAttribute(SESSION_ATTR_CONCURRENT_EXPIRED_SSO, ssoOrProxy);
+
+                       if (context != null) {
+                               context.setUserSession(null);
+                       }
+
+                       logger.info("Expired concurrent Ranger Admin session 
(ssoOrTrustedProxy={})", ssoOrProxy);
+
+                       if (!ssoOrProxy) {
+                               httpSession.invalidate();
+                       }
+               } catch (IllegalStateException e) {
+                       logger.debug("Session already invalidated while 
enforcing concurrent session limit", e);
+               }
+       }
+
        protected boolean validateUserSession(UserSessionBase userSession,
                        String currentLoginId) {
                if (currentLoginId
diff --git 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerKRBAuthenticationFilter.java
 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerKRBAuthenticationFilter.java
index 30ac069fe..99baab21f 100644
--- 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerKRBAuthenticationFilter.java
+++ 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerKRBAuthenticationFilter.java
@@ -61,9 +61,11 @@
 import org.apache.hadoop.security.authorize.AuthorizationException;
 import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.util.HttpExceptionUtils;
+import org.apache.ranger.biz.SessionMgr;
 import org.apache.ranger.biz.UserMgr;
 import org.apache.ranger.common.PropertiesUtil;
 import org.apache.ranger.common.RESTErrorUtil;
+import org.apache.ranger.common.RangerConstants;
 import org.apache.ranger.util.RestUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -333,8 +335,16 @@ protected void doFilter(FilterChain filterChain,
        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain filterChain) throws IOException, 
ServletException {
+               HttpServletRequest  httpRequest  = (HttpServletRequest) request;
+               HttpServletResponse httpResponse = (HttpServletResponse) 
response;
+               HttpSession         httpSession  = 
httpRequest.getSession(false);
+
+               if (SessionMgr.isConcurrentSessionExpired(httpSession)) {
+                       handleConcurrentSessionExpiredRequest(httpRequest, 
httpResponse);
+                       return;
+               }
+
                String authtype = PropertiesUtil.getProperty(RANGER_AUTH_TYPE);
-               HttpServletRequest httpRequest = (HttpServletRequest)request;
                Authentication existingAuth = 
SecurityContextHolder.getContext().getAuthentication();
                if(isSpnegoEnable(authtype) && (existingAuth == null || 
!existingAuth.isAuthenticated())){
                        
KerberosName.setRules(PropertiesUtil.getProperty(NAME_RULES, "DEFAULT"));
@@ -347,7 +357,7 @@ public void doFilter(ServletRequest request, 
ServletResponse response,
                        }
                        try{
                                if 
(StringUtils.equals(httpRequest.getParameter("action"), 
RestUtil.TIMEOUT_ACTION)) {
-                                       handleTimeoutRequest(httpRequest, 
(HttpServletResponse) response);
+                                       handleTimeoutRequest(httpRequest, 
httpResponse);
                                } else {
                                        super.doFilter(request, response, 
filterChain);
                                }
@@ -371,7 +381,6 @@ public void doFilter(ServletRequest request, 
ServletResponse response,
        
                        if (allowTrustedProxy && 
StringUtils.isNotEmpty(doAsUser) && existingAuth != null && 
existingAuth.isAuthenticated()
                                        && StringUtils.equals(action, 
RestUtil.TIMEOUT_ACTION)) {
-                               HttpServletResponse httpResponse = 
(HttpServletResponse) response;
                                handleTimeoutRequest(httpRequest, httpResponse);
                        } else {
                                filterChain.doFilter(request, response);
@@ -379,6 +388,38 @@ public void doFilter(ServletRequest request, 
ServletResponse response,
                }
        }
 
+       private void handleConcurrentSessionExpiredRequest(HttpServletRequest 
httpRequest, HttpServletResponse httpResponse) throws IOException {
+               HttpSession httpSession = httpRequest.getSession(false);
+
+               if (SessionMgr.isConcurrentSessionExpiredSso(httpSession)) {
+                       LOG.info("Concurrent session expired for SSO/Trusted 
Proxy user; redirecting to Knox login");
+                       handleTimeoutRequest(httpRequest, httpResponse);
+                       return;
+               }
+
+               LOG.info("Concurrent session expired; redirecting to Ranger 
login");
+
+               if (httpSession != null) {
+                       try {
+                               httpSession.invalidate();
+                       } catch (IllegalStateException e) {
+                               LOG.debug("Session already invalidated", e);
+                       }
+               }
+
+               String loginPage   = 
PropertiesUtil.getProperty("ranger.logout.success.page", "/login.jsp");
+               String redirectUrl = httpRequest.getContextPath() + loginPage;
+               String ajaxHeader  = httpRequest.getHeader("X-Requested-With");
+
+               if ("XMLHttpRequest".equalsIgnoreCase(ajaxHeader)) {
+                       httpResponse.setHeader("X-Frame-Options", "DENY");
+                       
httpResponse.setStatus(RangerConstants.SC_AUTHENTICATION_TIMEOUT);
+                       httpResponse.setHeader("X-Rngr-Redirect-Url", 
redirectUrl);
+               } else {
+                       httpResponse.sendRedirect(redirectUrl);
+               }
+       }
+
        private void handleTimeoutRequest(HttpServletRequest httpRequest, 
HttpServletResponse httpResponse) throws IOException{
                String xForwardedURL = 
RestUtil.constructForwardableURL(httpRequest);
                if (LOG.isDebugEnabled()) {
diff --git 
a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml 
b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
index 164b63d02..ce9ac191d 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
@@ -178,6 +178,19 @@
                <name>ranger.admin.login.autolock.maxfailure</name>
                <value>5</value>
        </property>
+       <property>
+               <name>ranger.session.limit.concurrency</name>
+               <value>0</value>
+               <description>
+                       Maximum number of concurrent Ranger Admin UI sessions 
per user.
+                       When the limit is exceeded, the oldest UI session is 
expired so the new login succeeds.
+                       0 or a negative value means no limit (default). Plugin 
policy/tag/role download
+                       sessions and non-browser REST/API clients (for example 
scripted RangerClient or curl)
+                       do not count toward this limit. Browser detection uses 
ranger.krb.browser-useragents-regex.
+                       Enforcement is per Ranger Admin process using that 
node's in-memory session list.
+                       In an HA or load-balanced deployment a user can hold up 
to this many UI sessions on each node.
+               </description>
+       </property>
 
 <!-- # anonymous access -->
        <property>
diff --git a/security-admin/src/main/webapp/WEB-INF/web.xml 
b/security-admin/src/main/webapp/WEB-INF/web.xml
index c2f1c985e..c340a26e5 100644
--- a/security-admin/src/main/webapp/WEB-INF/web.xml
+++ b/security-admin/src/main/webapp/WEB-INF/web.xml
@@ -33,6 +33,9 @@
   <listener>
     
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
   </listener>
+  <listener>
+    
<listener-class>org.apache.ranger.security.listener.RangerHttpSessionListener</listener-class>
+  </listener>
   <filter>
     <filter-name>springSecurityFilterChain</filter-name>
     
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
diff --git 
a/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java 
b/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java
index 6b3baaa58..1cba3c1e2 100644
--- a/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java
@@ -73,8 +73,13 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -87,7 +92,9 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -126,6 +133,7 @@ public class TestSessionMgr {
     public void tearDownSuperUserConfig() {
         
PropertiesUtil.getPropertiesMap().remove(RangerConstants.RANGER_ADMIN_SUPER_USERS);
         
PropertiesUtil.getPropertiesMap().remove(RangerConstants.RANGER_ADMIN_SUPER_GROUPS);
+        
PropertiesUtil.getPropertiesMap().remove(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY);
         RangerSuperUserConfig.resetForTests();
     }
 
@@ -693,4 +701,404 @@ public void 
testSetUserRoles_ConfigSuperUserGrantsKeyAdminForSysAdmin() {
 
         
PropertiesUtil.getPropertiesMap().remove(RangerConstants.RANGER_ADMIN_SUPER_USERS);
     }
+
+    @Test
+    public void testProcessSuccessLogin_LimitZeroDoesNotExpireOldestSession() {
+        RangerContextHolder.setSecurityContext(null);
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "0");
+
+        setupAuthentication("limitUser");
+
+        XXPortalUser portalUser = portalUser("limitUser", 70L);
+        stubPortalUserLookup(portalUser);
+        stubRolesAndPermissions(portalUser);
+        stubAuthSessionCreate(200L);
+        
when(httpUtil.getDeviceType(anyString())).thenReturn(RangerCommonEnums.DEVICE_UNKNOWN);
+
+        HttpSession currentSession = mock(HttpSession.class);
+        when(currentSession.getAttribute("auditLoginId")).thenReturn(null);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getSession()).thenReturn(currentSession);
+        when(request.getRequestURI()).thenReturn("/index.html");
+        when(request.getAttribute("spnegoEnabled")).thenReturn(null);
+
+        UserSessionBase ret = 
sessionMgr.processSuccessLogin(XXAuthSession.AUTH_TYPE_PASSWORD, "Mozilla/5.0", 
request);
+
+        assertNotNull(ret);
+        assertEquals(70L, ret.getUserId());
+    }
+
+    @Test
+    public void 
testProcessSuccessLogin_LimitOneExpiresOldestFormLoginSession() {
+        RangerContextHolder.setSecurityContext(null);
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "1");
+
+        setupAuthentication("limitUser");
+
+        XXPortalUser portalUser = portalUser("limitUser", 71L);
+        stubPortalUserLookup(portalUser);
+        stubRolesAndPermissions(portalUser);
+        stubAuthSessionCreate(201L);
+        
when(httpUtil.getDeviceType(anyString())).thenReturn(RangerCommonEnums.DEVICE_UNKNOWN);
+
+        HttpSession currentSession = mock(HttpSession.class);
+        when(currentSession.getAttribute("auditLoginId")).thenReturn(null);
+
+        HttpSession oldestSession = mockUiSession("limitUser", 71L, false, 1L);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getSession()).thenReturn(currentSession);
+        when(request.getRequestURI()).thenReturn("/index.html");
+        when(request.getAttribute("spnegoEnabled")).thenReturn(null);
+
+        try (MockedStatic<RangerHttpSessionListener> mocked = 
Mockito.mockStatic(RangerHttpSessionListener.class)) {
+            CopyOnWriteArrayList<HttpSession> sessions = new 
CopyOnWriteArrayList<>();
+            sessions.add(oldestSession);
+            
mocked.when(RangerHttpSessionListener::getActiveSessionOnServer).thenReturn(sessions);
+
+            UserSessionBase ret = 
sessionMgr.processSuccessLogin(XXAuthSession.AUTH_TYPE_PASSWORD, "Mozilla/5.0", 
request);
+
+            assertNotNull(ret);
+            
verify(oldestSession).setAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED, 
Boolean.TRUE);
+            
verify(oldestSession).setAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED_SSO,
 false);
+            verify(oldestSession).invalidate();
+        }
+    }
+
+    @Test
+    public void 
testProcessSuccessLogin_LimitOneMarksOldestSsoSessionExpiredWithoutInvalidate() 
{
+        RangerContextHolder.setSecurityContext(null);
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "1");
+
+        setupAuthentication("ssoUser");
+
+        XXPortalUser portalUser = portalUser("ssoUser", 72L);
+        stubPortalUserLookup(portalUser);
+        stubRolesAndPermissions(portalUser);
+        stubAuthSessionCreate(202L);
+        
when(httpUtil.getDeviceType(anyString())).thenReturn(RangerCommonEnums.DEVICE_UNKNOWN);
+
+        HttpSession currentSession = mock(HttpSession.class);
+        when(currentSession.getAttribute("auditLoginId")).thenReturn(null);
+
+        HttpSession oldestSession = mockUiSession("ssoUser", 72L, true, 1L);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getSession()).thenReturn(currentSession);
+        when(request.getRequestURI()).thenReturn("/index.html");
+        when(request.getAttribute("spnegoEnabled")).thenReturn(Boolean.TRUE);
+
+        try (MockedStatic<RangerHttpSessionListener> mocked = 
Mockito.mockStatic(RangerHttpSessionListener.class)) {
+            CopyOnWriteArrayList<HttpSession> sessions = new 
CopyOnWriteArrayList<>();
+            sessions.add(oldestSession);
+            
mocked.when(RangerHttpSessionListener::getActiveSessionOnServer).thenReturn(sessions);
+
+            UserSessionBase ret = 
sessionMgr.processSuccessLogin(XXAuthSession.AUTH_TYPE_TRUSTED_PROXY, 
"Mozilla/5.0", request);
+
+            assertNotNull(ret);
+            assertTrue(ret.isSSOEnabled());
+            
verify(oldestSession).setAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED, 
Boolean.TRUE);
+            
verify(oldestSession).setAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED_SSO,
 true);
+            verify(oldestSession, never()).invalidate();
+        }
+    }
+
+    @Test
+    public void 
testProcessSuccessLogin_DownloadRequestDoesNotConsumeSessionQuota() {
+        RangerContextHolder.setSecurityContext(null);
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "1");
+
+        setupAuthentication("limitUser");
+
+        XXPortalUser portalUser = portalUser("limitUser", 73L);
+        stubPortalUserLookup(portalUser);
+        stubRolesAndPermissions(portalUser);
+        
when(httpUtil.getDeviceType(anyString())).thenReturn(RangerCommonEnums.DEVICE_UNKNOWN);
+
+        HttpSession currentSession = mock(HttpSession.class);
+        when(currentSession.getAttribute("auditLoginId")).thenReturn(null);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getSession()).thenReturn(currentSession);
+        
when(request.getRequestURI()).thenReturn("/service/plugins/policies/download/hadoopdev");
+        when(request.getAttribute("spnegoEnabled")).thenReturn(null);
+
+        UserSessionBase ret = 
sessionMgr.processSuccessLogin(XXAuthSession.AUTH_TYPE_PASSWORD, "Mozilla/5.0", 
request);
+
+        assertNotNull(ret);
+        
verify(currentSession).setAttribute(SessionMgr.SESSION_ATTR_DOWNLOAD_ONLY, 
Boolean.TRUE);
+    }
+
+    @Test
+    public void testProcessSuccessLogin_ApiRequestDoesNotConsumeSessionQuota() 
{
+        RangerContextHolder.setSecurityContext(null);
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "1");
+
+        setupAuthentication("limitUser");
+
+        XXPortalUser portalUser = portalUser("limitUser", 74L);
+        stubPortalUserLookup(portalUser);
+        stubRolesAndPermissions(portalUser);
+        stubAuthSessionCreate(204L);
+        
when(httpUtil.getDeviceType(anyString())).thenReturn(RangerCommonEnums.DEVICE_UNKNOWN);
+
+        HttpSession currentSession = mock(HttpSession.class);
+        when(currentSession.getAttribute("auditLoginId")).thenReturn(null);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getSession()).thenReturn(currentSession);
+        
when(request.getRequestURI()).thenReturn("/service/public/v2/api/policies");
+        when(request.getAttribute("spnegoEnabled")).thenReturn(null);
+
+        UserSessionBase ret = 
sessionMgr.processSuccessLogin(XXAuthSession.AUTH_TYPE_PASSWORD, "curl/8.0", 
request);
+
+        assertNotNull(ret);
+        verify(currentSession).setAttribute(SessionMgr.SESSION_ATTR_NON_UI, 
Boolean.TRUE);
+        verify(currentSession, 
never()).setAttribute(eq(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED), any());
+    }
+
+    @Test
+    public void 
testProcessSuccessLogin_LimitTwoExpiresOnlyOldestOfTwoExistingSessions() {
+        RangerContextHolder.setSecurityContext(null);
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "2");
+
+        setupAuthentication("limitUser");
+
+        XXPortalUser portalUser = portalUser("limitUser", 75L);
+        stubPortalUserLookup(portalUser);
+        stubRolesAndPermissions(portalUser);
+        stubAuthSessionCreate(205L);
+        
when(httpUtil.getDeviceType(anyString())).thenReturn(RangerCommonEnums.DEVICE_UNKNOWN);
+
+        HttpSession currentSession = mock(HttpSession.class);
+        when(currentSession.getAttribute("auditLoginId")).thenReturn(null);
+
+        HttpSession oldestSession = mockUiSession("limitUser", 75L, false, 1L);
+        HttpSession newerSession  = mockUiSession("limitUser", 75L, false, 2L);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getSession()).thenReturn(currentSession);
+        when(request.getRequestURI()).thenReturn("/index.html");
+        when(request.getAttribute("spnegoEnabled")).thenReturn(null);
+
+        try (MockedStatic<RangerHttpSessionListener> mocked = 
Mockito.mockStatic(RangerHttpSessionListener.class)) {
+            CopyOnWriteArrayList<HttpSession> sessions = new 
CopyOnWriteArrayList<>();
+            sessions.add(newerSession);
+            sessions.add(oldestSession);
+            
mocked.when(RangerHttpSessionListener::getActiveSessionOnServer).thenReturn(sessions);
+
+            UserSessionBase ret = 
sessionMgr.processSuccessLogin(XXAuthSession.AUTH_TYPE_PASSWORD, "Mozilla/5.0", 
request);
+
+            assertNotNull(ret);
+            
verify(oldestSession).setAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED, 
Boolean.TRUE);
+            verify(oldestSession).invalidate();
+            verify(newerSession, 
never()).setAttribute(eq(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED), any());
+            verify(newerSession, never()).invalidate();
+        }
+    }
+
+    @Test
+    public void testProcessSuccessLogin_DoesNotExpireOtherUsersSessions() {
+        RangerContextHolder.setSecurityContext(null);
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "1");
+
+        setupAuthentication("userB");
+
+        XXPortalUser portalUser = portalUser("userB", 76L);
+        stubPortalUserLookup(portalUser);
+        stubRolesAndPermissions(portalUser);
+        stubAuthSessionCreate(206L);
+        
when(httpUtil.getDeviceType(anyString())).thenReturn(RangerCommonEnums.DEVICE_UNKNOWN);
+
+        HttpSession currentSession = mock(HttpSession.class);
+        when(currentSession.getAttribute("auditLoginId")).thenReturn(null);
+
+        HttpSession otherUserSession = mockUiSession("userA", 77L, false, 1L);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getSession()).thenReturn(currentSession);
+        when(request.getRequestURI()).thenReturn("/index.html");
+        when(request.getAttribute("spnegoEnabled")).thenReturn(null);
+
+        try (MockedStatic<RangerHttpSessionListener> mocked = 
Mockito.mockStatic(RangerHttpSessionListener.class)) {
+            CopyOnWriteArrayList<HttpSession> sessions = new 
CopyOnWriteArrayList<>();
+            sessions.add(otherUserSession);
+            
mocked.when(RangerHttpSessionListener::getActiveSessionOnServer).thenReturn(sessions);
+
+            UserSessionBase ret = 
sessionMgr.processSuccessLogin(XXAuthSession.AUTH_TYPE_PASSWORD, "Mozilla/5.0", 
request);
+
+            assertNotNull(ret);
+            assertEquals(76L, ret.getUserId());
+            verify(otherUserSession, 
never()).setAttribute(eq(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED), any());
+            verify(otherUserSession, never()).invalidate();
+        }
+    }
+
+    @Test
+    public void 
testEnforceConcurrentSessionLimit_ConcurrentSameUserExpiresOldest() throws 
Exception {
+        
PropertiesUtil.getPropertiesMap().put(SessionMgr.PROP_SESSION_LIMIT_CONCURRENCY,
 "1");
+
+        CountDownLatch concurrentExpiredReads = new CountDownLatch(2);
+        HttpSession    oldestSession          = mockUiSession("limitUser", 
80L, false, 1L, concurrentExpiredReads);
+        HttpSession    sessionA               = mock(HttpSession.class);
+        HttpSession    sessionB               = mock(HttpSession.class);
+
+        CopyOnWriteArrayList<HttpSession> active = 
RangerHttpSessionListener.getActiveSessionOnServer();
+        active.add(oldestSession);
+
+        CountDownLatch              start = new CountDownLatch(1);
+        CountDownLatch              done  = new CountDownLatch(2);
+        AtomicReference<Throwable>  error = new AtomicReference<>();
+
+        try {
+            Thread threadA = new Thread(() -> {
+                try {
+                    start.await();
+                    sessionMgr.enforceConcurrentSessionLimit("limitUser", 
sessionA);
+                } catch (Throwable t) {
+                    error.compareAndSet(null, t);
+                } finally {
+                    done.countDown();
+                }
+            });
+            Thread threadB = new Thread(() -> {
+                try {
+                    start.await();
+                    sessionMgr.enforceConcurrentSessionLimit("LimitUser", 
sessionB);
+                } catch (Throwable t) {
+                    error.compareAndSet(null, t);
+                } finally {
+                    done.countDown();
+                }
+            });
+
+            threadA.start();
+            threadB.start();
+            start.countDown();
+
+            assertTrue(done.await(5, TimeUnit.SECONDS));
+            assertNull(error.get());
+            verify(oldestSession, 
times(1)).setAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED, 
Boolean.TRUE);
+            verify(oldestSession, times(1)).invalidate();
+        } finally {
+            active.remove(oldestSession);
+        }
+    }
+
+    @Test
+    public void testIsBrowserUserAgent() {
+        assertTrue(SessionMgr.isBrowserUserAgent("Mozilla/5.0"));
+        assertTrue(SessionMgr.isBrowserUserAgent("Chrome/120.0"));
+        assertFalse(SessionMgr.isBrowserUserAgent("curl/8.0"));
+        assertFalse(SessionMgr.isBrowserUserAgent("Apache-HttpClient/4.5"));
+        assertFalse(SessionMgr.isBrowserUserAgent(null));
+        assertFalse(SessionMgr.isBrowserUserAgent(""));
+    }
+
+    @Test
+    public void testIsPluginOrSecureDownloadRequest() {
+        
assertTrue(SessionMgr.isPluginOrSecureDownloadRequest("/service/plugins/policies/download/hadoopdev"));
+        
assertTrue(SessionMgr.isPluginOrSecureDownloadRequest("/service/secure/policies/download/1"));
+        assertFalse(SessionMgr.isPluginOrSecureDownloadRequest("/index.html"));
+        assertFalse(SessionMgr.isPluginOrSecureDownloadRequest(null));
+        assertFalse(SessionMgr.isPluginOrSecureDownloadRequest(""));
+    }
+
+    private void setupAuthentication(String loginId) {
+        Authentication authentication = mock(Authentication.class);
+        WebAuthenticationDetails details = 
mock(WebAuthenticationDetails.class);
+        when(authentication.getDetails()).thenReturn(details);
+        when(authentication.getName()).thenReturn(loginId);
+        when(details.getSessionId()).thenReturn("httpSess1");
+        when(details.getRemoteAddress()).thenReturn("127.0.0.1");
+        SecurityContext sc = SecurityContextHolder.createEmptyContext();
+        sc.setAuthentication(authentication);
+        SecurityContextHolder.setContext(sc);
+    }
+
+    private XXPortalUser portalUser(String loginId, Long id) {
+        XXPortalUser portalUser = new XXPortalUser();
+        portalUser.setId(id);
+        portalUser.setLoginId(loginId);
+        return portalUser;
+    }
+
+    private void stubPortalUserLookup(XXPortalUser portalUser) {
+        XXPortalUserDao portalDao = mock(XXPortalUserDao.class);
+        when(daoManager.getXXPortalUser()).thenReturn(portalDao);
+        
when(portalDao.findByLoginId(portalUser.getLoginId())).thenReturn(portalUser);
+    }
+
+    private void stubRolesAndPermissions(XXPortalUser portalUser) {
+        XXPortalUserRoleDao roleDao = mock(XXPortalUserRoleDao.class);
+        when(daoManager.getXXPortalUserRole()).thenReturn(roleDao);
+        
when(roleDao.findByUserId(portalUser.getId())).thenReturn(Collections.emptyList());
+        XXUserDao xxUserDao = mock(XXUserDao.class);
+        when(daoManager.getXXUser()).thenReturn(xxUserDao);
+        XXUser xUser = new XXUser();
+        xUser.setId(portalUser.getId());
+        xUser.setName(portalUser.getLoginId());
+        
when(xxUserDao.findByUserName(portalUser.getLoginId())).thenReturn(xUser);
+        XXModuleDefDao moduleDefDao = mock(XXModuleDefDao.class);
+        when(daoManager.getXXModuleDef()).thenReturn(moduleDefDao);
+        when(moduleDefDao.findAccessibleModulesByUserId(portalUser.getId(), 
portalUser.getId())).thenReturn(Collections.emptyList());
+    }
+
+    private void stubAuthSessionCreate(Long id) {
+        XXAuthSession created = new XXAuthSession();
+        created.setId(id);
+        XXAuthSessionDao authDao = mock(XXAuthSessionDao.class);
+        when(daoManager.getXXAuthSession()).thenReturn(authDao);
+        when(authDao.create(any(XXAuthSession.class))).thenReturn(created);
+    }
+
+    private HttpSession mockUiSession(String loginId, Long userId, boolean 
ssoEnabled, long creationTime) {
+        return mockUiSession(loginId, userId, ssoEnabled, creationTime, null);
+    }
+
+    private HttpSession mockUiSession(String loginId, Long userId, boolean 
ssoEnabled, long creationTime, CountDownLatch concurrentExpiredReads) {
+        HttpSession httpSession = mock(HttpSession.class);
+        UserSessionBase userSession = new UserSessionBase();
+        XXPortalUser portalUser = portalUser(loginId, userId);
+        userSession.setXXPortalUser(portalUser);
+        userSession.setSSOEnabled(ssoEnabled);
+        RangerSecurityContext securityContext = new RangerSecurityContext();
+        securityContext.setUserSession(userSession);
+        Map<String, Object> attributes = new ConcurrentHashMap<>();
+        
attributes.put(RangerSecurityContextFormationFilter.AKA_SC_SESSION_KEY, 
securityContext);
+        when(httpSession.getAttribute(anyString())).thenAnswer(invocation -> {
+            String name = invocation.getArgument(0);
+
+            Object value = attributes.get(name);
+
+            if (concurrentExpiredReads != null
+                    && SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED.equals(name)
+                    && value == null) {
+                concurrentExpiredReads.countDown();
+
+                try {
+                    concurrentExpiredReads.await(250, TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+
+            return value;
+        });
+        lenient().doAnswer(invocation -> {
+            String name  = invocation.getArgument(0);
+            Object value = invocation.getArgument(1);
+
+            if (value == null) {
+                attributes.remove(name);
+            } else {
+                attributes.put(name, value);
+            }
+
+            return null;
+        }).when(httpSession).setAttribute(anyString(), any());
+        lenient().when(httpSession.getCreationTime()).thenReturn(creationTime);
+        return httpSession;
+    }
 }
diff --git 
a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerKRBAuthenticationFilter.java
 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerKRBAuthenticationFilter.java
new file mode 100644
index 000000000..0022d0d24
--- /dev/null
+++ 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerKRBAuthenticationFilter.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.security.web.filter;
+
+import org.apache.ranger.biz.SessionMgr;
+import org.apache.ranger.common.RangerConstants;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.mockito.Mockito;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Concurrent-session expiry handling for RangerKRBAuthenticationFilter 
(RANGER-5749).
+ * This class is new on ranger-2.10; master already had a broader filter test 
suite.
+ */
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestRangerKRBAuthenticationFilter {
+    @Test
+    public void testDoFilter_concurrentSessionExpiredSso_redirectsToKnox() 
throws Exception {
+        RangerKRBAuthenticationFilter filter = new 
RangerKRBAuthenticationFilter();
+
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
+        FilterChain chain = Mockito.mock(FilterChain.class);
+        HttpSession session = Mockito.mock(HttpSession.class);
+
+        when(res.getWriter()).thenReturn(new PrintWriter(new StringWriter()));
+        when(req.getSession(false)).thenReturn(session);
+        
when(session.getAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED)).thenReturn(Boolean.TRUE);
+        
when(session.getAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED_SSO)).thenReturn(Boolean.TRUE);
+        when(req.getRequestedSessionId()).thenReturn("sid");
+        when(req.getRequestURI()).thenReturn("/index.html");
+        when(req.getRequestURL()).thenReturn(new 
StringBuffer("http://localhost/index.html";));
+        when(req.getHeaderNames()).thenReturn(Collections.emptyEnumeration());
+        doNothing().when(res).sendRedirect(Mockito.anyString());
+        doNothing().when(session).invalidate();
+
+        filter.doFilter(req, res, chain);
+
+        verify(res, times(1)).sendRedirect(Mockito.anyString());
+        verify(session, times(1)).invalidate();
+        verify(chain, never()).doFilter(any(ServletRequest.class), 
any(ServletResponse.class));
+    }
+
+    @Test
+    public void 
testDoFilter_concurrentSessionExpiredFormLogin_redirectsToRangerLogin() throws 
Exception {
+        RangerKRBAuthenticationFilter filter = new 
RangerKRBAuthenticationFilter();
+
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
+        FilterChain chain = Mockito.mock(FilterChain.class);
+        HttpSession session = Mockito.mock(HttpSession.class);
+
+        when(req.getSession(false)).thenReturn(session);
+        
when(session.getAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED)).thenReturn(Boolean.TRUE);
+        
when(session.getAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED_SSO)).thenReturn(Boolean.FALSE);
+        when(req.getContextPath()).thenReturn("");
+        doNothing().when(res).sendRedirect(Mockito.anyString());
+        doNothing().when(session).invalidate();
+
+        filter.doFilter(req, res, chain);
+
+        verify(session, times(1)).invalidate();
+        verify(res, times(1)).sendRedirect("/login.jsp");
+        verify(chain, never()).doFilter(any(ServletRequest.class), 
any(ServletResponse.class));
+    }
+
+    @Test
+    public void 
testDoFilter_concurrentSessionExpiredFormLogin_ajaxReturnsTimeout() throws 
Exception {
+        RangerKRBAuthenticationFilter filter = new 
RangerKRBAuthenticationFilter();
+
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
+        FilterChain chain = Mockito.mock(FilterChain.class);
+        HttpSession session = Mockito.mock(HttpSession.class);
+
+        when(req.getSession(false)).thenReturn(session);
+        
when(session.getAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED)).thenReturn(Boolean.TRUE);
+        
when(session.getAttribute(SessionMgr.SESSION_ATTR_CONCURRENT_EXPIRED_SSO)).thenReturn(Boolean.FALSE);
+        when(req.getContextPath()).thenReturn("");
+        when(req.getHeader("X-Requested-With")).thenReturn("XMLHttpRequest");
+        doNothing().when(session).invalidate();
+
+        filter.doFilter(req, res, chain);
+
+        verify(session, times(1)).invalidate();
+        verify(res).setStatus(RangerConstants.SC_AUTHENTICATION_TIMEOUT);
+        verify(res).setHeader("X-Rngr-Redirect-Url", "/login.jsp");
+        verify(res, never()).sendRedirect(Mockito.anyString());
+        verify(chain, never()).doFilter(any(ServletRequest.class), 
any(ServletResponse.class));
+    }
+}

Reply via email to