diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java index 8ae5894..c89a4cf 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java @@ -33,19 +33,24 @@ import org.jasig.cas.client.validation.Assertion; public class AssertionPrincipal extends SimplePrincipal implements Serializable { /** AssertionPrincipal.java */ - private static final long serialVersionUID = 2288520214366461693L; + private static final long serialVersionUID = 1572526837434920575L; /** CAS assertion describing authenticated state */ private Assertion assertion; + /** Service principal ID. */ + private String servicePrincipal; + /** * Creates a new principal containing the CAS assertion. * * @param name Principal name. + * @param servicePrincipal Service principal ID. * @param assertion CAS assertion. */ - public AssertionPrincipal(final String name, final Assertion assertion) { + public AssertionPrincipal(final String name, final String servicePrincipal, final Assertion assertion) { super(name); + this.servicePrincipal = servicePrincipal; this.assertion = assertion; } @@ -55,4 +60,11 @@ public class AssertionPrincipal extends SimplePrincipal implements Serializable public Assertion getAssertion() { return this.assertion; } + + /** + * @return Service principal ID. + */ + public String getServicePrincipal() { + return servicePrincipal; + } } 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 5deb87e..3b9e5e3 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 @@ -27,6 +27,9 @@ import java.security.Principal; import java.security.acl.Group; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import javax.security.auth.Subject; import javax.security.auth.callback.*; import javax.security.auth.login.LoginException; @@ -134,6 +137,10 @@ public class CasLoginModule implements LoginModule { */ protected static final Map ASSERTION_CACHE = new HashMap(); + /** Unique suffix pattern appended to some service URLs to make them unique. */ + protected static final Pattern UNIQUE_SUFFIX_PATTERN = + Pattern.compile(".*;uuid=\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}"); + /** Logger instance */ protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -146,9 +153,12 @@ public class CasLoginModule implements LoginModule { /** CAS ticket validator */ protected TicketValidator ticketValidator; - /** CAS service parameter used if no service is provided via TextCallback on login */ + /** CAS ticket validation URL. */ protected String service; + /** Service principal. */ + protected String servicePrincipal; + /** CAS assertion */ protected Assertion assertion; @@ -179,6 +189,16 @@ public class CasLoginModule implements LoginModule { /** Units of cache timeout. */ protected TimeUnit cacheTimeoutUnit = DEFAULT_CACHE_TIMEOUT_UNIT; + + /** + * @return Unique suffix suitable for appending to service URLs to make them unique. + */ + public static String createUniqueSuffix() + { + return ";uuid=" + UUID.randomUUID(); + } + + /** * Initializes the CAS login module. * @@ -302,11 +322,20 @@ public class CasLoginModule implements LoginModule { if (ticketCallback.getPassword() != null) { this.ticket = new TicketCredential(new String(ticketCallback.getPassword())); - final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName() - : this.service; - + if (CommonUtils.isBlank(this.service)) { + this.service = serviceCallback.getName(); + final Matcher m = UNIQUE_SUFFIX_PATTERN.matcher(this.service); + if (m.matches()) { + this.servicePrincipal = this.service; + this.service = m.replaceFirst(""); + } else { + this.servicePrincipal = this.service + createUniqueSuffix(); + } + } else { + this.servicePrincipal = this.service + createUniqueSuffix(); + } if (this.cacheAssertions) { - this.assertion = ASSERTION_CACHE.get(ticket); + this.assertion = ASSERTION_CACHE.get(this.ticket); if (this.assertion != null) { logger.debug("Assertion found in cache."); } @@ -320,9 +349,11 @@ public class CasLoginModule implements LoginModule { "Neither login module nor callback handler provided required service parameter."); } try { - logger.debug("Attempting ticket validation with service={} and ticket={}", service, + logger.debug( + "Attempting ticket validation with service={} and ticket={}", + this.service, this.ticket); - this.assertion = this.ticketValidator.validate(this.ticket.getName(), service); + this.assertion = this.ticketValidator.validate(this.ticket.getName(), this.service); } catch (final Exception e) { logger.info("Login failed due to CAS ticket validation failure: {}", e); @@ -383,7 +414,9 @@ public class CasLoginModule implements LoginModule { throw new LoginException("Ticket credential not found."); } - final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(), + final AssertionPrincipal casPrincipal = new AssertionPrincipal( + this.assertion.getPrincipal().getName(), + this.servicePrincipal, this.assertion); this.subject.getPrincipals().add(casPrincipal); @@ -396,7 +429,7 @@ public class CasLoginModule implements LoginModule { // Add group principal containing role data final Group roleGroup = new SimpleGroup(this.roleGroupName); - for (final String defaultRole : defaultRoles) { + for (final String defaultRole : this.defaultRoles) { roleGroup.addMember(new SimplePrincipal(defaultRole)); } @@ -417,9 +450,9 @@ public class CasLoginModule implements LoginModule { this.subject.getPrincipals().add(roleGroup); // Place principal name in shared state for downstream JAAS modules (module chaining use case) - this.sharedState.put(LOGIN_NAME, assertion.getPrincipal().getName()); + this.sharedState.put(LOGIN_NAME, this.assertion.getPrincipal().getName()); - logger.debug("Created JAAS subject with principals: {}", subject.getPrincipals()); + logger.debug("Created JAAS subject with principals: {}", this.subject.getPrincipals()); if (this.cacheAssertions) { logger.debug("Caching assertion for principal {}", this.assertion.getPrincipal()); diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/SerializationTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/SerializationTests.java index 18a6e9c..297401f 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/SerializationTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/SerializationTests.java @@ -72,7 +72,7 @@ public class SerializationTests extends TestCase { final SimpleGroup simpleGroup = new SimpleGroup("group"); final AttributePrincipalImpl attributePrincipal = new AttributePrincipalImpl("attr", Collections. singletonMap("LOA", "3")); - final AssertionPrincipal assertionPrincipal = new AssertionPrincipal("assertion", new AssertionImpl( + final AssertionPrincipal assertionPrincipal = new AssertionPrincipal("assertion", "foo", new AssertionImpl( attributePrincipal, Collections. singletonMap("authenticationMethod", "username"))); return new Object[] { simplePrincipal, simpleGroup, attributePrincipal, assertionPrincipal, }; diff --git a/cas-client-integration-atlassian/pom.xml b/cas-client-integration-atlassian/pom.xml index 74b9821..29e92ff 100644 --- a/cas-client-integration-atlassian/pom.xml +++ b/cas-client-integration-atlassian/pom.xml @@ -560,7 +560,7 @@ atlassian Atlassian Repository - http://repository.atlassian.com/maven2/ + https://maven.atlassian.com/content/groups/public/ diff --git a/cas-client-integration-jboss/src/main/java/org/jasig/cas/client/jboss/authentication/WebAuthenticationFilter.java b/cas-client-integration-jboss/src/main/java/org/jasig/cas/client/jboss/authentication/WebAuthenticationFilter.java index cb4c45a..8f0cff9 100644 --- a/cas-client-integration-jboss/src/main/java/org/jasig/cas/client/jboss/authentication/WebAuthenticationFilter.java +++ b/cas-client-integration-jboss/src/main/java/org/jasig/cas/client/jboss/authentication/WebAuthenticationFilter.java @@ -20,6 +20,8 @@ package org.jasig.cas.client.jboss.authentication; import java.io.IOException; import java.security.GeneralSecurityException; +import java.util.UUID; + import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; @@ -28,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.jasig.cas.client.jaas.AssertionPrincipal; +import org.jasig.cas.client.jaas.CasLoginModule; import org.jasig.cas.client.util.AbstractCasFilter; import org.jasig.cas.client.util.CommonUtils; import org.jboss.web.tomcat.security.login.WebAuthentication; @@ -61,7 +64,7 @@ public final class WebAuthenticationFilter extends AbstractCasFilter { try { final String service = constructServiceUrl(request, response); logger.debug("Attempting CAS ticket validation with service={} and ticket={}", service, ticket); - if (!new WebAuthentication().login(service, ticket)) { + if (!new WebAuthentication().login(service + CasLoginModule.createUniqueSuffix(), ticket)) { logger.debug("JBoss Web authentication failed."); throw new GeneralSecurityException("JBoss Web authentication failed."); } diff --git a/pom.xml b/pom.xml index 179dd69..44de109 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ 4.0.0 org.jasig.cas.client - 3.3-SNAPSHOT + 3.3-jaas-unique-principal cas-client pom