[jsf] javax.faces.application.ViewExpiredException :보기를 복원 할 수 없습니다

컨테이너 관리 보안으로 간단한 응용 프로그램을 작성했습니다. 문제는 로그인하고 로그 아웃 한 다른 페이지를 열면 첫 페이지로 돌아와서 링크 등을 클릭하거나 페이지를 새로 고치면이 예외가 발생합니다. 로그 아웃하고 세션이 파괴 되었기 때문에 정상적인 것 같습니다. 예를 들어 index.xhtml 또는 login.xhtml로 사용자를 리디렉션하고 해당 오류 페이지 / 메시지를 보지 못하게하려면 어떻게해야합니까?

즉, 로그 아웃 한 후 다른 페이지를 인덱스 / 로그인 페이지로 자동 리디렉션하는 방법은 무엇입니까?

여기있어:

javax.faces.application.ViewExpiredException: viewId:/index.xhtml - View /index.xhtml could not be restored.
    at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:212)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:110)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:312)
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:343)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
    at filter.HttpHttpsFilter.doFilter(HttpHttpsFilter.java:66)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:277)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188)
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:641)
    at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97)
    at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:85)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185)
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:325)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:226)
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165)
    at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791)
    at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693)
    at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954)
    at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170)
    at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88)
    at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76)
    at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53)
    at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57)
    at com.sun.grizzly.ContextTask.run(ContextTask.java:69)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309)
    at java.lang.Thread.run(Thread.java:619)



답변

소개

ViewExpiredException이 때마다 발생합니다 javax.faces.STATE_SAVING_METHOD으로 설정 server(기본값)와 최종 사용자가를 통해보기에 HTTP POST 요청을 전송 <h:form>과 함께 <h:commandLink>, <h:commandButton>또는 <f:ajax>관련 뷰 상태가 더 이상 세션에서 사용할 수없는 상태.

