SEC-965: Added support for CAS proxy ticket authentication on any URL
This commit is contained in:
parent
373d07ce46
commit
a76a947b12
|
@ -38,6 +38,8 @@ public class ServiceProperties implements InitializingBean {
|
|||
|
||||
private String service;
|
||||
|
||||
private boolean authenticateAllArtifacts;
|
||||
|
||||
private boolean sendRenew = false;
|
||||
|
||||
private String artifactParameter = DEFAULT_CAS_ARTIFACT_PARAMETER;
|
||||
|
@ -47,7 +49,9 @@ public class ServiceProperties implements InitializingBean {
|
|||
//~ Methods ========================================================================================================
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.hasLength(this.service, "service must be specified.");
|
||||
if(!authenticateAllArtifacts) {
|
||||
Assert.hasLength(this.service, "service must be specified unless authenticateAllArtifacts is true.");
|
||||
}
|
||||
Assert.hasLength(this.artifactParameter, "artifactParameter cannot be empty.");
|
||||
Assert.hasLength(this.serviceParameter, "serviceParameter cannot be empty.");
|
||||
}
|
||||
|
@ -115,4 +119,19 @@ public class ServiceProperties implements InitializingBean {
|
|||
public final void setServiceParameter(final String serviceParameter) {
|
||||
this.serviceParameter = serviceParameter;
|
||||
}
|
||||
|
||||
public final boolean isAuthenticateAllArtifacts() {
|
||||
return authenticateAllArtifacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, then any non-null artifact (ticket) should be authenticated.
|
||||
* Additionally, the service will be determined dynamically in order to
|
||||
* ensure the service matches the expected value for this artifact.
|
||||
*
|
||||
* @param authenticateAllArtifacts
|
||||
*/
|
||||
public final void setAuthenticateAllArtifacts(final boolean authenticateAllArtifacts) {
|
||||
this.authenticateAllArtifacts = authenticateAllArtifacts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jasig.cas.client.validation.Assertion;
|
||||
import org.jasig.cas.client.validation.TicketValidationException;
|
||||
import org.jasig.cas.client.validation.TicketValidator;
|
||||
|
@ -28,6 +30,7 @@ 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;
|
||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||
|
@ -50,6 +53,9 @@ import org.springframework.util.Assert;
|
|||
* @author Scott Battaglia
|
||||
*/
|
||||
public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
|
@ -72,7 +78,6 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
|
|||
Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
|
||||
Assert.hasText(this.key, "A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
|
||||
Assert.notNull(this.messages, "A message source must be set");
|
||||
Assert.notNull(this.serviceProperties, "serviceProperties is a required field.");
|
||||
}
|
||||
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
|
@ -132,7 +137,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
|
|||
|
||||
private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
|
||||
try {
|
||||
final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), serviceProperties.getService());
|
||||
final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), getServiceUrl(authentication));
|
||||
final UserDetails userDetails = loadUserByAssertion(assertion);
|
||||
userDetailsChecker.check(userDetails);
|
||||
return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(),
|
||||
|
@ -142,6 +147,32 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serviceUrl. If the {@link Authentication#getDetails()} is an
|
||||
* instance of {@link ServiceAuthenticationDetails}, then
|
||||
* {@link ServiceAuthenticationDetails#getServiceUrl()} is used. Otherwise,
|
||||
* the {@link ServiceProperties#getService()} is used.
|
||||
*
|
||||
* @param authentication
|
||||
* @return
|
||||
*/
|
||||
private String getServiceUrl(Authentication authentication) {
|
||||
String serviceUrl;
|
||||
if(authentication.getDetails() instanceof ServiceAuthenticationDetails) {
|
||||
serviceUrl = ((ServiceAuthenticationDetails)authentication.getDetails()).getServiceUrl();
|
||||
}else if(serviceProperties == null){
|
||||
throw new IllegalStateException("serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
|
||||
}else if(serviceProperties.getService() == null){
|
||||
throw new IllegalStateException("serviceProperties.getService() cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
|
||||
}else {
|
||||
serviceUrl = serviceProperties.getService();
|
||||
}
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("serviceUrl = "+serviceUrl);
|
||||
}
|
||||
return serviceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for retrieving the UserDetails based on the assertion. Default is to call configured userDetailsService and pass the username. Deployers
|
||||
* can override this method and retrieve the user based on any criteria they desire.
|
||||
|
|
|
@ -65,6 +65,7 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
|
|||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.hasLength(this.loginUrl, "loginUrl must be specified");
|
||||
Assert.notNull(this.serviceProperties, "serviceProperties must be specified");
|
||||
Assert.notNull(this.serviceProperties.getService(),"serviceProperties.getService() cannot be null.");
|
||||
}
|
||||
|
||||
public final void commence(final HttpServletRequest servletRequest, final HttpServletResponse response,
|
||||
|
|
|
@ -17,41 +17,139 @@ package org.springframework.security.cas.web;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
import org.jasig.cas.client.validation.TicketValidator;
|
||||
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.web.authentication.ServiceAuthenticationDetails;
|
||||
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
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.util.Assert;
|
||||
|
||||
/**
|
||||
* Processes a CAS service ticket.
|
||||
* Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy tickets.
|
||||
* <h2>Service Tickets</h2>
|
||||
* <p>
|
||||
* A service ticket consists of an opaque ticket string. It arrives at this filter by the user's browser successfully
|
||||
* authenticating using CAS, and then receiving a HTTP redirect to a <code>service</code>. The opaque ticket string is
|
||||
* presented in the <code>ticket</code> request parameter. This filter monitors the <code>service</code> URL so it can
|
||||
* receive the service ticket and process it. The CAS server knows which <code>service</code> URL to use via the
|
||||
* {@link ServiceProperties#getService()} method.
|
||||
* presented in the <code>ticket</code> request parameter.
|
||||
* <p>
|
||||
* This filter monitors the <code>service</code> URL so it can
|
||||
* receive the service ticket and process it. By default this filter processes the URL <tt>/j_spring_cas_security_check</tt>.
|
||||
* When processing this URL, the value of {@link ServiceProperties#getService()} is used as the <tt>service</tt> when validating
|
||||
* the <code>ticket</code>. This means that it is important that {@link ServiceProperties#getService()} specifies the same value
|
||||
* as the <tt>filterProcessesUrl</tt>.
|
||||
* <p>
|
||||
* Processing the service ticket involves creating a <code>UsernamePasswordAuthenticationToken</code> which
|
||||
* uses {@link #CAS_STATEFUL_IDENTIFIER} for the <code>principal</code> and the opaque ticket string as the
|
||||
* <code>credentials</code>.
|
||||
* <h2>Obtaining Proxy Granting Tickets</h2>
|
||||
* <p>
|
||||
* If specified, the filter can also monitor the <code>proxyReceptorUrl</code>. The filter will respond to requests matching
|
||||
* this url so that the CAS Server can provide a PGT to the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
|
||||
* <code>proxyGrantingTicketStorage</code> must be provided in order for the filter to respond to proxy receptor requests. By configuring
|
||||
* a shared {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the CasAuthenticationFilter one can have the
|
||||
* CasAuthenticationFilter handle the proxying requirements for CAS.
|
||||
* <h2>Proxy Tickets</h2>
|
||||
* <p>
|
||||
* The filter can process tickets present on any url. This is useful when wanting to process proxy tickets. In order for proxy
|
||||
* tickets to get processed {@link ServiceProperties#isAuthenticateAllArtifacts()} must return <code>true</code>. Additionally,
|
||||
* if the request is already authenticated, authentication will <b>not</b> occur. Last, {@link AuthenticationDetailsSource#buildDetails(Object)}
|
||||
* must return a {@link ServiceAuthenticationDetails}. This can be accomplished using the {@link ServiceAuthenticationDetailsSource}.
|
||||
* In this case {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
|
||||
* <p>
|
||||
* Processing the proxy ticket involves creating a <code>UsernamePasswordAuthenticationToken</code> which
|
||||
* uses {@link #CAS_STATELESS_IDENTIFIER} for the <code>principal</code> and the opaque ticket string as the
|
||||
* <code>credentials</code>. When a proxy ticket is successfully authenticated, the FilterChain continues and the
|
||||
* <code>authenticationSuccessHandler</code> is not used.
|
||||
* <h2>Notes about the <code>AuthenticationManager</code></h2>
|
||||
* <p>
|
||||
* The configured <code>AuthenticationManager</code> is expected to provide a provider that can recognise
|
||||
* <code>UsernamePasswordAuthenticationToken</code>s containing this special <code>principal</code> name, and process
|
||||
* them accordingly by validation with the CAS server.
|
||||
* 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 ticket.
|
||||
* <h2>Example Configuration</h2>
|
||||
* <p>
|
||||
* By configuring a shared {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the
|
||||
* CasAuthenticationFilter one can have the CasAuthenticationFilter handle the proxying requirements for CAS. In addition, the
|
||||
* URI endpoint for the proxying would also need to be configured (i.e. the part after protocol, hostname, and port).
|
||||
* <p>
|
||||
* By default this filter processes the URL <tt>/j_spring_cas_security_check</tt>.
|
||||
* An example configuration that supports service tickets, obtaining proxy granting tickets, and proxy tickets is
|
||||
* illustrated below:
|
||||
*
|
||||
* <pre>
|
||||
* <b:bean id="serviceProperties"
|
||||
* class="org.springframework.security.cas.ServiceProperties"
|
||||
* p:service="https://service.example.com/cas-sample/j_spring_cas_security_check"
|
||||
* p:authenticateAllArtifacts="true"/>
|
||||
* <b:bean id="casEntryPoint"
|
||||
* class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"
|
||||
* p:serviceProperties-ref="serviceProperties" p:loginUrl="https://login.example.org/cas/login" />
|
||||
* <b:bean id="casFilter"
|
||||
* class="org.springframework.security.cas.web.CasAuthenticationFilter"
|
||||
* p:authenticationManager-ref="authManager"
|
||||
* p:serviceProperties-ref="serviceProperties"
|
||||
* p:proxyGrantingTicketStorage-ref="pgtStorage"
|
||||
* p:proxyReceptorUrl="/j_spring_cas_security_proxyreceptor">
|
||||
* <b:property name="authenticationDetailsSource">
|
||||
* <b:bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"/>
|
||||
* </b:property>
|
||||
* <b:property name="authenticationFailureHandler">
|
||||
* <b:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"
|
||||
* p:defaultFailureUrl="/casfailed.jsp"/>
|
||||
* </b:property>
|
||||
* </b:bean>
|
||||
* <!--
|
||||
* NOTE: In a real application you should not use an in memory implementation. You will also want
|
||||
* to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup()
|
||||
* -->
|
||||
* <b:bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
|
||||
* <b:bean id="casAuthProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"
|
||||
* p:serviceProperties-ref="serviceProperties"
|
||||
* p:key="casAuthProviderKey">
|
||||
* <b:property name="authenticationUserDetailsService">
|
||||
* <b:bean
|
||||
* class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
|
||||
* <b:constructor-arg ref="userService" />
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* <b:property name="ticketValidator">
|
||||
* <b:bean
|
||||
* class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"
|
||||
* p:acceptAnyProxy="true"
|
||||
* p:proxyCallbackUrl="https://service.example.com/cas-sample/j_spring_cas_security_proxyreceptor"
|
||||
* p:proxyGrantingTicketStorage-ref="pgtStorage">
|
||||
* <b:constructor-arg value="https://login.example.org/cas" />
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* <b:property name="statelessTicketCache">
|
||||
* <b:bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
|
||||
* <b:property name="cache">
|
||||
* <b:bean class="net.sf.ehcache.Cache"
|
||||
* init-method="initialise"
|
||||
* destroy-method="dispose">
|
||||
* <b:constructor-arg value="casTickets"/>
|
||||
* <b:constructor-arg value="50"/>
|
||||
* <b:constructor-arg value="true"/>
|
||||
* <b:constructor-arg value="false"/>
|
||||
* <b:constructor-arg value="3600"/>
|
||||
* <b:constructor-arg value="900"/>
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* </b:bean>
|
||||
* </b:property>
|
||||
* </b:bean>
|
||||
* </pre>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Rob Winch
|
||||
|
@ -81,26 +179,59 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
|
|||
|
||||
private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER;
|
||||
|
||||
private boolean authenticateAllArtifacts;
|
||||
|
||||
private AuthenticationFailureHandler proxyFailureHandler = new SimpleUrlAuthenticationFailureHandler();
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
public CasAuthenticationFilter() {
|
||||
super("/j_spring_cas_security_check");
|
||||
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Override
|
||||
protected final void successfulAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain chain, Authentication authResult)
|
||||
throws IOException, ServletException {
|
||||
boolean continueFilterChain = proxyTicketRequest(serviceTicketRequest(request, response),request);
|
||||
if(!continueFilterChain) {
|
||||
super.successfulAuthentication(request, response, chain, authResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
|
||||
}
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authResult);
|
||||
|
||||
// Fire event
|
||||
if (this.eventPublisher != null) {
|
||||
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
|
||||
throws AuthenticationException, IOException {
|
||||
// if the request is a proxy request process it and return null to indicate the request has been processed
|
||||
if(isProxyRequest(request)) {
|
||||
if(proxyReceptorRequest(request)) {
|
||||
logger.debug("Responding to proxy receptor request");
|
||||
CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
|
||||
return null;
|
||||
}
|
||||
|
||||
final String username = CAS_STATEFUL_IDENTIFIER;
|
||||
String password = request.getParameter(this.artifactParameter);
|
||||
final boolean serviceTicketRequest = serviceTicketRequest(request, response);
|
||||
final String username = serviceTicketRequest ? CAS_STATEFUL_IDENTIFIER : CAS_STATELESS_IDENTIFIER;
|
||||
String password = obtainArtifact(request);
|
||||
|
||||
if (password == null) {
|
||||
logger.debug("Failed to obtain an artifact (cas ticket)");
|
||||
password = "";
|
||||
}
|
||||
|
||||
|
@ -111,11 +242,47 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
|
|||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* If present, gets the artifact (CAS ticket) from the {@link HttpServletRequest}.
|
||||
* @param request
|
||||
* @return if present the artifact from the {@link HttpServletRequest}, else null
|
||||
*/
|
||||
protected String obtainArtifact(HttpServletRequest request) {
|
||||
return request.getParameter(artifactParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to provide proxying capabilities.
|
||||
*/
|
||||
@Override
|
||||
protected boolean requiresAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
|
||||
return isProxyRequest(request) || super.requiresAuthentication(request, response);
|
||||
final boolean serviceTicketRequest = serviceTicketRequest(request, response);
|
||||
final boolean result = serviceTicketRequest || proxyReceptorRequest(request) || (proxyTicketRequest(serviceTicketRequest, request));
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("requiresAuthentication = "+result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} for proxy requests.
|
||||
* @param proxyFailureHandler
|
||||
*/
|
||||
public final void setProxyAuthenticationFailureHandler(
|
||||
AuthenticationFailureHandler proxyFailureHandler) {
|
||||
Assert.notNull(proxyFailureHandler,"proxyFailureHandler cannot be null");
|
||||
this.proxyFailureHandler = proxyFailureHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the {@link AuthenticationFailureHandler} to distinguish between
|
||||
* handling proxy ticket authentication failures and service ticket
|
||||
* failures.
|
||||
*/
|
||||
@Override
|
||||
public final void setAuthenticationFailureHandler(
|
||||
AuthenticationFailureHandler failureHandler) {
|
||||
super.setAuthenticationFailureHandler(new CasAuthenticationFailureHandler(failureHandler));
|
||||
}
|
||||
|
||||
public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
|
||||
|
@ -129,15 +296,97 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
|
|||
|
||||
public final void setServiceProperties(final ServiceProperties serviceProperties) {
|
||||
this.artifactParameter = serviceProperties.getArtifactParameter();
|
||||
this.authenticateAllArtifacts = serviceProperties.isAuthenticateAllArtifacts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the request is eligible to be processed as a proxy request.
|
||||
* Indicates if the request is elgible to process a service ticket. This method exists for readability.
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
private boolean serviceTicketRequest(final HttpServletRequest request, final HttpServletResponse response) {
|
||||
boolean result = super.requiresAuthentication(request, response);
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("serviceTicketRequest = "+result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the request is elgible to process a proxy ticket.
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private boolean isProxyRequest(final HttpServletRequest request) {
|
||||
private boolean proxyTicketRequest(final boolean serviceTicketRequest, final HttpServletRequest request) {
|
||||
if(serviceTicketRequest) {
|
||||
return false;
|
||||
}
|
||||
final boolean result = authenticateAllArtifacts && obtainArtifact(request) != null && !authenticated();
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("proxyTicketRequest = "+result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a user is already authenticated.
|
||||
* @return
|
||||
*/
|
||||
private boolean authenticated() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken);
|
||||
}
|
||||
/**
|
||||
* Indicates if the request is elgible to be processed as the proxy receptor.
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private boolean proxyReceptorRequest(final HttpServletRequest request) {
|
||||
final String requestUri = request.getRequestURI();
|
||||
return this.proxyGrantingTicketStorage != null && !CommonUtils.isEmpty(this.proxyReceptorUrl) && requestUri.endsWith(this.proxyReceptorUrl);
|
||||
final boolean result = proxyReceptorConfigured() && requestUri.endsWith(this.proxyReceptorUrl);
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("proxyReceptorRequest = "+result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the {@link CasAuthenticationFilter} is configured to handle the proxy receptor requests.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean proxyReceptorConfigured() {
|
||||
final boolean result = this.proxyGrantingTicketStorage != null && !CommonUtils.isEmpty(this.proxyReceptorUrl);
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("proxyReceptorConfigured = "+result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for the AuthenticationFailureHandler that will flex the {@link AuthenticationFailureHandler} that is used. The value
|
||||
* {@link CasAuthenticationFilter#setProxyAuthenticationFailureHandler(AuthenticationFailureHandler) will be used for proxy requests
|
||||
* that fail. The value {@link CasAuthenticationFilter#setAuthenticationFailureHandler(AuthenticationFailureHandler)} will be used for
|
||||
* service tickets that fail.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
private class CasAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
private final AuthenticationFailureHandler serviceTicketFailureHandler;
|
||||
public CasAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
|
||||
Assert.notNull(failureHandler,"failureHandler");
|
||||
this.serviceTicketFailureHandler = failureHandler;
|
||||
}
|
||||
public void onAuthenticationFailure(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException exception) throws IOException,
|
||||
ServletException {
|
||||
if(serviceTicketRequest(request, response)) {
|
||||
serviceTicketFailureHandler.onAuthenticationFailure(request, response, exception);
|
||||
}else {
|
||||
proxyFailureHandler.onAuthenticationFailure(request, response, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2011 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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A default implementation of {@link ServiceAuthenticationDetails} that figures
|
||||
* out the value for {@link #getServiceUrl()} by inspecting the current
|
||||
* {@link HttpServletRequest} and using the current URL minus the artifact and
|
||||
* the corresponding value.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails implements ServiceAuthenticationDetails {
|
||||
private static final long serialVersionUID = 6192409090610517700L;
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private final String serviceUrl;
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param request
|
||||
* the current {@link HttpServletRequest} to obtain the
|
||||
* {@link #getServiceUrl()} from.
|
||||
* @param artifactPattern
|
||||
* the {@link Pattern} that will be used to clean up the query
|
||||
* string from containing the artifact name and value. This can
|
||||
* be created using {@link #createArtifactPattern(String)}.
|
||||
*/
|
||||
DefaultServiceAuthenticationDetails(HttpServletRequest request, Pattern artifactPattern) {
|
||||
super(request);
|
||||
final String query = getQueryString(request,artifactPattern);
|
||||
this.serviceUrl = UrlUtils.buildFullRequestUrl(request.getScheme(),
|
||||
request.getServerName(), request.getServerPort(),
|
||||
request.getRequestURI(), query);
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
/**
|
||||
* Returns the current URL minus the artifact parameter and its value, if present.
|
||||
* @see org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails#getServiceUrl()
|
||||
*/
|
||||
public String getServiceUrl() {
|
||||
return serviceUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result
|
||||
+ serviceUrl.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails)) {
|
||||
return false;
|
||||
}
|
||||
ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
|
||||
return serviceUrl.equals(that.getServiceUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(super.toString());
|
||||
result.append("ServiceUrl: ");
|
||||
result.append(serviceUrl);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* If present, removes the artifactParameterName and the corresponding value from the query String.
|
||||
* @param request
|
||||
* @return the query String minus the artifactParameterName and the corresponding value.
|
||||
*/
|
||||
private String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) {
|
||||
final String query = request.getQueryString();
|
||||
if(query == null) {
|
||||
return null;
|
||||
}
|
||||
final String result = artifactPattern.matcher(query).replaceFirst("");
|
||||
if(result.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
// strip off the trailing & only if the artifact was the first query param
|
||||
return result.startsWith("&") ? result.substring(1) : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Pattern} that can be passed into the constructor. This
|
||||
* allows the {@link Pattern} to be reused for every instance of
|
||||
* {@link DefaultServiceAuthenticationDetails}.
|
||||
*
|
||||
* @param artifactParameterName
|
||||
* @return
|
||||
*/
|
||||
static Pattern createArtifactPattern(String artifactParameterName) {
|
||||
Assert.hasLength(artifactParameterName);
|
||||
return Pattern.compile("&?"+Pattern.quote(artifactParameterName)+"=[^&]*");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2011 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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* In order for the {@link CasAuthenticationProvider} to provide the correct
|
||||
* service url to authenticate the ticket, the returned value of
|
||||
* {@link Authentication#getDetails()} should implement this interface when
|
||||
* tickets can be sent to any URL rather than only
|
||||
* {@link ServiceProperties#getService()}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
* @see ServiceAuthenticationDetailsSource
|
||||
*/
|
||||
public interface ServiceAuthenticationDetails extends Serializable {
|
||||
|
||||
/**
|
||||
* Gets the absolute service url (i.e. https://example.com/service/).
|
||||
*
|
||||
* @return the service url. Cannot be <code>null</code>.
|
||||
*/
|
||||
String getServiceUrl();
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2011 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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
|
||||
/**
|
||||
* The {@code AuthenticationDetailsSource} that is set on the
|
||||
* {@code CasAuthenticationFilter} should return a value that implements
|
||||
* {@code ServiceAuthenticationDetails} if the application needs to authenticate
|
||||
* dynamic service urls. The
|
||||
* {@code ServiceAuthenticationDetailsSource#buildDetails(HttpServletRequest)}
|
||||
* creates a default {@code ServiceAuthenticationDetails}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class ServiceAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest,
|
||||
ServiceAuthenticationDetails> {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private final Pattern artifactPattern;
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
/**
|
||||
* Creates an implementation that uses the default CAS artifactParameterName.
|
||||
*/
|
||||
public ServiceAuthenticationDetailsSource() {
|
||||
this(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an implementation that uses the specified artifactParameterName
|
||||
*
|
||||
* @param artifactParameterName
|
||||
* the artifactParameterName that is removed from the current
|
||||
* URL. The result becomes the service url. Cannot be null and
|
||||
* cannot be an empty String.
|
||||
*/
|
||||
public ServiceAuthenticationDetailsSource(final String artifactParameterName) {
|
||||
this.artifactPattern = DefaultServiceAuthenticationDetails.createArtifactPattern(artifactParameterName);
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
/**
|
||||
* @param context the {@code HttpServletRequest} object.
|
||||
* @return the {@code ServiceAuthenticationDetails} containing information about the current request
|
||||
*/
|
||||
public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
|
||||
return new DefaultServiceAuthenticationDetails(context,artifactPattern);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Authentication processing mechanisms which respond to the submission of authentication
|
||||
* credentials using CAS.
|
||||
*/
|
||||
package org.springframework.security.cas.web.authentication;
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
package org.springframework.security.cas.authentication;
|
||||
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.jasig.cas.client.validation.Assertion;
|
||||
|
@ -23,11 +23,13 @@ import org.jasig.cas.client.validation.AssertionImpl;
|
|||
import org.jasig.cas.client.validation.TicketValidationException;
|
||||
import org.jasig.cas.client.validation.TicketValidator;
|
||||
import org.junit.*;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
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;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
@ -35,6 +37,7 @@ import org.springframework.security.core.userdetails.AuthenticationUserDetailsSe
|
|||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
@ -148,6 +151,87 @@ public class CasAuthenticationProviderTests {
|
|||
assertEquals("ST-456", newResult.getCredentials());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateAllNullService() throws Exception {
|
||||
String serviceUrl = "https://service/context";
|
||||
ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
|
||||
when(details.getServiceUrl()).thenReturn(serviceUrl);
|
||||
TicketValidator validator = mock(TicketValidator.class);
|
||||
when(validator.validate(any(String.class),any(String.class))).thenReturn(new AssertionImpl("rod"));
|
||||
|
||||
ServiceProperties serviceProperties = makeServiceProperties();
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
|
||||
cap.setTicketValidator(validator);
|
||||
cap.setServiceProperties(serviceProperties);
|
||||
cap.afterPropertiesSet();
|
||||
|
||||
String ticket = "ST-456";
|
||||
UsernamePasswordAuthenticationToken token =
|
||||
new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
|
||||
|
||||
Authentication result = cap.authenticate(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateAllAuthenticationIsSuccessful() throws Exception {
|
||||
String serviceUrl = "https://service/context";
|
||||
ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
|
||||
when(details.getServiceUrl()).thenReturn(serviceUrl);
|
||||
TicketValidator validator = mock(TicketValidator.class);
|
||||
when(validator.validate(any(String.class),any(String.class))).thenReturn(new AssertionImpl("rod"));
|
||||
|
||||
ServiceProperties serviceProperties = makeServiceProperties();
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
|
||||
cap.setTicketValidator(validator);
|
||||
cap.setServiceProperties(serviceProperties);
|
||||
cap.afterPropertiesSet();
|
||||
|
||||
String ticket = "ST-456";
|
||||
UsernamePasswordAuthenticationToken token =
|
||||
new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
|
||||
|
||||
Authentication result = cap.authenticate(token);
|
||||
verify(validator).validate(ticket, serviceProperties.getService());
|
||||
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
result = cap.authenticate(token);
|
||||
verify(validator,times(2)).validate(ticket, serviceProperties.getService());
|
||||
|
||||
token.setDetails(details);
|
||||
result = cap.authenticate(token);
|
||||
verify(validator).validate(ticket, serviceUrl);
|
||||
|
||||
serviceProperties.setAuthenticateAllArtifacts(false);
|
||||
serviceProperties.setService(null);
|
||||
cap.setServiceProperties(serviceProperties);
|
||||
cap.afterPropertiesSet();
|
||||
result = cap.authenticate(token);
|
||||
verify(validator,times(2)).validate(ticket, serviceUrl);
|
||||
|
||||
token.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest()));
|
||||
try {
|
||||
cap.authenticate(token);
|
||||
fail("Expected Exception");
|
||||
}catch(IllegalStateException success) {}
|
||||
|
||||
cap.setServiceProperties(null);
|
||||
cap.afterPropertiesSet();
|
||||
try {
|
||||
cap.authenticate(token);
|
||||
fail("Expected Exception");
|
||||
}catch(IllegalStateException success) {}
|
||||
}
|
||||
|
||||
@Test(expected = BadCredentialsException.class)
|
||||
public void missingTicketIdIsDetected() throws Exception {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
|
|
|
@ -16,19 +16,34 @@
|
|||
package org.springframework.security.cas.web;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
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.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.NullRememberMeServices;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -40,6 +55,11 @@ import org.springframework.security.core.AuthenticationException;
|
|||
public class CasAuthenticationFilterTests {
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
|
@ -105,6 +125,31 @@ public class CasAuthenticationFilterTests {
|
|||
assertFalse(filter.requiresAuthentication(request, response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequiresAuthenticationAuthAll() {
|
||||
ServiceProperties properties = new ServiceProperties();
|
||||
properties.setAuthenticateAllArtifacts(true);
|
||||
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setServiceProperties(properties);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
request.setRequestURI(filter.getFilterProcessesUrl());
|
||||
assertTrue(filter.requiresAuthentication(request, response));
|
||||
|
||||
request.setRequestURI("/other");
|
||||
assertFalse(filter.requiresAuthentication(request, response));
|
||||
request.setParameter(properties.getArtifactParameter(), "value");
|
||||
assertTrue(filter.requiresAuthentication(request, response));
|
||||
SecurityContextHolder.getContext().setAuthentication(new AnonymousAuthenticationToken("key", "principal", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
|
||||
assertTrue(filter.requiresAuthentication(request, response));
|
||||
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("un", "principal", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
|
||||
assertTrue(filter.requiresAuthentication(request, response));
|
||||
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("un", "principal", "ROLE_ANONYMOUS"));
|
||||
assertFalse(filter.requiresAuthentication(request, response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateProxyUrl() throws Exception {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
|
@ -117,9 +162,42 @@ public class CasAuthenticationFilterTests {
|
|||
assertNull(filter.attemptAuthentication(request, response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoFilterAuthenticateAll() throws Exception {
|
||||
AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class);
|
||||
AuthenticationManager manager = mock(AuthenticationManager.class);
|
||||
Authentication authentication = new TestingAuthenticationToken("un", "pwd","ROLE_USER");
|
||||
when(manager.authenticate(any(Authentication.class))).thenReturn(authentication);
|
||||
ServiceProperties serviceProperties = new ServiceProperties();
|
||||
serviceProperties.setAuthenticateAllArtifacts(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameter("ticket", "ST-1-123");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
filter.setServiceProperties(serviceProperties);
|
||||
filter.setAuthenticationSuccessHandler(successHandler);
|
||||
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
|
||||
filter.setAuthenticationManager(manager);
|
||||
filter.afterPropertiesSet();
|
||||
|
||||
filter.doFilter(request,response,chain);
|
||||
assertFalse("Authentication should not be null",SecurityContextHolder.getContext().getAuthentication() == null);
|
||||
verify(chain).doFilter(request, response);
|
||||
verifyZeroInteractions(successHandler);
|
||||
|
||||
// validate for when the filterProcessUrl matches
|
||||
filter.setFilterProcessesUrl(request.getRequestURI());
|
||||
SecurityContextHolder.clearContext();
|
||||
filter.doFilter(request,response,chain);
|
||||
verifyNoMoreInteractions(chain);
|
||||
verify(successHandler).onAuthenticationSuccess(request, response, authentication);
|
||||
}
|
||||
|
||||
// SEC-1592
|
||||
@Test
|
||||
public void testChainNotInvokedForProxy() throws Exception {
|
||||
public void testChainNotInvokedForProxyReceptor() throws Exception {
|
||||
CasAuthenticationFilter filter = new CasAuthenticationFilter();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
|
|
@ -35,6 +35,18 @@ public class ServicePropertiesTests {
|
|||
sp.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowNullServiceWhenAuthenticateAllTokens() throws Exception {
|
||||
ServiceProperties sp = new ServiceProperties();
|
||||
sp.setAuthenticateAllArtifacts(true);
|
||||
sp.afterPropertiesSet();
|
||||
sp.setAuthenticateAllArtifacts(false);
|
||||
try {
|
||||
sp.afterPropertiesSet();
|
||||
fail("Expected Exception");
|
||||
}catch(IllegalArgumentException success) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() throws Exception {
|
||||
ServiceProperties[] sps = {new ServiceProperties(), new SamlServiceProperties()};
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2011 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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package org.springframework.security.cas.web.authentication;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class DefaultServiceAuthenticationDetailsTests {
|
||||
private DefaultServiceAuthenticationDetails details;
|
||||
private MockHttpServletRequest request;
|
||||
private Pattern artifactPattern;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
request = new MockHttpServletRequest();
|
||||
request.setScheme("https");
|
||||
request.setServerName("localhost");
|
||||
request.setServerPort(8443);
|
||||
request.setRequestURI("/cas-sample/secure/");
|
||||
artifactPattern = DefaultServiceAuthenticationDetails.createArtifactPattern(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlNullQuery() throws Exception {
|
||||
details = new DefaultServiceAuthenticationDetails(request,artifactPattern);
|
||||
assertEquals(UrlUtils.buildFullRequestUrl(request),details.getServiceUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketOnlyParam() {
|
||||
request.setQueryString("ticket=123");
|
||||
details = new DefaultServiceAuthenticationDetails(request,artifactPattern);
|
||||
String serviceUrl = details.getServiceUrl();
|
||||
request.setQueryString(null);
|
||||
assertEquals(UrlUtils.buildFullRequestUrl(request),serviceUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketFirstMultiParam() {
|
||||
request.setQueryString("ticket=123&other=value");
|
||||
details = new DefaultServiceAuthenticationDetails(request,artifactPattern);
|
||||
String serviceUrl = details.getServiceUrl();
|
||||
request.setQueryString("other=value");
|
||||
assertEquals(UrlUtils.buildFullRequestUrl(request),serviceUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketLastMultiParam() {
|
||||
request.setQueryString("other=value&ticket=123");
|
||||
details = new DefaultServiceAuthenticationDetails(request,artifactPattern);
|
||||
String serviceUrl = details.getServiceUrl();
|
||||
request.setQueryString("other=value");
|
||||
assertEquals(UrlUtils.buildFullRequestUrl(request),serviceUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceUrlTicketMiddleMultiParam() {
|
||||
request.setQueryString("other=value&ticket=123&last=this");
|
||||
details = new DefaultServiceAuthenticationDetails(request,artifactPattern);
|
||||
String serviceUrl = details.getServiceUrl();
|
||||
request.setQueryString("other=value&last=this");
|
||||
assertEquals(UrlUtils.buildFullRequestUrl(request),serviceUrl);
|
||||
}
|
||||
}
|
|
@ -215,7 +215,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
|
|||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
successfulAuthentication(request, response, authResult);
|
||||
successfulAuthentication(request, response, chain, authResult);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,8 +280,35 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
|
|||
* <li>Delegates additional behaviour to the {@link AuthenticationSuccessHandler}.</li>
|
||||
* </ol>
|
||||
*
|
||||
* Subclasses can override this method to continue the {@link FilterChain} after successful authentication.
|
||||
* @param request
|
||||
* @param response
|
||||
* @param chain
|
||||
* @param authResult the object returned from the <tt>attemptAuthentication</tt> method.
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
|
||||
Authentication authResult) throws IOException, ServletException{
|
||||
successfulAuthentication(request, response, authResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour for successful authentication.
|
||||
* <ol>
|
||||
* <li>Sets the successful <tt>Authentication</tt> object on the {@link SecurityContextHolder}</li>
|
||||
* <li>Invokes the configured {@link SessionAuthenticationStrategy} to handle any session-related behaviour
|
||||
* (such as creating a new session to protect against session-fixation attacks).</li>
|
||||
* <li>Informs the configured <tt>RememberMeServices</tt> of the successful login</li>
|
||||
* <li>Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured
|
||||
* <tt>ApplicationEventPublisher</tt></li>
|
||||
* <li>Delegates additional behaviour to the {@link AuthenticationSuccessHandler}.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param authResult the object returned from the <tt>attemptAuthentication</tt> method.
|
||||
* @deprecated since 3.1. Use {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authResult) throws IOException, ServletException {
|
||||
|
||||
|
|
Loading…
Reference in New Issue