注销和会话失效后,JSF PrimeFaces ajax请求

时间:2015-11-03 16:08:38

标签: spring jsf primefaces spring-boot tomcat8

在我的Spring Boot1.2.7 / JSF2.2.12 / PrimeFaces5.2 / Tomcat 8应用程序中,我试图在已执行/ logout的网站上调用AJAX后实现重定向到登录页面。

为此,我添加了JsfRedirectStrategy:

/**
* Inspired by <a href=
* "http://stackoverflow.com/questions/10143539/jsf-2-spring-security-3-x-and-richfaces-4-redirect-to-login-page-on-session-tim">StackOverflow.com</a> 
* and by <a href=http://www.icesoft.org/wiki/display/ICE/Spring+Security#SpringSecurity-Step4%3AConfigureYourSpringSecurityredirectStrategy">
* Spring Security 3 and ICEfaces 3 Tutorial</a>.
*
* @author banterCZ
*/
public class JsfRedirectStrategy implements InvalidSessionStrategy {

    final static Logger logger = LoggerFactory.getLogger(JsfRedirectStrategy.class);

    private static final String FACES_REQUEST_HEADER = "faces-request";

    private String invalidSessionUrl;

    /**
     * {@inheritDoc}
     */
    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        boolean ajaxRedirect = "partial/ajax".equals(request.getHeader(FACES_REQUEST_HEADER));
        if (ajaxRedirect) {
            String contextPath = request.getContextPath();
            String redirectUrl = contextPath + invalidSessionUrl;
            logger.debug("Session expired due to ajax request, redirecting to '{}'", redirectUrl);

            String ajaxRedirectXml = createAjaxRedirectXml(redirectUrl);
            logger.debug("Ajax partial response to redirect: {}", ajaxRedirectXml);

            response.setContentType("text/xml");
            response.getWriter().write(ajaxRedirectXml);
        } else {
            String requestURI = getRequestUrl(request);
            logger.debug(
                    "Session expired due to non-ajax request, starting a new session and redirect to requested url '{}'",
                    requestURI);
            request.getSession(true);
            response.sendRedirect(requestURI);
        }

    }

    private String getRequestUrl(HttpServletRequest request) {
        StringBuffer requestURL = request.getRequestURL();

        String queryString = request.getQueryString();
        if (StringUtils.hasText(queryString)) {
            requestURL.append("?").append(queryString);
        }

        return requestURL.toString();
    }

    private String createAjaxRedirectXml(String redirectUrl) {
        return new StringBuilder().append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
                .append("<partial-response><redirect url=\"").append(redirectUrl)
                .append("\"></redirect></partial-response>").toString();
    }

    public void setInvalidSessionUrl(String invalidSessionUrl) {
        this.invalidSessionUrl = invalidSessionUrl;
    }

}