뷰 상태는 숨겨진 입력 필드의 값으로 식별된다 javax.faces.ViewState<h:form>. 상태 저장 방법을로 설정 server하면 세션의 직렬화 된보기 상태를 참조하는보기 상태 ID 만 포함됩니다. 따라서 어떤 이유로 세션이 만료 된 경우 (서버 또는 클라이언트 측에서 시간 초과되거나 브라우저에서 또는 HttpSession#invalidate()서버 를 호출하여 세션 쿠키가 더 이상 유지되지 않거나 세션 쿠키가 있는 서버 특정 버그로 인해 세션 쿠키가 더 이상 유지되지 않는 경우) WildFly 에서 알려진 경우 직렬화 된보기 상태를 더 이상 세션에서 사용할 수 없으며 최종 사용자는이 예외를받습니다. 세션의 작동을 이해하려면 서블릿 작동 방법을 참조하십시오 . 인스턴스화, 세션, 공유 변수 및 멀티 스레딩 .

JSF가 세션에 저장할 뷰의 양에도 제한이 있습니다. 한계에 도달하면 가장 최근에 사용한보기가 만료됩니다. com.sun.faces.numberOfViewsInSession vs. com.sun.faces.numberOfLogicalViews 도 참조하십시오 .

상태 저장 방법을로 설정 client하면 javax.faces.ViewState숨겨진 입력 필드에 전체 직렬화 된보기 상태가 포함되므로 ViewExpiredException세션이 만료 될 때 최종 사용자가 시간을 얻지 못합니다 . 그러나 여전히 클러스터 환경 ( “오류 : MAC이 확인하지 않음”이 증상 임) 및 / 또는 클라이언트 측 상태에 구현 별 시간 초과가 있거나 서버가 다시 시작하는 동안 AES 키를 다시 생성 할 때 발생할 수 있습니다. 상태 저장 방법이 클라이언트로 설정되어 있고 사용자 세션이 올바른 해결 방법 인 동안 클러스터 환경에서 ViewExpiredException 얻기를 참조하십시오 .

해결책에 관계없이를 사용 하지 마십시오 enableRestoreView11Compatibility. 원래보기 상태를 전혀 복원하지는 않습니다. 기본적으로 뷰와 모든 관련 뷰 범위 Bean을 처음부터 다시 작성하므로 모든 원본 데이터 (상태)가 손실됩니다. 응용 프로그램이 혼란스러운 방식으로 작동하므로 ( “안녕하세요, 입력 값은 어디에 있습니까?.”) 사용자 경험에 매우 좋지 않습니다. 상태 비 저장 뷰를보다 잘 사용하거나 <o:enableRestorableView>모든 뷰가 아닌 특정 뷰에서만 관리 할 수 ​​있습니다.

받는 사람으로 이유 : JSF이 응답에 뷰 상태, 머리를 저장해야하는 이유 JSF는 서버에 UI 구성 요소의 상태를 저장?

페이지 탐색시 ViewExpiredException 방지

ViewExpiredException상태 저장이로 설정된 경우 로그 아웃 후 다시 탐색하는 경우 를 피하려면 로그 아웃 server후 POST 요청을 리디렉션하는 것만으로는 충분하지 않습니다. 또한 브라우저에 동적 JSF 페이지를 캐시 하지 않도록 지시해야 합니다. 그렇지 않으면 브라우저에서 GET 요청을 보낼 때 서버에서 새 페이지를 요청하는 대신 브라우저에서 캐시에서 해당 페이지를 표시 할 수 있습니다 (예 : 뒤로 단추).

javax.faces.ViewState캐시 된 페이지 의 숨겨진 필드는 현재 세션에서 더 이상 유효하지 않은보기 상태 ID 값을 포함 할 수 있습니다. 페이지 간 탐색을 위해 GET (정규 링크 / 버튼) 대신 POST (명령 링크 / 버튼)를 사용하고 캐시 된 페이지에서 해당 명령 링크 / 버튼을 클릭하면 (ab) 로 실패합니다 ViewExpiredException.

JSF 2.0에서 로그 아웃 한 후 리디렉션을 발사, 하나 추가하려면 <redirect />받는 사람 <navigation-case>(있는 경우) 질문에, 또는 추가 ?faces-redirect=true받는 outcome값.

<h:commandButton value="Logout" action="logout?faces-redirect=true" />

또는

public String logout() {
    // ...
    return "index?faces-redirect=true";
}

브라우저가 동적 JSF 페이지를 캐시하지 않도록 지시하려면 Filter의 서블릿 이름에 맵핑 된를 작성 FacesServlet하고 필요한 응답 헤더를 추가하여 브라우저 캐시를 사용 안함으로 설정하십시오. 예 :

@WebFilter(servletNames={"Faces Servlet"}) // Must match <servlet-name> of your FacesServlet.
public class NoCacheFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        if (!req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
            res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
            res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
            res.setDateHeader("Expires", 0); // Proxies.
        }

        chain.doFilter(request, response);
    }

    // ...
}

페이지 새로 고침시 ViewExpiredException 방지

ViewExpiredException상태 저장이로 설정되어있을 때 현재 페이지를 새로 고치지 않으 server려면 GET (일반 링크 / 버튼)에 의해서만 페이지 간 탐색을 수행해야 할뿐만 아니라 확인해야합니다. 양식을 제출하기 위해 독점적으로 ajax를 사용하고 있습니다. 어쨌든 양식을 동기식 (ajaj가 아닌)으로 제출하는 경우 뷰를 상태 비 저장 (나중 섹션 참조) 또는 POST 후 리디렉션을 보내는 것이 가장 좋습니다 (이전 섹션 참조).

갖는 ViewExpiredException페이지 새로 고침하는 것은 기본 구성에서 매우 드문 경우입니다. JSF가 세션에 저장할 뷰 수의 한계에 도달 한 경우에만 발생할 수 있습니다. 따라서 수동으로 제한을 너무 낮게 설정하거나 “백그라운드”에서 새보기를 지속적으로 생성하는 경우에만 발생합니다 (예 : 동일한 페이지에서 잘못 구현 된 아약스 폴 또는 잘못 구현 됨 404). 같은 페이지의 깨진 이미지에 오류 페이지). 해당 제한에 대한 자세한 내용은 com.sun.faces.numberOfViewsInSession과 com.sun.faces.numberOfLogicalViews 를 참조하십시오 . 또 다른 원인은 런타임 클래스 경로에서 서로 충돌하는 중복 JSF 라이브러리가 있습니다. JSF를 설치하는 올바른 절차는 JSF 위키 페이지에 요약되어 있습니다.

ViewExpiredException 처리

