From ec0c7d51621e09cedd980fd69a692e05b2945971 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Wed, 25 Jul 2012 16:58:36 -0400 Subject: [PATCH 1/2] CASC-166 Fix race condition in cached assertion cleanup. Perform assertion cleanup on same thread as JAAS module invocations to ensure that cleanup of expired assertions occurs before the cache is interrogated. A verifying test case accompanies this fix. The test case required a new module option, cacheTimeoutUnits, in order to complete on a time scale suitable for unit tests. --- .../jasig/cas/client/jaas/CasLoginModule.java | 82 ++++++++++++++----- .../cas/client/jaas/CasLoginModuleTests.java | 68 +++++++++++++-- .../src/test/resources/log4j.properties | 30 +++++++ 3 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 cas-client-core/src/test/resources/log4j.properties diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java index 58a3e0e..565d0ec 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java @@ -26,9 +26,14 @@ import java.beans.PropertyDescriptor; import java.io.IOException; import java.security.Principal; import java.security.acl.Group; -import java.util.*; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; @@ -79,6 +84,10 @@ import org.jasig.cas.client.validation.TicketValidator; * for JAAS providers that attempt to periodically reauthenticate to renew principal. * Since CAS tickets are one-time-use, a cached assertion must be provided on reauthentication. *
  • cacheTimeout (optional) - Assertion cache timeout in minutes.
  • + *
  • cacheTimeoutUnits (optional) - Assertion cache timeout units. + * MUST be one of following: + * Calendar.HOUR(10), Calendar.MINUTE(12), Calendar.SECOND(13), Calendar.MILLISECOND(14). + * Default unit is minutes.
  • * * *

    @@ -129,6 +138,9 @@ public class CasLoginModule implements LoginModule { */ public static final int DEFAULT_CACHE_TIMEOUT = 480; + /** Default assertion cache timeout unit is minutes. */ + public static final int DEFAULT_CACHE_TIMEOUT_UNITS = Calendar.MINUTE; + /** * Stores mapping of ticket to assertion to support JAAS providers that * attempt to periodically re-authenticate to renew principal. Since @@ -137,9 +149,6 @@ public class CasLoginModule implements LoginModule { */ protected static final Map ASSERTION_CACHE = new HashMap(); - /** Executor responsible for assertion cache cleanup */ - protected static Executor cacheCleanerExecutor = Executors.newSingleThreadExecutor(); - /** Logger instance */ protected final Log log = LogFactory.getLog(getClass()); @@ -182,8 +191,20 @@ public class CasLoginModule implements LoginModule { /** Assertion cache timeout in minutes */ protected int cacheTimeout = DEFAULT_CACHE_TIMEOUT; + /** + * Units of cache timeout. Must be one of following {@link Calendar} time unit constants: + *

    + */ + protected int cacheTimeoutUnits = DEFAULT_CACHE_TIMEOUT_UNITS; + /** * Initializes the CAS login module. + * * @param subject Authentication subject. * @param handler Callback handler. * @param state Shared state map. @@ -201,10 +222,12 @@ public class CasLoginModule implements LoginModule { * which by default are single use, reauthentication fails. Assertion caching addresses this * behavior. *
  • cacheTimeout (optional) - assertion cache timeout in minutes.
  • + *
  • cacheTimeoutUnits (optional) - Assertion cache timeout units. + * MUST be one of following: + * Calendar.HOUR(10), Calendar.MINUTE(12), Calendar.SECOND(13), Calendar.MILLISECOND(14). + * Default unit is minutes.
  • * */ - - public final void initialize(final Subject subject, final CallbackHandler handler, final Map state, final Map options) { this.assertion = null; this.callbackHandler = handler; @@ -245,11 +268,20 @@ public class CasLoginModule implements LoginModule { } else if ("cacheTimeout".equals(key)) { this.cacheTimeout = Integer.parseInt((String) options.get(key)); log.debug("Set cacheTimeout=" + this.cacheTimeout); + } else if ("cacheTimeoutUnits".equals(key)) { + final int units = Integer.parseInt((String) options.get(key)); + if (units == Calendar.HOUR || units == Calendar.MINUTE || + units == Calendar.SECOND || units == Calendar.MILLISECOND) { + this.cacheTimeoutUnits = units; + log.debug("Set cacheTimeoutUnits=" + this.cacheTimeoutUnits); + } else { + throw new IllegalArgumentException("Invalid time unit constant " + units); + } } } if (this.cacheAssertions) { - cacheCleanerExecutor.execute(new CacheCleaner()); + cleanCache(); } CommonUtils.assertNotNull(ticketValidatorClass, "ticketValidatorClass is required."); @@ -301,7 +333,8 @@ public class CasLoginModule implements LoginModule { final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName() : this.service; if (this.cacheAssertions) { - synchronized(ASSERTION_CACHE) { + // Multiple threads may be accessing the static cache concurrently + synchronized (ASSERTION_CACHE) { if (ASSERTION_CACHE.get(ticket) != null) { log.debug("Assertion found in cache."); this.assertion = ASSERTION_CACHE.get(ticket); @@ -425,7 +458,10 @@ public class CasLoginModule implements LoginModule { if (log.isDebugEnabled()) { log.debug("Caching assertion for principal " + this.assertion.getPrincipal()); } - ASSERTION_CACHE.put(this.ticket, this.assertion); + // Multiple threads may be accessing the static cache concurrently + synchronized (ASSERTION_CACHE) { + ASSERTION_CACHE.put(this.ticket, this.assertion); + } } } else { // Login must have failed if there is no assertion defined @@ -554,23 +590,26 @@ public class CasLoginModule implements LoginModule { this.subject.getPrivateCredentials().removeAll(this.subject.getPrivateCredentials(clazz)); } - /** Removes expired entries from the assertion cache. */ - private class CacheCleaner implements Runnable { - public void run() { - if (log.isDebugEnabled()) { - log.debug("Cleaning assertion cache of size " + CasLoginModule.ASSERTION_CACHE.size()); - } - final Iterator> iter = - CasLoginModule.ASSERTION_CACHE.entrySet().iterator(); + + /** + * Removes expired entries from the assertion cache. + */ + private void cleanCache() { + if (log.isDebugEnabled()) { + log.debug("Cleaning assertion cache of size " + ASSERTION_CACHE.size()); + } + // Multiple threads may be accessing the static cache concurrently + synchronized (ASSERTION_CACHE) { + final Iterator> iter = ASSERTION_CACHE.entrySet().iterator(); final Calendar cutoff = Calendar.getInstance(); - cutoff.add(Calendar.MINUTE, -CasLoginModule.this.cacheTimeout); + cutoff.add(this.cacheTimeoutUnits, -this.cacheTimeout); while (iter.hasNext()) { final Assertion assertion = iter.next().getValue(); final Calendar created = Calendar.getInstance(); created.setTime(assertion.getValidFromDate()); if (created.before(cutoff)) { if (log.isDebugEnabled()) { - log.debug("Removing expired assertion for principal " + assertion.getPrincipal()); + log.debug("Removing expired assertion for principal " + assertion.getPrincipal()); } iter.remove(); } @@ -578,3 +617,4 @@ public class CasLoginModule implements LoginModule { } } } + diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java index 623730a..1f84d23 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java @@ -28,13 +28,16 @@ import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.login.LoginException; -import static org.junit.Assert.*; - import org.jasig.cas.client.PublicTestHttpServer; -import org.junit.AfterClass; +import org.jasig.cas.client.validation.TicketValidationException; import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * Unit test for {@link CasLoginModule} class. * @@ -150,15 +153,16 @@ public class CasLoginModuleTests { final String USERNAME = "username"; final String SERVICE = "https://example.com/service"; final String TICKET = "ST-300000-aA5Yuvrxzpv8Tau1cYQ7-srv1"; - final String RESPONSE1 = "" + final String SUCCESS_RESPONSE = "" + "" + USERNAME + ""; - final String RESPONSE2 = "Ticket ST-300000-aA5Yuvrxzpv8Tau1cYQ7-srv1 not recognized"; - server.content = RESPONSE1.getBytes(server.encoding); - + final String FAILURE_RESPONSE = "Ticket ST-300000-aA5Yuvrxzpv8Tau1cYQ7-srv1 not recognized"; + options.put("cacheAssertions", "true"); options.put("cacheTimeout", "1"); + + server.content = SUCCESS_RESPONSE.getBytes(server.encoding); module.initialize( subject, new ServiceAndTicketCallbackHandler(SERVICE, TICKET), @@ -173,7 +177,7 @@ public class CasLoginModuleTests { module.logout(); assertEquals(0, subject.getPrincipals().size()); assertEquals(0, subject.getPrivateCredentials().size()); - server.content = RESPONSE2.getBytes(server.encoding); + server.content = FAILURE_RESPONSE.getBytes(server.encoding); module.initialize( subject, new ServiceAndTicketCallbackHandler(SERVICE, TICKET), @@ -184,6 +188,54 @@ public class CasLoginModuleTests { assertEquals(this.subject.getPrincipals().size(), 3); assertEquals(TICKET, this.subject.getPrivateCredentials().iterator().next().toString()); } + + + /** + * Verify that cached assertions that are expired are never be accessible + * by {@link org.jasig.cas.client.jaas.CasLoginModule#login()} method. + * + * @throws Exception On errors. + */ + @Test + public void testAssertionCachingExpiration() throws Exception { + final String USERNAME = "hizzy"; + final String SERVICE = "https://example.com/service"; + final String TICKET = "ST-12345-ABCDEFGHIJKLMNOPQRSTUVWXYZ-hosta"; + final String SUCCESS_RESPONSE = "" + + "" + + USERNAME + + ""; + final String FAILURE_RESPONSE = "Ticket ST-12345-ABCDEFGHIJKLMNOPQRSTUVWXYZ-hosta not recognized"; + + options.put("cacheAssertions", "true"); + // Cache timeout is 1 second (Calendar.SECOND=13) + options.put("cacheTimeoutUnits", "13"); + options.put("cacheTimeout", "1"); + + server.content = SUCCESS_RESPONSE.getBytes(server.encoding); + module.initialize( + subject, + new ServiceAndTicketCallbackHandler(SERVICE, TICKET), + new HashMap(), + options); + assertTrue(module.login()); + module.commit(); + + Thread.sleep(1100); + // Assertion should now be expired from cache + server.content = FAILURE_RESPONSE.getBytes(server.encoding); + module.initialize( + subject, + new ServiceAndTicketCallbackHandler(SERVICE, TICKET), + new HashMap(), + options); + try { + module.login(); + fail("Should have thrown login exception."); + } catch (LoginException e) { + assertTrue(e.getCause() instanceof TicketValidationException); + } + } private boolean hasPrincipalName(final Subject subject, final Class principalClass, final String name) { final Set principals = subject.getPrincipals(principalClass); diff --git a/cas-client-core/src/test/resources/log4j.properties b/cas-client-core/src/test/resources/log4j.properties new file mode 100644 index 0000000..8ccad3b --- /dev/null +++ b/cas-client-core/src/test/resources/log4j.properties @@ -0,0 +1,30 @@ +# +# log4j configuration to get clean console listing during Maven tests +# + +# +# Licensed to Jasig under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Jasig licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +log4j.rootCategory=INFO, stdout +log4j.logger.org.apache.xml.security=OFF +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-5p %d{ISO8601} %t::%c{1} - %m%n + +log4j.logger.org.jasig.cas=DEBUG From ee2b71909846af8dd46e8b903803b59ff834a6eb Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Thu, 26 Jul 2012 10:25:06 -0400 Subject: [PATCH 2/2] CASC-166 Address code review feedback. Use ConcurrentHashMap to avoid explicit synchronization. Use TimeUnit to allow more user-friendly configuration of the units of the cache timeout (e.g. MINUTES, SECONDS) and rename option from cacheTimeoutUnits to cacheTimeoutUnit for consistency. --- .../jasig/cas/client/jaas/CasLoginModule.java | 108 ++++++++---------- .../cas/client/jaas/CasLoginModuleTests.java | 4 +- 2 files changed, 49 insertions(+), 63 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java index 565d0ec..02454e8 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java @@ -34,6 +34,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; @@ -84,10 +85,8 @@ import org.jasig.cas.client.validation.TicketValidator; * for JAAS providers that attempt to periodically reauthenticate to renew principal. * Since CAS tickets are one-time-use, a cached assertion must be provided on reauthentication. *
  • cacheTimeout (optional) - Assertion cache timeout in minutes.
  • - *
  • cacheTimeoutUnits (optional) - Assertion cache timeout units. - * MUST be one of following: - * Calendar.HOUR(10), Calendar.MINUTE(12), Calendar.SECOND(13), Calendar.MILLISECOND(14). - * Default unit is minutes.
  • + *
  • cacheTimeoutUnit (optional) - Assertion cache timeout unit. Must be one of {@link TimeUnit} enumeration + * names, e.g. DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS. Default unit is MINUTES.
  • * * *

    @@ -139,7 +138,7 @@ public class CasLoginModule implements LoginModule { public static final int DEFAULT_CACHE_TIMEOUT = 480; /** Default assertion cache timeout unit is minutes. */ - public static final int DEFAULT_CACHE_TIMEOUT_UNITS = Calendar.MINUTE; + public static final TimeUnit DEFAULT_CACHE_TIMEOUT_UNIT = TimeUnit.MINUTES; /** * Stores mapping of ticket to assertion to support JAAS providers that @@ -191,16 +190,8 @@ public class CasLoginModule implements LoginModule { /** Assertion cache timeout in minutes */ protected int cacheTimeout = DEFAULT_CACHE_TIMEOUT; - /** - * Units of cache timeout. Must be one of following {@link Calendar} time unit constants: - *

      - *
    • {@link Calendar#HOUR}
    • - *
    • {@link Calendar#MINUTE}
    • - *
    • {@link Calendar#SECOND}
    • - *
    • {@link Calendar#MILLISECOND}
    • - *
    - */ - protected int cacheTimeoutUnits = DEFAULT_CACHE_TIMEOUT_UNITS; + /** Units of cache timeout. */ + protected TimeUnit cacheTimeoutUnit = DEFAULT_CACHE_TIMEOUT_UNIT; /** * Initializes the CAS login module. @@ -222,17 +213,20 @@ public class CasLoginModule implements LoginModule { * which by default are single use, reauthentication fails. Assertion caching addresses this * behavior. *
  • cacheTimeout (optional) - assertion cache timeout in minutes.
  • - *
  • cacheTimeoutUnits (optional) - Assertion cache timeout units. - * MUST be one of following: - * Calendar.HOUR(10), Calendar.MINUTE(12), Calendar.SECOND(13), Calendar.MILLISECOND(14). - * Default unit is minutes.
  • + *
  • cacheTimeoutUnit (optional) - Assertion cache timeout unit. Must be one of {@link TimeUnit} enumeration + * names, e.g. DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS. Default unit is MINUTES.
  • * */ - public final void initialize(final Subject subject, final CallbackHandler handler, final Map state, final Map options) { + public final void initialize( + final Subject subject, + final CallbackHandler handler, + final Map state, + final Map options) { + this.assertion = null; this.callbackHandler = handler; this.subject = subject; - this.sharedState = (Map) state; + this.sharedState = (Map) state; this.sharedState = new HashMap(state); String ticketValidatorClass = null; @@ -268,15 +262,9 @@ public class CasLoginModule implements LoginModule { } else if ("cacheTimeout".equals(key)) { this.cacheTimeout = Integer.parseInt((String) options.get(key)); log.debug("Set cacheTimeout=" + this.cacheTimeout); - } else if ("cacheTimeoutUnits".equals(key)) { - final int units = Integer.parseInt((String) options.get(key)); - if (units == Calendar.HOUR || units == Calendar.MINUTE || - units == Calendar.SECOND || units == Calendar.MILLISECOND) { - this.cacheTimeoutUnits = units; - log.debug("Set cacheTimeoutUnits=" + this.cacheTimeoutUnits); - } else { - throw new IllegalArgumentException("Invalid time unit constant " + units); - } + } else if ("cacheTimeoutUnit".equals(key)) { + this.cacheTimeoutUnit = Enum.valueOf(TimeUnit.class, (String) options.get(key)); + log.debug("Set cacheTimeoutUnit=" + this.cacheTimeoutUnit); } } @@ -325,20 +313,19 @@ public class CasLoginModule implements LoginModule { throw (LoginException) new LoginException("IO exception in callback handler: " + e).initCause(e); } catch (final UnsupportedCallbackException e) { log.info("Login failed due to unsupported callback: " + e); - throw (LoginException) new LoginException("Callback handler does not support PasswordCallback and TextInputCallback.").initCause(e); + throw (LoginException) new LoginException( + "Callback handler does not support PasswordCallback and TextInputCallback.").initCause(e); } if (ticketCallback.getPassword() != null) { this.ticket = new TicketCredential(new String(ticketCallback.getPassword())); - final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName() : this.service; + final String service = CommonUtils.isNotBlank( + serviceCallback.getName()) ? serviceCallback.getName() : this.service; if (this.cacheAssertions) { - // Multiple threads may be accessing the static cache concurrently - synchronized (ASSERTION_CACHE) { - if (ASSERTION_CACHE.get(ticket) != null) { - log.debug("Assertion found in cache."); - this.assertion = ASSERTION_CACHE.get(ticket); - } + this.assertion = ASSERTION_CACHE.get(ticket); + if (this.assertion != null) { + log.debug("Assertion found in cache."); } } @@ -346,7 +333,8 @@ public class CasLoginModule implements LoginModule { log.debug("CAS assertion is null; ticket validation required."); if (CommonUtils.isBlank(service)) { log.info("Login failed because required CAS service parameter not provided."); - throw new LoginException("Neither login module nor callback handler provided required service parameter."); + throw new LoginException( + "Neither login module nor callback handler provided required service parameter."); } try { if (log.isDebugEnabled()) { @@ -413,7 +401,8 @@ public class CasLoginModule implements LoginModule { throw new LoginException("Ticket credential not found."); } - final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(), this.assertion); + final AssertionPrincipal casPrincipal = new AssertionPrincipal( + this.assertion.getPrincipal().getName(), this.assertion); this.subject.getPrincipals().add(casPrincipal); // Add group containing principal as sole member @@ -458,10 +447,7 @@ public class CasLoginModule implements LoginModule { if (log.isDebugEnabled()) { log.debug("Caching assertion for principal " + this.assertion.getPrincipal()); } - // Multiple threads may be accessing the static cache concurrently - synchronized (ASSERTION_CACHE) { - ASSERTION_CACHE.put(this.ticket, this.assertion); - } + ASSERTION_CACHE.put(this.ticket, this.assertion); } } else { // Login must have failed if there is no assertion defined @@ -522,10 +508,12 @@ public class CasLoginModule implements LoginModule { * @return Ticket validator with properties set. */ private TicketValidator createTicketValidator(final String className, final Map propertyMap) { - CommonUtils.assertTrue(propertyMap.containsKey("casServerUrlPrefix"), "Required property casServerUrlPrefix not found."); + CommonUtils.assertTrue( + propertyMap.containsKey("casServerUrlPrefix"), "Required property casServerUrlPrefix not found."); final Class validatorClass = ReflectUtils.loadClass(className); - final TicketValidator validator = ReflectUtils.newInstance(validatorClass, propertyMap.get("casServerUrlPrefix")); + final TicketValidator validator = ReflectUtils.newInstance( + validatorClass, propertyMap.get("casServerUrlPrefix")); try { final BeanInfo info = Introspector.getBeanInfo(validatorClass); @@ -570,7 +558,8 @@ public class CasLoginModule implements LoginModule { } else if (long.class.equals(pd.getPropertyType())) { return new Long(value); } else { - throw new IllegalArgumentException("No conversion strategy exists for property " + pd.getName() + " of type " + pd.getPropertyType()); + throw new IllegalArgumentException( + "No conversion strategy exists for property " + pd.getName() + " of type " + pd.getPropertyType()); } } @@ -598,21 +587,18 @@ public class CasLoginModule implements LoginModule { if (log.isDebugEnabled()) { log.debug("Cleaning assertion cache of size " + ASSERTION_CACHE.size()); } - // Multiple threads may be accessing the static cache concurrently - synchronized (ASSERTION_CACHE) { - final Iterator> iter = ASSERTION_CACHE.entrySet().iterator(); - final Calendar cutoff = Calendar.getInstance(); - cutoff.add(this.cacheTimeoutUnits, -this.cacheTimeout); - while (iter.hasNext()) { - final Assertion assertion = iter.next().getValue(); - final Calendar created = Calendar.getInstance(); - created.setTime(assertion.getValidFromDate()); - if (created.before(cutoff)) { - if (log.isDebugEnabled()) { - log.debug("Removing expired assertion for principal " + assertion.getPrincipal()); - } - iter.remove(); + final Iterator> iter = ASSERTION_CACHE.entrySet().iterator(); + final Calendar cutoff = Calendar.getInstance(); + cutoff.setTimeInMillis(System.currentTimeMillis() - this.cacheTimeoutUnit.toMillis(this.cacheTimeout)); + while (iter.hasNext()) { + final Assertion assertion = iter.next().getValue(); + final Calendar created = Calendar.getInstance(); + created.setTime(assertion.getValidFromDate()); + if (created.before(cutoff)) { + if (log.isDebugEnabled()) { + log.debug("Removing expired assertion for principal " + assertion.getPrincipal()); } + iter.remove(); } } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java index 1f84d23..ede32b0 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/jaas/CasLoginModuleTests.java @@ -208,8 +208,8 @@ public class CasLoginModuleTests { final String FAILURE_RESPONSE = "Ticket ST-12345-ABCDEFGHIJKLMNOPQRSTUVWXYZ-hosta not recognized"; options.put("cacheAssertions", "true"); - // Cache timeout is 1 second (Calendar.SECOND=13) - options.put("cacheTimeoutUnits", "13"); + // Cache timeout is 1 second + options.put("cacheTimeoutUnit", "SECONDS"); options.put("cacheTimeout", "1"); server.content = SUCCESS_RESPONSE.getBytes(server.encoding);