diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java index 36fd203f88..1f75d1276d 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java @@ -30,9 +30,7 @@ import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AccountStatusUserDetailsChecker; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.cas.ServiceProperties; -import org.springframework.security.cas.web.CasAuthenticationFilter; import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -51,11 +49,11 @@ import org.springframework.util.Assert; * Authentication Service (CAS). *
* This AuthenticationProvider
is capable of validating
- * {@link UsernamePasswordAuthenticationToken} requests which contain a
+ * {@link CasServiceTicketAuthenticationToken} requests which contain a
* principal
name equal to either
- * {@link CasAuthenticationFilter#CAS_STATEFUL_IDENTIFIER} or
- * {@link CasAuthenticationFilter#CAS_STATELESS_IDENTIFIER}. It can also validate a
- * previously created {@link CasAuthenticationToken}.
+ * {@link CasServiceTicketAuthenticationToken#CAS_STATEFUL_IDENTIFIER} or
+ * {@link CasServiceTicketAuthenticationToken#CAS_STATELESS_IDENTIFIER}. It can also
+ * validate a previously created {@link CasAuthenticationToken}.
*
* @author Ben Alex
* @author Scott Battaglia
@@ -95,13 +93,6 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
if (!supports(authentication.getClass())) {
return null;
}
- if (authentication instanceof UsernamePasswordAuthenticationToken
- && (!CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER.equals(authentication.getPrincipal().toString())
- && !CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER
- .equals(authentication.getPrincipal().toString()))) {
- // UsernamePasswordAuthenticationToken not CAS related
- return null;
- }
// If an existing CasAuthenticationToken, just check we created it
if (authentication instanceof CasAuthenticationToken) {
if (this.key.hashCode() != ((CasAuthenticationToken) authentication).getKeyHash()) {
@@ -117,8 +108,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
"Failed to provide a CAS service ticket to validate"));
}
- boolean stateless = (authentication instanceof UsernamePasswordAuthenticationToken
- && CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal()));
+ boolean stateless = (authentication instanceof CasServiceTicketAuthenticationToken token && token.isStateless());
CasAuthenticationToken result = null;
if (stateless) {
@@ -236,7 +226,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
@Override
public boolean supports(final Class> authentication) {
- return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication))
+ return (CasServiceTicketAuthenticationToken.class.isAssignableFrom(authentication))
|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
|| (CasAssertionAuthenticationToken.class.isAssignableFrom(authentication));
}
diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java
new file mode 100644
index 0000000000..d4471b6c57
--- /dev/null
+++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2023 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.
+ */
+
+package org.springframework.security.cas.authentication;
+
+import java.io.Serial;
+import java.util.Collection;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.SpringSecurityCoreVersion;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link org.springframework.security.core.Authentication} implementation that is
+ * designed to process CAS service ticket.
+ *
+ * @author Hal Deadman
+ * @since 6.1
+ */
+public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken {
+
+ static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";
+
+ static final String CAS_STATEFUL_IDENTIFIER = "_cas_stateful_";
+
+ @Serial
+ private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
+
+ private final String identifier;
+
+ private Object credentials;
+
+ /**
+ * This constructor can be safely used by any code that wishes to create a
+ * CasServiceTicketAuthenticationToken
, as the {@link #isAuthenticated()}
+ * will return false
.
+ *
+ */
+ public CasServiceTicketAuthenticationToken(String identifier, Object credentials) {
+ super(null);
+ this.identifier = identifier;
+ this.credentials = credentials;
+ setAuthenticated(false);
+ }
+
+ /**
+ * This constructor should only be used by AuthenticationManager
or
+ * AuthenticationProvider
implementations that are satisfied with
+ * producing a trusted (i.e. {@link #isAuthenticated()} = true
)
+ * authentication token.
+ * @param identifier
+ * @param credentials
+ * @param authorities
+ */
+ public CasServiceTicketAuthenticationToken(String identifier, Object credentials,
+ Collection extends GrantedAuthority> authorities) {
+ super(authorities);
+ this.identifier = identifier;
+ this.credentials = credentials;
+ super.setAuthenticated(true);
+ }
+
+ public static CasServiceTicketAuthenticationToken stateful(Object credentials) {
+ return new CasServiceTicketAuthenticationToken(CAS_STATEFUL_IDENTIFIER, credentials);
+ }
+
+ public static CasServiceTicketAuthenticationToken stateless(Object credentials) {
+ return new CasServiceTicketAuthenticationToken(CAS_STATELESS_IDENTIFIER, credentials);
+ }
+
+ public boolean isStateless() {
+ return CAS_STATELESS_IDENTIFIER.equals(this.identifier);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return this.credentials;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return this.identifier;
+ }
+
+ @Override
+ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+ Assert.isTrue(!isAuthenticated,
+ "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+ super.setAuthenticated(false);
+ }
+
+ @Override
+ public void eraseCredentials() {
+ super.eraseCredentials();
+ this.credentials = null;
+ }
+
+}
diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java
index 47311ccf7d..a1b58e6cc2 100644
--- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java
+++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.util.Assert;
/**
@@ -72,7 +73,8 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
String urlEncodedService = createServiceUrl(servletRequest, response);
String redirectUrl = createRedirectUrl(urlEncodedService);
preCommence(servletRequest, response);
- response.sendRedirect(redirectUrl);
+ new DefaultRedirectStrategy().sendRedirect(servletRequest, response, redirectUrl);
+ // response.sendRedirect(redirectUrl);
}
/**
diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java
index 8a0790c0cd..a951168f66 100644
--- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java
+++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,9 +29,9 @@ import org.apereo.cas.client.validation.TicketValidator;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.cas.ServiceProperties;
+import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
import org.springframework.security.core.Authentication;
@@ -41,6 +41,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -63,9 +64,9 @@ import org.springframework.util.Assert;
* filterProcessesUrl.
*
* Processing the service ticket involves creating a
- * UsernamePasswordAuthenticationToken
which uses
- * {@link #CAS_STATEFUL_IDENTIFIER} for the principal
and the opaque ticket
- * string as the credentials
.
+ * CasServiceTicketAuthenticationToken
which uses
+ * {@link CasServiceTicketAuthenticationToken#CAS_STATEFUL_IDENTIFIER} for the
+ * principal
and the opaque ticket string as the credentials
.
*
* If specified, the filter can also monitor the proxyReceptorUrl
. The filter
@@ -88,15 +89,15 @@ import org.springframework.util.Assert;
* {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
*
* Processing the proxy ticket involves creating a
- * UsernamePasswordAuthenticationToken
which uses
- * {@link #CAS_STATELESS_IDENTIFIER} for the principal
and the opaque ticket
- * string as the credentials
. When a proxy ticket is successfully
- * authenticated, the FilterChain continues and the
+ * CasServiceTicketAuthenticationToken
which uses
+ * {@link CasServiceTicketAuthenticationToken#CAS_STATELESS_IDENTIFIER} for the
+ * principal
and the opaque ticket string as the credentials
.
+ * When a proxy ticket is successfully authenticated, the FilterChain continues and the
* authenticationSuccessHandler
is not used.
*
AuthenticationManager
* The configured AuthenticationManager
is expected to provide a provider
- * that can recognise UsernamePasswordAuthenticationToken
s containing this
+ * that can recognise CasServiceTicketAuthenticationToken
s containing this
* special principal
name, and process them accordingly by validation with
* the CAS server. Additionally, it should be capable of using the result of
* {@link ServiceAuthenticationDetails#getServiceUrl()} as the service when validating the
@@ -175,19 +176,6 @@ import org.springframework.util.Assert;
*/
public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
- /**
- * Used to identify a CAS request for a stateful user agent, such as a web browser.
- */
- public static final String CAS_STATEFUL_IDENTIFIER = "_cas_stateful_";
-
- /**
- * Used to identify a CAS request for a stateless user agent, such as a remoting
- * protocol client (e.g. Hessian, Burlap, SOAP etc). Results in a more aggressive
- * caching strategy being used, as the absence of a HttpSession
will
- * result in a new authentication attempt on every request.
- */
- public static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";
-
/**
* The last portion of the receptor url, i.e. /proxy/receptor
*/
@@ -207,6 +195,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
public CasAuthenticationFilter() {
super("/login/cas");
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
+ setSecurityContextRepository(new HttpSessionSecurityContextRepository());
}
@Override
@@ -238,14 +227,15 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
return null;
}
- boolean serviceTicketRequest = serviceTicketRequest(request, response);
- String username = serviceTicketRequest ? CAS_STATEFUL_IDENTIFIER : CAS_STATELESS_IDENTIFIER;
- String password = obtainArtifact(request);
- if (password == null) {
+ String serviceTicket = obtainArtifact(request);
+ if (serviceTicket == null) {
this.logger.debug("Failed to obtain an artifact (cas ticket)");
- password = "";
+ serviceTicket = "";
}
- UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
+ boolean serviceTicketRequest = serviceTicketRequest(request, response);
+ CasServiceTicketAuthenticationToken authRequest = serviceTicketRequest
+ ? CasServiceTicketAuthenticationToken.stateful(serviceTicket)
+ : CasServiceTicketAuthenticationToken.stateless(serviceTicket);
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(authRequest);
}
diff --git a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java
index ed3e296029..f11f191507 100644
--- a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java
+++ b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java
@@ -29,7 +29,6 @@ import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.ServiceProperties;
-import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -87,8 +86,7 @@ public class CasAuthenticationProviderTests {
cap.setServiceProperties(makeServiceProperties());
cap.setTicketValidator(new MockTicketValidator(true));
cap.afterPropertiesSet();
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
- CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "ST-123");
+ CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("ST-123");
token.setDetails("details");
Authentication result = cap.authenticate(token);
// Confirm ST-123 was NOT added to the cache
@@ -120,8 +118,7 @@ public class CasAuthenticationProviderTests {
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
- CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, "ST-456");
+ CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless("ST-456");
token.setDetails("details");
Authentication result = cap.authenticate(token);
// Confirm ST-456 was added to the cache
@@ -135,7 +132,7 @@ public class CasAuthenticationProviderTests {
// Now try to authenticate again. To ensure TicketValidator not
// called again, set it to deliver an exception...
cap.setTicketValidator(new MockTicketValidator(false));
- // Previously created UsernamePasswordAuthenticationToken is OK
+ // Previously created CasServiceTicketAuthenticationToken is OK
Authentication newResult = cap.authenticate(token);
assertThat(newResult.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator());
assertThat(newResult.getCredentials()).isEqualTo("ST-456");
@@ -157,8 +154,7 @@ public class CasAuthenticationProviderTests {
cap.setServiceProperties(serviceProperties);
cap.afterPropertiesSet();
String ticket = "ST-456";
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
- CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
+ CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless(ticket);
Authentication result = cap.authenticate(token);
}
@@ -178,8 +174,7 @@ public class CasAuthenticationProviderTests {
cap.setServiceProperties(serviceProperties);
cap.afterPropertiesSet();
String ticket = "ST-456";
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
- CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
+ CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless(ticket);
Authentication result = cap.authenticate(token);
verify(validator).validate(ticket, serviceProperties.getService());
serviceProperties.setAuthenticateAllArtifacts(true);
@@ -211,8 +206,7 @@ public class CasAuthenticationProviderTests {
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
- CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "");
+ CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("");
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> cap.authenticate(token));
}
@@ -322,7 +316,7 @@ public class CasAuthenticationProviderTests {
@Test
public void supportsRequiredTokens() {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
- assertThat(cap.supports(UsernamePasswordAuthenticationToken.class)).isTrue();
+ assertThat(cap.supports(CasServiceTicketAuthenticationToken.class)).isTrue();
assertThat(cap.supports(CasAuthenticationToken.class)).isTrue();
}
diff --git a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java
index 3a5ee3ee49..6cc388d988 100644
--- a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java
+++ b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java
@@ -23,7 +23,6 @@ import org.apereo.cas.client.validation.Assertion;
import org.apereo.cas.client.validation.AssertionImpl;
import org.junit.jupiter.api.Test;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -116,16 +115,6 @@ public class CasAuthenticationTokenTests {
assertThat(!token1.equals(token2)).isTrue();
}
- @Test
- public void testNotEqualsDueToDifferentAuthenticationClass() {
- final Assertion assertion = new AssertionImpl("test");
- CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
- makeUserDetails(), assertion);
- UsernamePasswordAuthenticationToken token2 = new UsernamePasswordAuthenticationToken("Test", "Password",
- this.ROLES);
- assertThat(!token1.equals(token2)).isTrue();
- }
-
@Test
public void testNotEqualsDueToKey() {
final Assertion assertion = new AssertionImpl("test");