Как правильно установить URL службы в свойствах службы Spring CAS - PullRequest
8 голосов
/ 29 декабря 2010

При работе с Spring Security + CAS я продолжаю сталкиваться с небольшим блокпостом с URL-адресом обратного вызова, который отправляется в CAS, т. Е. Свойством service. Я просмотрел несколько примеров, таких как this и this , но все они используют жестко закодированные 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-адреса. Первая часть этого утверждения остается в силе, но, как указал Рагурам ниже с эта ссылка , мы не можем доверять HTTP-заголовку хоста от клиента по соображениям безопасности.

В идеале я хотел бы, чтобы URL службы был в точности таким, какой запрашивал пользователь (при условии, что запрос действителен, например, субдомен mycompany.com), поэтому он является бесшовным или, по крайней мере, я хотел бы указать только некоторые путь относительно корня контекста моего приложения, и 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>

Возможно ли это или просто, или я упустил очевидное?

Ответы [ 5 ]

5 голосов
/ 18 февраля 2015

Я знаю, что это немного устарело, но мне просто нужно было решить эту проблему, и я не смог ничего найти в новых стеках.

У нас есть несколько сред, использующих одну и ту же службу CAS (например, dev, qa, uat и локальные среды разработки);у нас есть возможность получать доступ к каждой среде с более чем одного URL-адреса (через веб-сервер на стороне клиента через обратный прокси-сервер и непосредственно к самому внутреннему серверу).Это означает, что указание одного URL в лучшем случае затруднительно.Может быть, есть способ сделать это, но можно использовать динамический ServiceProperties.getService().Я, вероятно, добавлю какую-нибудь проверку суффикса сервера, чтобы убедиться, что URL-адрес не был взломан в какой-то момент.

Вот что я сделал, чтобы заставить работать основной поток CAS независимо от URL, используемого для доступа к защищенному ресурсу ...

  1. Переопределить CasAuthenticationFilter.
  2. Переопределите CasAuthenticationProvider.
  3. setAuthenticateAllArtifacts(true) на ServiceProperties.

Вот длинная форма моего компонента конфигурации пружины:

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

Просто обычный компонент конфигурации пружины.

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

Стандартный способ подключения ксуществующий UserDetailsService

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

Динамически создает 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;
}

В этой части используется тот же создатель динамического URL, когда 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();
}
4 голосов
/ 09 января 2011

Весной 2.6.5 весной вы можете расширить 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 голосов
/ 30 марта 2011

используйте maven, добавьте заполнитель свойства и настройте его в процессе сборки

0 голосов
/ 06 сентября 2013

Я пытался создать подкласс CasAuthenticationProvider, как предлагает Паблоджим, но решение очень просто! с помощью Spring Expression Language (SPEL) вы можете получить URL динамически.

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

0 голосов
/ 26 июня 2012

Я сам не пробовал, но, похоже, у Spring Security есть решение этой проблемы с SavedRequestAwareAuthenticationSuccessHandler, показанным в обновлении Блог Боба .

...