这是我的WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .addFilterBefore(sessionManagementFilter(), AnonymousAuthenticationFilter.class)
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/invite.xhtml").permitAll()
            .antMatchers("/forgotpassword.xhtml").permitAll()
            .antMatchers("/resetpwd.xhtml").permitAll()
            .antMatchers("/admin/**").hasRole(Roles.ROLE_ADMIN.getSpringSecName())
            .antMatchers("/**").authenticated()
            .antMatchers("/actuator/**").permitAll()
            .and()
                .formLogin()
                    .loginPage("/login.xhtml").permitAll()
                    //.failureUrl("/login?error").permitAll()
            .and()
                .logout()
                    .logoutUrl("/logout")
                    .logoutRequestMatcher( new AntPathRequestMatcher("/logout"))
                    .logoutSuccessUrl("/login.xhtml")
                    .invalidateHttpSession(true)
                    .deleteCookies("JSESSIONID")
                    .permitAll();

        http.headers().frameOptions().disable();
     // @formatter:on
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/javax.faces.resource/**");
    }

    @Bean
    public SessionManagementFilter sessionManagementFilter() {
        SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(httpSessionSecurityContextRepository()); 
        sessionManagementFilter.setInvalidSessionStrategy(jsfRedirectStrategy());
        return sessionManagementFilter;
    }

    public HttpSessionSecurityContextRepository httpSessionSecurityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }

    @Bean
    public JsfRedirectStrategy jsfRedirectStrategy() {
        JsfRedirectStrategy jsfRedirectStrategy = new JsfRedirectStrategy();
        jsfRedirectStrategy.setInvalidSessionUrl("/login.xhtml");
        return jsfRedirectStrategy;
    }

}

这是退出链接:

<div id="LogoutContainer" class="PFTopLinks floatRight boldFont">
    <h:form rendered="#{not empty request.remoteUser}">
        <h:graphicImage name="main/images/pfPush.svg" />
        <h:outputLink value="${pageContext.request.contextPath}/logout">
            <span class="PFDarkText">Logout</span>
        </h:outputLink>
    </h:form>
</div>

问题:现在{AJ} JSF调用永远不会调用,因为JsfRedirectStrategy.onInvalidSessionDetected中的request.isRequestedSessionIdValid()始终返回{{ 1}}。 注销后我有一个SessionManagementFilter.doFilter()

的实例

我的代码出了什么问题?

1 个答案:

答案 0 :(得分:4)

我已使用以下代码重新实现此方法(基于此主题http://forum.primefaces.org/viewtopic.php?f=3&t=33380):

我添加了AjaxTimeoutPhaseListener阶段监听器:

public class AjaxTimeoutPhaseListener implements PhaseListener {

    private static final long serialVersionUID = 2639152532235352192L;

    public static Logger logger = LoggerFactory.getLogger(AjaxTimeoutPhaseListener.class);

    @Override
    public void afterPhase(PhaseEvent ev) {
    }

    @Override
    public void beforePhase(PhaseEvent ev) {
        FacesContext fc = FacesUtils.getContext();
        RequestContext rc = RequestContext.getCurrentInstance();
        HttpServletResponse response = FacesUtils.getResponse();
        HttpServletRequest request = FacesUtils.getRequest();

        if (FacesUtils.getExternalContext().getUserPrincipal() == null) {
            if (FacesUtils.getExternalContext().isResponseCommitted()) {
                // redirect is not possible
                return;
            }
            try {
                if (((rc != null && rc.isAjaxRequest())
                        || (fc != null && fc.getPartialViewContext().isPartialRequest()))
                        && fc.getResponseWriter() == null && fc.getRenderKit() == null) {

                    response.setCharacterEncoding(request.getCharacterEncoding());
                    RenderKitFactory factory = (RenderKitFactory) FactoryFinder
                            .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
                    RenderKit renderKit = factory.getRenderKit(fc,
                            fc.getApplication().getViewHandler().calculateRenderKitId(fc));
                    ResponseWriter responseWriter = renderKit.createResponseWriter(response.getWriter(), null,
                            request.getCharacterEncoding());
                    responseWriter = new PartialResponseWriter(responseWriter);
                    fc.setResponseWriter(responseWriter);

                    FacesUtils.redirect("/login.xhtml");
                }
            } catch (IOException ex) {
                StringBuilder error = new StringBuilder("Redirect to the specified page '");
                error.append("/login.xhtml");
                error.append("' failed");
                logger.error(error.toString(), ex);
                throw new FacesException(ex);
            }
        } else {
            return; // This is not a timeout case . Do nothing !
        }
    }

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

}

还添加了FacesUtils类(从OmniFaces lib中提取):

public class FacesUtils {

    public static Logger logger = LoggerFactory.getLogger(FacesUtils.class);

    /**
     * Returns the current faces context.
     * <p>
     * <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
     * submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
     * task.</i>
     * @return The current faces context.
     * @see FacesContext#getCurrentInstance()
     */
    public static FacesContext getContext() {
        return FacesContext.getCurrentInstance();
    }

    /**
     * Returns the HTTP servlet response.
     * <p>
     * <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
     * submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
     * task.</i>
     * @return The HTTP servlet response.
     * @see ExternalContext#getResponse()
     */
    public static HttpServletResponse getResponse() {
        return getResponse(getContext());
    }

    /**
     * {@inheritDoc}
     * @see Faces#getResponse()
     */
    public static HttpServletResponse getResponse(FacesContext context) {
        return (HttpServletResponse) context.getExternalContext().getResponse();
    }

    /**
     * Returns the HTTP servlet request.
     * <p>
     * <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
     * submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
     * task.</i>
     * @return The HTTP servlet request.
     * @see ExternalContext#getRequest()
     */
    public static HttpServletRequest getRequest() {
        return getRequest(getContext());
    }

    /**
     * {@inheritDoc}
     * @see Faces#getRequest()
     */
    public static HttpServletRequest getRequest(FacesContext context) {
        return (HttpServletRequest) context.getExternalContext().getRequest();
    }

    /**
     * Returns the current external context.
     * <p>
     * <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
     * submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
     * task.</i>
     * @return The current external context.
     * @see FacesContext#getExternalContext()
     */
    public static ExternalContext getExternalContext() {
        return getContext().getExternalContext();
    }

    /**
     * Returns the HTTP request context path. It's the webapp context name, with a leading slash. If the webapp runs
     * on context root, then it returns an empty string.
     * @return The HTTP request context path.
     * @see ExternalContext#getRequestContextPath()
     */
    public static String getRequestContextPath() {
        return getRequestContextPath(getContext());
    }

    /**
     * {@inheritDoc}
     * @see Faces#getRequestContextPath()
     */
    public static String getRequestContextPath(FacesContext context) {
        return context.getExternalContext().getRequestContextPath();
    }

    /**
     * Does a regular or ajax redirect.
     */
    public static void redirect(String redirectPage) throws FacesException {
        checkViewRoot(FacesUtils.getContext(), FacesUtils.getRequestContextPath());

        FacesContext fc = FacesUtils.getContext();
        ExternalContext ec = fc.getExternalContext();

        try {
            if (ec.isResponseCommitted()) {
                // redirect is not possible
                return;
            }

            // fix for renderer kit (Mojarra's and PrimeFaces's ajax redirect)
            if ((RequestContext.getCurrentInstance().isAjaxRequest() || fc.getPartialViewContext().isPartialRequest())
                    && fc.getResponseWriter() == null && fc.getRenderKit() == null) {
                ServletResponse response = (ServletResponse) ec.getResponse();
                ServletRequest request = (ServletRequest) ec.getRequest();
                response.setCharacterEncoding(request.getCharacterEncoding());

                RenderKitFactory factory = (RenderKitFactory) FactoryFinder
                        .getFactory(FactoryFinder.RENDER_KIT_FACTORY);

                RenderKit renderKit = factory.getRenderKit(fc,
                        fc.getApplication().getViewHandler().calculateRenderKitId(fc));

                ResponseWriter responseWriter = renderKit.createResponseWriter(response.getWriter(), null,
                        request.getCharacterEncoding());
                fc.setResponseWriter(responseWriter);
            }

            ec.redirect(ec.getRequestContextPath() + (redirectPage != null ? redirectPage : ""));
        } catch (IOException e) {
            logger.error("Redirect to the specified page '" + redirectPage + "' failed");
            throw new FacesException(e);
        }
    }

    public static void checkViewRoot(FacesContext ctx, String viewId) {
        if (ctx.getViewRoot() == null) {
            UIViewRoot viewRoot = ctx.getApplication().getViewHandler().createView(ctx, viewId);
            if (viewRoot != null) {
                ctx.setViewRoot(viewRoot);
            }
        }
    }

}

还在faces-config.xml添加了以下行:

<lifecycle>
    <phase-listener>com.domain.AjaxTimeoutPhaseListener</phase-listener>
</lifecycle>

现在一切正常