2010-12-29 3 views
8

При работе с Spring Security + CAS я продолжаю удалять небольшой дорожный блок с URL-адресом обратного вызова, который отправляется в CAS, т. Е. Свойство службы. Я рассмотрел множество примеров, таких как this и this, но все они используют жестко закодированные URL-адреса (даже Spring's CAS docs). Типичный надрез выглядит примерно так ...Как правильно установить URL службы в свойствах службы CAS Spring

<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-адреса. Первая часть этого заявления все еще стоит, но как сказал Рагхурам ниже с this link, мы не можем доверять HTTP-заголовку HTTP от клиента по соображениям безопасности.

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

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

+0

Я пользуюсь весной 3; обратите внимание на ссылку на весеннюю безопасность. 3 docs –

+0

Возможно, [эта ссылка] (https://jira.springsource.org/browse/SEC-1374) связана и дает некоторое представление о ваших требованиях/проблемах? – Raghuram

+0

Ну, я, конечно, кое-что узнал и устранил одно возможное решение. Поскольку я не могу полагаться на HTTP-запрос, я все равно хотел бы установить службу через некоторые производные значения во время развертывания, которые должны быть безопасными. –

ответ

4

Весной 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> 
+0

В соответствии с связанным документом я использую Spring Security 3, который имеет все эти методы, помеченные как final (http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/ security/cas/ServiceProperties.html) –

+0

Итак, как говорится в проблеме, вы можете просто подклассифицировать CasAuthenticationProvider и CasEntryPoint и предоставить вам собственную версию свойств службы. Я обновил ответ, чтобы сделать его более явным. – Pablojim

+0

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

2

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

+0

или использовать весенние профили – chrismarx

0

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

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

+0

Чтобы уточнить, кто из тех, кого волнует этот ответ, имя хоста, которое он получает, будет именем фактического сервера, а не именем хоста из запроса. Если у вас разные версии приложений, работающих на разных физических серверах, это может быть идеальным. – chrismarx

5

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

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

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

  1. Override 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(); 

Edit: вот что я сделал для списка допустимых суффиксов сервера ..

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

Самая полезная информация, которую я мог найти на этом посту. Это потрясающе. Я действительно боролся с реализацией чего-то подобного (по тем же причинам, что и вы) в нашей сложной системе. – Schaka