【问题标题】:Grails isn't responding with a 401 during ajax request and timed out sessionGrails 在 ajax 请求和超时会话期间没有响应 401
【发布时间】:2016-06-21 14:36:27
【问题描述】:

我正在使用 grails 以及 spring security 和 angularjs。当用户会话过期并且用户单击页面上的 ajax 操作而不是响应 401 时,应用程序会尝试重定向到原始 ajax 操作没有响应的登录页面。

我仍在使用传统的登录页面,并且我的一些应用程序仍然有一些传统的页面链接,所以当会话过期并且用户单击页面链接时,我想重定向到登录页面。

如果用户单击 ajax 请求,我希望获得 401 响应而不是重定向的 html 响应,以便我可以在我的 javascript 中进行重定向。

我有以下配置设置。

grails.plugin.springsecurity.providerNames = ['hriLoginClientAuthenticationProvider']
grails.plugin.springsecurity.useSecurityEventListener = true
grails.plugin.springsecurity.failureHandler.defaultFailureUrl = '/login?error=1'
grails.plugin.springsecurity.auth.loginFormUrl = '/login'
grails.plugin.springsecurity.logout.postOnly = false

我需要做什么才能让 ajax 请求不重定向到登录页面?

【问题讨论】:

标签: angularjs grails spring-security


【解决方案1】:

我遇到了类似的问题,并在过滤器链中实现了一个过滤器,以检测 AJAX 请求并以自定义的 HTTP 状态进行响应(如果您愿意,可以将其更改为 401)。

这基本上分为三个部分。首先,是过滤器。它是一个 servlet 过滤器,用于检查请求以及会话中的身份验证状态。其次,将过滤器定义为Resources.groovy 中应用程序上下文中的bean。最后,将其插入到 Spring Security 过滤器链中,我在 Bootstrap.groovy 中完成了。

我现在将引导您完成此操作。

首先是servlet过滤器(在src/java下)

package com.xyz.security;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.security.web.util.ThrowableCauseExtractor;
import org.springframework.web.filter.GenericFilterBean;

public class AjaxTimeoutRedirectFilter extends GenericFilterBean {

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

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
    private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();

    private int customSessionExpiredErrorCode = 901;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        try {
            chain.doFilter(request, response);

            // logger.debug("Chain processed normally");
        } catch (IOException ex) {
            throw ex;
        } catch (Exception ex) {
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class,
                            causeChain);

            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer
                        .getFirstThrowableOfType(AccessDeniedException.class,
                                causeChain);
            }

            if (ase != null) {
                if (ase instanceof AuthenticationException) {
                    throw ase;
                } else if (ase instanceof AccessDeniedException) {

                    if (authenticationTrustResolver
                            .isAnonymous(SecurityContextHolder.getContext()
                                    .getAuthentication())) {
                        // logger.info("User session expired or not logged in yet");
                        String ajaxHeader = ((HttpServletRequest) request)
                                .getHeader("X-Requested-With");

                        if ("XMLHttpRequest".equals(ajaxHeader)) {
                            // logger.info("Ajax call detected, send {} error code",
                            // this.customSessionExpiredErrorCode);
                            HttpServletResponse resp = (HttpServletResponse) response;
                            resp.sendError(this.customSessionExpiredErrorCode);
                        } else {
                            // logger.info("Redirect to login page");
                            throw ase;
                        }
                    } else {
                        throw ase;
                    }
                }
            }

        }
    }

    private static final class DefaultThrowableAnalyzer extends
            ThrowableAnalyzer {
        /**
         * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
         */
        protected void initExtractorMap() {
            super.initExtractorMap();

            registerExtractor(ServletException.class,
                    new ThrowableCauseExtractor() {
                        public Throwable extractCause(Throwable throwable) {
                            ThrowableAnalyzer.verifyThrowableHierarchy(
                                    throwable, ServletException.class);
                            return ((ServletException) throwable)
                                    .getRootCause();
                        }
                    });
        }

    }

    public void setCustomSessionExpiredErrorCode(
            int customSessionExpiredErrorCode) {
        this.customSessionExpiredErrorCode = customSessionExpiredErrorCode;
    }
}

其次,将过滤器定义为Resources.groovy中应用上下文中的bean

beans = {
    ajaxTimeoutRedirectFilter(com.xyz.security.AjaxTimeoutRedirectFilter)
}

最后,将过滤器放入 Spring Security 过滤器链(为此我使用了BootStrap.groovy

import grails.plugin.springsecurity.SecurityFilterPosition
import grails.plugin.springsecurity.SpringSecurityUtils
class BootStrap {

    def init = { servletContext ->

        SpringSecurityUtils.clientRegisterFilter('ajaxTimeoutRedirectFilter', SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order + 10)

    }
    def destroy = {
    }
}

【讨论】:

    【解决方案2】:

    您是否考虑过当用户在客户端空闲时“锁定屏幕”?当然,您应该在服务器端处理会话结束,但实际上它似乎比等待来自客户端的操作更清洁和更安全的解决方案(特别是如果用户在屏幕上留下并留下了一些敏感数据)。

    查看这个ng-idle 指令。

    【讨论】:

      猜你喜欢
      • 2011-04-25
      • 2012-04-23
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多