繁体   English   中英

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

[英]How to correctly set the service URL in Spring's CAS service properties

使用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>

首先,我不想硬编码服务器名称或端口,因为我希望这个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>

要么...

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

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

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

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

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

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

这是我的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()方法动态创建服务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;
}

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

在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>

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

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

示例: <property name="service" value="https://#{T(java.net.InetAddress).getLocalHost().getHostName()}:${application.port}${cas.service}/login/cascheck"/>

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM