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

时间:2010-12-29 15:08:10

标签: java spring spring-security cas

使用Spring Security + CAS时,我一直使用发送到CAS的回调URL(即服务属性)来阻止小路障。我查看过一些示例,例如thisthis,但它们都使用硬编码的网址(甚至是Spring's CAS docs)。一个典型的剪辑看起来像这样......

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="http://localhost:8080/click/j_spring_cas_security_check" />
  </bean>

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

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

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="/my_cas_callback" />
  </bean>

... OR

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="${container.and.app.derived.value.here}" />
  </bean>

这是否可能或容易,或者我错过了显而易见的事情?

4 个答案:

答案 0 :(得分:5)

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

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

无论用于访问安全资源的URL如何,我都采取了以下措施来使基本CAS流程正常工作...

  1. 覆盖CasAuthenticationFilter
  2. 覆盖CasAuthenticationProvider
  3. setAuthenticateAllArtifacts(true) ServiceProperties
  4. 这是我的spring配置bean的长形式:

    @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)电话。这将使服务票证验证程序使用AuthenticationDetailsSource实现而不是硬编码的ServiceProperties.getService()调用

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

    标准票证验证器..

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

    makeDynamicUrlFromRequest()方法动态创建服务网址。在票证验证时使用此位。

    @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想要重定向到登录屏幕时,此部分使用相同的动态网址创建者。

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

答案 1 :(得分:4)

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

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

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

当然,如果您的实施不安全,您将面临风险......所以要小心。

最终看起来像:

<bean id="serviceProperties" class="my.ServiceProperties">
    <property name="serviceRelativeUrl" value="/my_cas_callback" />
    <property name="validDomainPattern" value="*.mydomain.com" />
</bean>

答案 2 :(得分:2)

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

答案 3 :(得分:-1)

我自己没有尝试过,但似乎Spring Security已经解决了这个问题,并在SavedRequestAwareAuthenticationSuccessHandler的更新中显示了Bob's blog