I know this is a little outdated, but I just had to solve this problem and could not find anything in the new stacks.
We have several environments that use the same CAS service (think dev, qa, uat and local development environments); we have the ability to hit each environment from multiple URLs (through the client side web server through the reverse proxy server and directly to the internal server). This means that specifying a single URL is difficult at best. There may be a way to do this, but be able to use dynamic ServiceProperties.getService()
. I will probably add some kind of server suffix check to make sure the URL was not captured at some point.
Here I have made the underlying CAS stream work regardless of the URL used to access the protected resource ...
- Cancel the
CasAuthenticationFilter
. - Cancel the
CasAuthenticationProvider
. setAuthenticateAllArtifacts(true)
on ServiceProperties
.
Here is a long form of my spring bean configuration:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true) public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter {
Just plain spring bean configuration.
@Value("${cas.server.url:https://localhost:9443/cas}") private String casServerUrl; @Value("${cas.service.validation.uri:/webapi/j_spring_cas_security_check}") private String casValidationUri; @Value("${cas.provider.key:whatever_your_key}") private String casProviderKey;
Some external configuration options.
@Bean public ServiceProperties serviceProperties() { ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setService(casValidationUri); serviceProperties.setSendRenew(false); serviceProperties.setAuthenticateAllArtifacts(true); return serviceProperties; }
The keyword above is a call to setAuthenticateAllArtifacts(true)
. This will be done by a service validator that uses the AuthenticationDetailsSource
implementation, rather than a hard-coded call to ServiceProperties.getService()
@Bean public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { return new Cas20ServiceTicketValidator(casServerUrl); }
Standard ticket validator.
@Resource private UserDetailsService userDetailsService; @Bean public AuthenticationUserDetailsService authenticationUserDetailsService() { return new AuthenticationUserDetailsService() { @Override public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException { String username = (token.getPrincipal() == null) ? "NONE_PROVIDED" : token.getName(); return userDetailsService.loadUserByUsername(username); } }; }
Standard Binding to Existing UserDetailsService
@Bean public CasAuthenticationProvider casAuthenticationProvider() { CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider(); casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService()); casAuthenticationProvider.setServiceProperties(serviceProperties()); casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator()); casAuthenticationProvider.setKey(casProviderKey); return casAuthenticationProvider; }
Standard Authentication Provider
@Bean public CasAuthenticationFilter casAuthenticationFilter() throws Exception { CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); casAuthenticationFilter.setAuthenticationManager(authenticationManager()); casAuthenticationFilter.setServiceProperties(serviceProperties()); casAuthenticationFilter.setAuthenticationDetailsSource(dynamicServiceResolver()); return casAuthenticationFilter; }
The dynamicServiceResolver()
setting is specified here.
@Bean AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails> dynamicServiceResolver() { return new AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails>() { @Override public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) { final String url = makeDynamicUrlFromRequest(serviceProperties()); return new ServiceAuthenticationDetails() { @Override public String getServiceUrl() { return url; } }; } }; }
Dynamically creates a service url from the makeDynamicUrlFromRequest()
method. This bit is used when checking tickets.
@Bean public CasAuthenticationEntryPoint casAuthenticationEntryPoint() { CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint() { @Override protected String createServiceUrl(final HttpServletRequest request, final HttpServletResponse response) { return CommonUtils.constructServiceUrl(null, response, makeDynamicUrlFromRequest(serviceProperties()) , null, serviceProperties().getArtifactParameter(), false); } }; casAuthenticationEntryPoint.setLoginUrl(casServerUrl + "/login"); casAuthenticationEntryPoint.setServiceProperties(serviceProperties()); return casAuthenticationEntryPoint; }
This part uses the same dynamic URL creator when CAS wants to redirect to the login screen.
private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){ return "https://howeverYouBuildYourOwnDynamicUrl.com"; }
This is what you do. I just passed in the ServiceProperties to store the URI of the service for which we are configured. We use HATEAOS on the back and have an implementation, for example:
return UriComponentsBuilder.fromHttpUrl( linkTo(methodOn(ExposedRestResource.class) .aMethodOnThatResource(null)).withSelfRel().getHref()) .replacePath(serviceProperties.getService()) .build(false) .toUriString();
Edit: this is what I did for the list of valid server suffixes.
private List<String> validCasServerHostEndings; @Value("${cas.valid.server.suffixes:company.com,localhost}") private void setValidCasServerHostEndings(String endings){ validCasServerHostEndings = new ArrayList<>(); for (String ending : StringUtils.split(endings, ",")) { if (StringUtils.isNotBlank(ending)){ validCasServerHostEndings.add(StringUtils.trim(ending)); } } } private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){ UriComponents url = UriComponentsBuilder.fromHttpUrl( linkTo(methodOn(ExposedRestResource.class) .aMethodOnThatResource(null)).withSelfRel().getHref()) .replacePath(serviceProperties.getService()) .build(false); boolean valid = false; for (String validCasServerHostEnding : validCasServerHostEndings) { if (url.getHost().endsWith(validCasServerHostEnding)){ valid = true; break; } } if (!valid){ throw new AccessDeniedException("The server is unable to authenticate the requested url."); } return url.toString(); }