在ajax请求和超时会话期间,Grails没有响应401

时间:2016-03-07 21:05:30

标签: angularjs grails spring-security

我正在使用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请求不重定向到登录页面,我需要做什么?

2 个答案:

答案 0 :(得分: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 = {
    }
}

答案 1 :(得分:0)

您是否考虑过"锁定屏幕"当用户在客户端闲置时?当然,您应该在服务器端处理会话结束,但事实上它似乎比等待来自客户端的操作更清晰,更安全(特别是如果用户在屏幕上左右移动了一些敏感数据)。

查看此ng-idle指令。