如何在Spring的CAS服务属性中正确设置服务URL

使用Spring Security + CAS时,我一直使用发送到CAS的回调URL(即服务属性)来阻止小路障。 我已经看了很多这样的例子,但它们都使用硬编码的URL(甚至是Spring的CAS文档 )。 一个典型的剪辑看起来像这样……

   

首先,我不想硬编码服务器名称或端口,因为我希望这个WAR可以在任何地方部署,我不希望我的应用程序在编译时绑定到特定的DNS条目。 其次,我不明白为什么Spring无法自动检测我的应用程序的上下文和请求的URL以自动构建URL。 该声明的第一部分仍然有效,但As Raghuram通过此链接指出,出于安全原因,我们无法信任来自客户端的HTTP Host Header。

理想情况下,我希望服务URL完全符合用户的要求(只要请求有效,例如mycompany.com的子域),所以它是无缝的,或者至少我只想指定一些相对于我的路径应用程序上下文root并让Spring动态确定服务URL。 像下面这样的东西……

     

要么…

     

这是否可能或容易,或者我错过了明显的?

在Spring 2.6.5 spring中,您可以扩展org.springframework.security.ui.cas.ServiceProperties

在第3个春季,该方法是最终的,您可以通过inheritanceCasAuthenticationProvider和CasEntryPoint来解决这个问题,然后使用您自己的ServiceProperties版本并使用更动态的实现覆盖getService()方法。

您可以使用主机标头计算所需的域,并通过validation仅使用您控制下的域/子域来使其更安全。 然后附加一些可配置的值。

当然,你的风险是你的实施是不安全的……所以要小心。

它可能最终看起来像:

     

我知道这有点旧,但我只是必须解决这个问题,并且在新的堆栈中找不到任何东西。

我们有多个环境共享相同的CAS服务(想想dev,qa,uat和本地开发环境); 我们有能力从多个URL(通过客户端Web服务器通过反向代理并直接到后端服务器本身)命中每个环境。 这意味着指定单个URL最多也是困难的。 也许有办法做到这一点,但能够使用动态ServiceProperties.getService() 。 我可能会添加某种服务器后缀检查,以确保URL在某些时候没有被劫持。

无论用于访问安全资源的URL如何,我都是为了使基本CAS流程工作而做的…

  1. 覆盖CasAuthenticationFilter
  2. 覆盖CasAuthenticationProvider
  3. ServiceProperties上的setAuthenticateAllArtifacts(true)

这是我的spring配置bean的长forms:

 @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true) public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter { 

只是通常的spring配置bean。

 @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; 

一些外部化配置参数。

 @Bean public ServiceProperties serviceProperties() { ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setService(casValidationUri); serviceProperties.setSendRenew(false); serviceProperties.setAuthenticateAllArtifacts(true); return serviceProperties; } 

上面的关键是setAuthenticateAllArtifacts(true)调用。 这将使服务票证validation程序使用AuthenticationDetailsSource实现而不是硬编码的ServiceProperties.getService()调用

 @Bean public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { return new Cas20ServiceTicketValidator(casServerUrl); } 

标准机票validation器..

 @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); } }; } 

现有UserDetailsS​​ervice的标准挂钩

 @Bean public CasAuthenticationProvider casAuthenticationProvider() { CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider(); casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService()); casAuthenticationProvider.setServiceProperties(serviceProperties()); casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator()); casAuthenticationProvider.setKey(casProviderKey); return casAuthenticationProvider; } 

标准认证提供商

 @Bean public CasAuthenticationFilter casAuthenticationFilter() throws Exception { CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); casAuthenticationFilter.setAuthenticationManager(authenticationManager()); casAuthenticationFilter.setServiceProperties(serviceProperties()); casAuthenticationFilter.setAuthenticationDetailsSource(dynamicServiceResolver()); return casAuthenticationFilter; } 

这里的关键是dynamicServiceResolver()设置..

 @Bean AuthenticationDetailsSource dynamicServiceResolver() { return new AuthenticationDetailsSource() { @Override public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) { final String url = makeDynamicUrlFromRequest(serviceProperties()); return new ServiceAuthenticationDetails() { @Override public String getServiceUrl() { return url; } }; } }; } 

makeDynamicUrlFromRequest()方法动态创建服务URL。 这个位用于票证validation。

 @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; } 

CAS希望重定向到登录屏幕时,此部分使用相同的动态URL创建者。

 private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){ return "https://howeverYouBuildYourOwnDynamicUrl.com"; } 

这就是你所做的一切。 我只传入ServiceProperties来保存我们配置的服务的URI。 我们在背面使用HATEAOS并具有如下实现:

 return UriComponentsBuilder.fromHttpUrl( linkTo(methodOn(ExposedRestResource.class) .aMethodOnThatResource(null)).withSelfRel().getHref()) .replacePath(serviceProperties.getService()) .build(false) .toUriString(); 

编辑:这是我为有效的服务器后缀列表所做的。

 private List 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(); } 

使用maven,添加属性占位符,并在构建过程中对其进行配置

我尝试将CasAuthenticationProvider子类化为Pablojim建议,但解决方案更容易! 使用Spring Expression Language(SPEL),您可以获得动态的url。

示例:

我自己没有尝试过,但是看起来Spring Security已经通过Bob博客更新中显示的SavedRequestAwareAuthenticationSuccessHandler解决了这个问题。