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

And*_*ite 8 java spring cas spring-security

使用Spring Security + CAS时,我会一直使用发送到CAS的回调URL(即服务属性)来触及一个小路障.我已经看了一堆的例子,如,但它们都使用硬编码网址(甚至Spring的CAS文档).一个典型的剪辑看起来像这样......

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="http://localhost:8080/click/j_spring_cas_security_check" />
  </bean>
Run Code Online (Sandbox Code Playgroud)

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

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

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="/my_cas_callback" />
  </bean>
Run Code Online (Sandbox Code Playgroud)

要么...

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

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

Mat*_*att 5

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

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

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

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

这是我的spring配置bean的长形式:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter {
Run Code Online (Sandbox Code Playgroud)

只是通常的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;
Run Code Online (Sandbox Code Playgroud)

一些外化配置参数.

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

上面的关键是setAuthenticateAllArtifacts(true)电话.这将使服务票证验证器使用AuthenticationDetailsSource实现而不是硬编码ServiceProperties.getService()调用

@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
    return new Cas20ServiceTicketValidator(casServerUrl);
}
Run Code Online (Sandbox Code Playgroud)

标准机票验证器..

@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);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

现有UserDetailsS​​ervice的标准挂钩

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

标准认证提供商

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

关键是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;
                }
            };
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

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

@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;
}
Run Code Online (Sandbox Code Playgroud)

当CAS想要重定向到登录屏幕时,此部分使用相同的动态URL创建者.

private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
    return "https://howeverYouBuildYourOwnDynamicUrl.com";
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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();
}
Run Code Online (Sandbox Code Playgroud)


Pab*_*jim 4

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

在 spring 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>
Run Code Online (Sandbox Code Playgroud)