1515
1616import static jakarta .faces .application .StateManager .STATE_SAVING_METHOD_CLIENT ;
1717import static jakarta .faces .application .StateManager .STATE_SAVING_METHOD_PARAM_NAME ;
18+ import static jakarta .ws .rs .core .MediaType .TEXT_PLAIN ;
1819import static org .apache .shiro .SecurityUtils .getSecurityManager ;
1920import static org .apache .shiro .SecurityUtils .isSecurityManagerTypeOf ;
2021import static org .apache .shiro .SecurityUtils .unwrapSecurityManager ;
5859import static org .apache .shiro .ee .listeners .IniEnvironment .hasFacesContext ;
5960import static org .apache .shiro .web .filter .authc .NoAccessFilter .FORM_RESUBMIT_CHECK_SERVLET_PATH ;
6061import static org .apache .shiro .web .mgt .CookieRememberMeManager .DEFAULT_REMEMBER_ME_COOKIE_NAME ;
62+ import java .util .concurrent .atomic .AtomicReference ;
63+ import java .util .function .Consumer ;
6164import java .util .regex .Pattern ;
6265import java .util .stream .Collectors ;
6366import jakarta .servlet .ServletContext ;
@@ -99,6 +102,7 @@ public class FormResubmitSupport {
99102 static final String FORM_IS_RESUBMITTED = "org.apache.shiro.form-is-resubmitted" ;
100103 static final String FORM_RESUBMIT_WHITELIST = "org.apache.shiro.form-resubmit-whitelist" ;
101104 static final String FORM_RESUBMIT_BLACKLIST = "org.apache.shiro.form-resubmit-blacklist" ;
105+ static final String FORM_DATA_CACHE = "org.apache.shiro.form-data-cache" ;
102106 // encoded view state
103107 private static final String FACES_VIEW_STATE = "jakarta.faces.ViewState" ;
104108 private static final String FACES_VIEW_STATE_EQUALS = FACES_VIEW_STATE + "=" ;
@@ -112,7 +116,6 @@ public class FormResubmitSupport {
112116 = Pattern .compile ("[\\ &]?(%s.\\ w+|%s.\\ w+|%s)=[\\ w\\ s:%%\\ d]*" .formatted (
113117 "jakarta.faces.partial" , "jakarta.faces.behavior" , FACES_SOURCE ));
114118 private static final Pattern INITIAL_AMPERSAND = Pattern .compile ("^\\ &" );
115- private static final String FORM_DATA_CACHE = "org.apache.shiro.form-data-cache" ;
116119 private static final String FORM_RESUBMIT_HOST = "org.apache.shiro.form-resubmit-host" ;
117120 private static final String FORM_RESUBMIT_PORT = "org.apache.shiro.form-resubmit-port" ;
118121 private static final Optional <String > RESUBMIT_HOST = Optional .ofNullable (System .getProperty (FORM_RESUBMIT_HOST ));
@@ -203,7 +206,7 @@ static String getPostData(ServletRequest request) {
203206 return request .getReader ().lines ().collect (Collectors .joining ());
204207 }
205208
206- static String getSavedFormDataFromKey (@ NonNull String savedFormDataKey ) {
209+ static String getSavedFormDataFromKey (@ NonNull String savedFormDataKey , Consumer < Cache < Object , ?>> cacheConsumer ) {
207210 String savedFormData = null ;
208211 if (isSecurityManagerTypeOf (getSecurityManager (), DefaultSecurityManager .class )) {
209212 DefaultSecurityManager dsm = getSecurityManager (DefaultSecurityManager .class );
@@ -218,7 +221,7 @@ static String getSavedFormDataFromKey(@NonNull String savedFormDataKey) {
218221 } else {
219222 savedFormData = (String ) cache .get (cacheKey );
220223 }
221- cache . remove ( cacheKey );
224+ cacheConsumer . accept ( cache );
222225 }
223226 }
224227 return savedFormData ;
@@ -343,14 +346,21 @@ private static void doRedirectToSaved(HttpServletRequest request, HttpServletRes
343346 String savedFormDataKey = Servlets .getRequestCookie (request , SHIRO_FORM_DATA_KEY );
344347 boolean doRedirectAtEnd = true ;
345348 if (savedFormDataKey != null && resubmit ) {
346- String formData = getSavedFormDataFromKey (savedFormDataKey );
347- if (formData != null ) {
348- Optional .ofNullable (resubmitSavedForm (formData , savedRequest ,
349- request , response , request .getServletContext (), false , true ))
350- .ifPresent (path -> doFacesRedirect (request , response , path ));
351- doRedirectAtEnd = false ;
352- } else {
353- deleteCookie (response , request .getServletContext (), SHIRO_FORM_DATA_KEY );
349+ AtomicReference <Cache <Object , ?>> cache = new AtomicReference <>();
350+ String formData = getSavedFormDataFromKey (savedFormDataKey , cache ::set );
351+ try {
352+ if (formData != null ) {
353+ Optional .ofNullable (resubmitSavedForm (formData , savedFormDataKey , savedRequest ,
354+ request , response , request .getServletContext (), false , true ))
355+ .ifPresent (path -> doFacesRedirect (request , response , path ));
356+ doRedirectAtEnd = false ;
357+ } else {
358+ deleteCookie (response , request .getServletContext (), SHIRO_FORM_DATA_KEY );
359+ }
360+ } finally {
361+ if (cache .get () != null ) {
362+ cache .get ().remove (savedFormDataKey );
363+ }
354364 }
355365 }
356366 if (doRedirectAtEnd ) {
@@ -417,7 +427,7 @@ static boolean isLoginUrl(HttpServletRequest request) {
417427 return loginUrl != null && request .getRequestURI ().equals (request .getContextPath () + loginUrl );
418428 }
419429
420- static String resubmitSavedForm (@ NonNull String savedFormData , @ NonNull String savedRequest ,
430+ static String resubmitSavedForm (@ NonNull String savedFormData , String savedFormDataKey , @ NonNull String savedRequest ,
421431 HttpServletRequest originalRequest , HttpServletResponse originalResponse ,
422432 ServletContext servletContext , boolean rememberedAjaxResubmit , boolean redirect )
423433 throws InterruptedException , IOException {
@@ -433,10 +443,13 @@ static String resubmitSavedForm(@NonNull String savedFormData, @NonNull String s
433443 return resubmitResponseCleanup (originalRequest );
434444 }
435445 URI overriddenRequestURI = overrideSavedRequestURI (URI .create (savedRequest ));
436- HttpClient client = buildHttpClient (overriddenRequestURI , servletContext , originalRequest );
437- if (!checkWhitelist (servletContext , overriddenRequestURI , client )) {
446+ var cookieManager = new CookieManager ();
447+ HttpClient client = HttpClient .newBuilder ().connectTimeout (Duration .ofSeconds (2 ))
448+ .cookieHandler (cookieManager ).build ();
449+ if (!checkWhitelist (servletContext , overriddenRequestURI , client , savedFormDataKey )) {
438450 return savedRequest ;
439451 }
452+ initializeCookies (overriddenRequestURI , servletContext , cookieManager , originalRequest );
440453 HttpResponse <String > response ;
441454 PartialAjaxResult decodedFormData ;
442455 try {
@@ -564,9 +577,8 @@ private static String resubmitResponseCleanup(HttpServletRequest originalRequest
564577 return null ;
565578 }
566579
567- private static HttpClient buildHttpClient (URI savedRequest , ServletContext servletContext ,
568- HttpServletRequest originalRequest ) {
569- CookieManager cookieManager = new CookieManager ();
580+ private static void initializeCookies (URI savedRequest , ServletContext servletContext ,
581+ CookieManager cookieManager , HttpServletRequest originalRequest ) {
570582 var session = SecurityUtils .getSubject ().getSession ();
571583 var sessionCookieName = getSessionCookieName (servletContext , getSecurityManager ());
572584 var sessionCookie = new HttpCookie (sessionCookieName , session .getId ().toString ());
@@ -589,10 +601,10 @@ private static HttpClient buildHttpClient(URI savedRequest, ServletContext servl
589601 }
590602 }
591603 }
592- return HttpClient .newBuilder ().connectTimeout (Duration .ofSeconds (2 )).cookieHandler (cookieManager ).build ();
593604 }
594605
595- private static boolean checkWhitelist (ServletContext servletContext , URI savedRequestURI , HttpClient client ) {
606+ private static boolean checkWhitelist (ServletContext servletContext , URI savedRequestURI , HttpClient client ,
607+ String savedFormDataKey ) {
596608 if (!isSecurityManagerTypeOf (getSecurityManager (), DefaultSecurityManager .class )) {
597609 log .warn ("Shiro SecurityManager is not configured for form resubmit whitelist caching" );
598610 return false ;
@@ -612,7 +624,7 @@ private static boolean checkWhitelist(ServletContext servletContext, URI savedRe
612624 } else if (isBlacklisted (blacklist , authority )) {
613625 log .debug ("Form resubmit blacklist cache hit for {}" , savedRequestURI );
614626 return false ;
615- } else if (checkWhitelistClient (savedRequestURI , servletContext .getContextPath (), client )) {
627+ } else if (checkWhitelistClient (savedRequestURI , servletContext .getContextPath (), client , savedFormDataKey )) {
616628 putWhitelistEntry (whitelist , authority );
617629 blacklist .remove (authority );
618630 return true ;
@@ -675,7 +687,8 @@ static boolean isBlacklisted(Cache<String, Long> blacklist, String authority,
675687 return active ;
676688 }
677689
678- private static boolean checkWhitelistClient (URI savedRequestURI , String contextPath , HttpClient client ) {
690+ private static boolean checkWhitelistClient (URI savedRequestURI , String contextPath , HttpClient client ,
691+ String savedFormDataKey ) {
679692 try {
680693 var rememberMeManager = getRememberMeManager ();
681694 if (rememberMeManager == null || rememberMeManager .getCipherService () == null
@@ -687,11 +700,13 @@ private static boolean checkWhitelistClient(URI savedRequestURI, String contextP
687700 var request = HttpRequest .newBuilder ()
688701 .uri (URI .create ("%s://%s%s%s" .formatted (savedRequestURI .getScheme (), savedRequestURI .getAuthority (),
689702 contextPath , FORM_RESUBMIT_CHECK_SERVLET_PATH )))
690- .timeout (Duration .ofSeconds (3 )).GET ().build ();
703+ .timeout (Duration .ofSeconds (3 )).header (CONTENT_TYPE , TEXT_PLAIN )
704+ .POST (HttpRequest .BodyPublishers .ofString (rememberMeManager .getCipherService ()
705+ .encrypt (savedFormDataKey .getBytes (StandardCharsets .UTF_8 ),
706+ rememberMeManager .getEncryptionCipherKey ()).toBase64 ())).build ();
691707 var response = client .send (request , HttpResponse .BodyHandlers .ofString ());
692708
693- if (response .statusCode () == OK && Objects .equals (decrypt (response .body (), rememberMeManager ),
694- SecurityUtils .getSubject ().getSession ().getId ().toString ())) {
709+ if (response .statusCode () == OK && Objects .equals (decrypt (response .body (), rememberMeManager ), savedFormDataKey )) {
695710 log .debug ("Form resubmit whitelist check succeeded for {}" , savedRequestURI );
696711 return true ;
697712 } else {
0 commit comments