당신은 피할 수를 처리 할 때 ViewExpiredException다른 탭 / 창에서 로그 아웃하는 동안 이미 일부 브라우저 탭 / 창에서 열립니다 된 임의의 페이지에 POST 작업 후, 당신은을 지정하고 싶습니다 error-page에 있음을 위해 web.xml하는 간다 “세션 시간이 초과되었습니다”페이지로 이동하십시오. 예 :

<error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>

실제로 홈 페이지 나 로그인 페이지로 다시 리디렉션 하려는 경우 오류 페이지에서 메타 새로 고침 헤더를 사용 하십시오.

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Session expired</title>
        <meta http-equiv="refresh" content="0;url=#{request.contextPath}/login.xhtml" />
    </head>
    <body>
        <h1>Session expired</h1>
        <h3>You will be redirected to login page</h3>
        <p><a href="#{request.contextPath}/login.xhtml">Click here if redirect didn't work or when you're impatient</a>.</p>
    </body>
</html>

( 0in content은 리디렉션 전의 시간 (초) 을 나타내 0므로 “즉시 리디렉션”을 의미 3합니다.

ajax 요청 중에 예외를 처리하려면 특수한 것이 필요합니다 ExceptionHandler. JSF / PrimeFaces ajax 요청에 대한 세션 제한 시간 및 ViewExpiredException 처리 도 참조하십시오 . OmniFaces FullAjaxExceptionHandler쇼케이스 페이지 에서 실제 예제를 찾을 수 있습니다 (이것은 또한 비 요약 요청도 포함합니다).

또한 “일반”오류 페이지에 매핑해야주의 <error-code>500대신의 <exception-type>예의 java.lang.Exceptionjava.lang.Throwable, 그렇지 않은 경우에 싸여 모든 예외 ServletException등은 ViewExpiredException여전히 일반 오류 페이지에 끝날 것입니다. web.xml의 java.lang.Throwable error-page에 표시된 ViewExpiredException 도 참조하십시오 .

<error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>

상태 비 저장 뷰

완전히 다른 대안은 상태 비 저장 모드에서 JSF보기를 실행하는 것입니다. 이 방법으로 JSF 상태는 저장되지 않으며 뷰는 절대 만료되지 않지만 모든 요청에서 처음부터 다시 작성됩니다. 의 transient속성을 <f:view>로 설정하여 상태 비 저장 뷰를 켤 수 있습니다 true.

<f:view transient="true">

</f:view>

이 방법으로 javax.faces.ViewState숨겨진 필드는 "stateless"Mojarra에서 고정 값을 얻습니다 (이 시점에서 MyFaces를 확인하지 않았습니다). 이 기능은 Mojarra 2.1.19 및 2.2.0에서 도입 되었으며 이전 버전에서는 사용할 수 없습니다.

결과적으로 더 이상 뷰 범위 Bean을 사용할 수 없습니다. 이제 요청 범위 Bean처럼 작동합니다. 단점 중 하나는 숨겨진 입력 및 / 또는 느슨한 요청 매개 변수를 사용하여 상태를 직접 추적해야한다는 것입니다. 주로 입력과 필드와 그 형태 rendered, readonly또는 disabled아약스 이벤트에 의해 제어되는 속성이 영향을받습니다.

가 있습니다 <f:view>반드시에만 마스터 템플릿보기 및 / 또는 상주 고유하지 않아도됩니다. 또한 템플릿 클라이언트에 다시 선언하고 중첩하는 것이 합법적입니다. 기본적으로 부모 <f:view>를 “확장”합니다 . 예 : 마스터 템플릿 :

<f:view contentType="text/html">
    <ui:insert name="content" />
</f:view>

그리고 템플릿 클라이언트에서 :

<ui:define name="content">
    <f:view transient="true">
        <h:form>...</h:form>
    </f:view>
</f:view>

당신도 포장 할 수 <f:view>A의를 <c:if>이를 조건 적으로 만든다. 위의 예 와 같이 중첩 된 내용뿐만 아니라 전체 보기 에 적용됩니다 <h:form>.

또한보십시오


구체적인 문제와 관련이없는 순수한 페이지 간 탐색에 HTTP POST를 사용하는 것은 사용자 / SEO 친화적이 아닙니다. JSF 2.0에서는 일반 바닐라 페이지 간 탐색을 선호 <h:link>하거나 그 <h:button>이상 이어야합니다 <h:commandXxx>.

예를 들어 대신

<h:form id="menu">
    <h:commandLink value="Foo" action="foo?faces-redirect=true" />
    <h:commandLink value="Bar" action="bar?faces-redirect=true" />
    <h:commandLink value="Baz" action="baz?faces-redirect=true" />
</h:form>

더 잘해

<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />

또한보십시오


답변

에 아래 줄을 추가 해 보셨습니까 web.xml?

<context-param>
   <param-name>com.sun.faces.enableRestoreView11Compatibility</param-name>
   <param-value>true</param-value>
</context-param>

이 문제가 발생했을 때 이것이 매우 효과적이라는 것을 알았습니다.


답변

web.xml 을 변경하기 전에 먼저 ManagedBean을 확인해야합니다 implements Serializable.

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

특히 MyFaces 를 사용하는 경우


답변

Richface에서 다중 파트 양식을 피하십시오.

<h:form enctype="multipart/form-data">
    <a4j:poll id="poll" interval="10000"/>
</h:form>

Richfaces를 사용하는 경우 멀티 파트 양식 내부의 ajax 요청이 각 요청마다 새로운 View ID를 반환한다는 것을 알았습니다.

디버깅하는 방법 :

각 아약스 요청에서 View ID가 반환됩니다. View ID가 항상 같은 한 괜찮습니다. 각 요청에서 새로운 View ID를 얻는다면 문제가있는 것이므로 수정해야합니다.


답변

자신 만의 커스텀 AjaxExceptionHandler 또는 Primfaces-extensions를 사용하고

faces-config.xml 업데이트

...
<factory>
  <exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...

jsf 페이지에 다음 코드 추가

...
<pe:ajaxErrorHandler />
...


답변

이 오류가 발생했습니다 : javax.faces.application.ViewExpiredException. 다른 요청을 사용할 때 서버를 다시 시작한 후에도 동일한 JsessionId가있는 것을 발견했습니다. 브라우저 캐시 때문입니다. 브라우저를 닫고 시도하면 작동합니다.


답변

우리의 페이지가 x 시간 동안 유휴 상태 일 때 뷰가 만료되고 javax.faces.application.ViewExpiredException이 발생하여 하나의 솔루션이 발생하지 않도록 ViewHandler를 확장하고 restoreView 메소드를 재정의하는 CustomViewHandler를 작성하고 다른 모든 메소드는 부모의

import java.io.IOException;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

public class CustomViewHandler extends ViewHandler {
    private ViewHandler parent;

    public CustomViewHandler(ViewHandler parent) {
        //System.out.println("CustomViewHandler.CustomViewHandler():Parent View Handler:"+parent.getClass());
        this.parent = parent;
    }

    @Override
    public UIViewRoot restoreView(FacesContext facesContext, String viewId) {
    /**
     * {@link javax.faces.application.ViewExpiredException}. This happens only  when we try to logout from timed out pages.
     */
        UIViewRoot root = null;
        root = parent.restoreView(facesContext, viewId);
        if(root == null) {
            root = createView(facesContext, viewId);
        }
        return root;
    }

    @Override
    public Locale calculateLocale(FacesContext facesContext) {
        return parent.calculateLocale(facesContext);
    }

    @Override
    public String calculateRenderKitId(FacesContext facesContext) {
        String renderKitId = parent.calculateRenderKitId(facesContext);
        //System.out.println("CustomViewHandler.calculateRenderKitId():RenderKitId: "+renderKitId);
        return renderKitId;
    }

    @Override
    public UIViewRoot createView(FacesContext facesContext, String viewId) {
        return parent.createView(facesContext, viewId);
    }

    @Override
    public String getActionURL(FacesContext facesContext, String actionId) {
        return parent.getActionURL(facesContext, actionId);
    }

    @Override
    public String getResourceURL(FacesContext facesContext, String resId) {
        return parent.getResourceURL(facesContext, resId);
    }

    @Override
    public void renderView(FacesContext facesContext, UIViewRoot viewId) throws IOException, FacesException {
        parent.renderView(facesContext, viewId);
    }

    @Override
    public void writeState(FacesContext facesContext) throws IOException {
        parent.writeState(facesContext);
    }

    public ViewHandler getParent() {
        return parent;
    }

}   

그런 다음 faces-config.xml에 추가해야합니다.

<application>
    <view-handler>com.demo.CustomViewHandler</view-handler>
</application>

아래 링크의 원래 답변에 감사드립니다 :
http://www.gregbugaj.com/?p=164