Add support for unique service principal.
Some JAAS frameworks (e.g. JBoss 5) may create an implicit principal from username/password credentials passed into the JAAS pipeline. Since the CAS service URL is used for the username part of the credential, support has been added to append a unique suffix to the service URL and make it available to the custom AssertionPrincpal princpal type. A unqiue URL is generated prior to invoking WebAuthentication#login() to account for this feature of JBoss 5.
This commit is contained in:
parent
bd2c35a38f
commit
f9dfd6cf2f
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TicketCredential, Assertion> ASSERTION_CACHE = new HashMap<TicketCredential, Assertion>();
|
||||
|
||||
/** 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());
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public class SerializationTests extends TestCase {
|
|||
final SimpleGroup simpleGroup = new SimpleGroup("group");
|
||||
final AttributePrincipalImpl attributePrincipal = new AttributePrincipalImpl("attr",
|
||||
Collections.<String, Object> singletonMap("LOA", "3"));
|
||||
final AssertionPrincipal assertionPrincipal = new AssertionPrincipal("assertion", new AssertionImpl(
|
||||
final AssertionPrincipal assertionPrincipal = new AssertionPrincipal("assertion", "foo", new AssertionImpl(
|
||||
attributePrincipal, Collections.<String, Object> singletonMap("authenticationMethod", "username")));
|
||||
|
||||
return new Object[] { simplePrincipal, simpleGroup, attributePrincipal, assertionPrincipal, };
|
||||
|
|
|
|||
|
|
@ -560,7 +560,7 @@
|
|||
<repository>
|
||||
<id>atlassian</id>
|
||||
<name>Atlassian Repository</name>
|
||||
<url>http://repository.atlassian.com/maven2/</url>
|
||||
<url>https://maven.atlassian.com/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue