2010-12-29 9 views
8

Podczas pracy z programem Spring Security + CAS wciąż trafiam na mały blok drogi z adresem zwrotnym, który jest wysyłany do CAS, tj. Usługą. Spojrzałem na kilka przykładów, takich jak this i this, ale wszystkie używają zakodowanych adresów URL (nawet Spring's CAS docs). Typowym wycinek wygląda mniej więcej tak ...Jak poprawnie ustawić adres URL usługi w usługach Springa w usłudze 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> 

Po pierwsze, nie chcę, aby ciężko kodem nazwę serwera lub port ponieważ chcę tej wojny będzie rozmieszczenia w dowolnym miejscu i nie chcę mojej aplikacji przywiązany do określonego wpisu DNS podczas kompilacji. Po drugie, nie rozumiem, dlaczego Spring nie może automatycznie wykryć kontekstu mojej aplikacji i adresu URL żądania, aby automagicznie zbudować adres URL. Pierwsza część tego oświadczenia nadal jest ważna, ale jak wskazano poniżej w Raghuram pod numerem this link, nie możemy ufać nagłówkowi HTTP Host z klienta ze względów bezpieczeństwa.

Idealnie chciałbym, aby adres URL usługi był dokładnie tym, o co prosił użytkownik (o ile żądanie jest ważne, takie jak poddomena mycompany.com), więc jest bezproblemowy lub przynajmniej chciałbym tylko określić niektóre ścieżki w kontekście katalogu głównego kontekstu aplikacji i mają Spring określający URL usługi w locie. Coś jak poniżej ...

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

albo ...

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

Czy cokolwiek z tego możliwe lub proste lub zostały pominięte, co oczywiste?

+0

Używam sprężyny 3; zwróć uwagę na link do wiosennego zabezpieczenia 3 dokumenty –

+0

Być może [ten link] (https://jira.springsource.org/browse/SEC-1374) jest powiązany i daje wgląd w twoje wymagania/problem? – Raghuram

+0

Cóż, z pewnością nauczyłem się czegoś i wyeliminowałem jedno możliwe rozwiązanie. Ponieważ nie mogę polegać na żądaniu HTTP, nadal chciałbym ustawić usługę za pomocą niektórych wyprowadzonych wartości w czasie wdrażania, który powinien być bezpieczny. –

Odpowiedz

4

Wiosną 2.6.5 wiosną można przedłużyć org.springframework.security.ui.cas.ServiceProperties

Wiosną 3 metoda jest ostateczna można obejść ten problem przez instacji CasAuthenticationProvider i CasEntryPoint a następnie używać z własną wersję ServiceProperties i zastąpić metodę getService() bardziej dynamiczną implementacją.

Możesz użyć nagłówka hosta, aby obliczyć wymaganą domenę i zwiększyć jej bezpieczeństwo, sprawdzając, czy używane są tylko domeny/subdomeny pod kontrolą użytkownika. Następnie dołącz do tej wartości konfigurowalnej.

Oczywiście ryzykujesz, że twoje wdrożenie będzie niebezpieczne, ale ... więc bądź ostrożny.

To może skończyć się patrząc jak:

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

Zgodnie z połączonym dokumentem używam Spring Security 3, który ma wszystkie te metody oznaczone jako ostateczne (http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/ security/cas/ServiceProperties.html) –

+0

Tak więc, jak podaje problem, można po prostu podklasować CasAuthenticationProvider i CasEntryPoint i dostarczyć własną wersję właściwości usługi. Zaktualizowałem odpowiedź, aby była bardziej wyraźna. – Pablojim

+0

Myślę, że prawdopodobnie masz rację. Nie miałem okazji jeszcze tego wypróbować, ale kiedy to zrobię i jeśli nie pojawi się lepsza odpowiedź, wygląda na to, że to najlepsza odpowiedź. –

2

użytku Maven, dodać obiekt zastępczy i skonfigurować go w procesie budowania

+0

lub użyj profili sprężynowych – chrismarx

0

Próbowałem podklasy CasAuthenticationProvider, jak sugeruje Pabloimim, ale rozwiązanie jest bardzo proste! z językiem Spring Expression (SPEL) możesz uzyskać adres URL w sposób dinamiczny.

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

+0

Aby wyjaśnić, że ktoś jest podekscytowany tą odpowiedzią, nazwa hosta spowoduje pobranie rzeczywistej nazwy serwera, a nie nazwy hosta z żądania. Jeśli masz różne wersje aplikacji działającej na różnych serwerach fizycznych, może to być idealne. – chrismarx

5

wiem, że to jest nieco stary, ale miałem tylko do rozwiązania tego samego problemu i nie można było znaleźć coś w nowszych stosów.

Mamy wiele środowisk współdzielących tę samą usługę CAS (pomyśl o środowisku dev, qa, uat i lokalnym środowisku programistycznym); mamy możliwość trafienia w każde środowisko z więcej niż jednego adresu URL (za pośrednictwem serwera WWW po stronie klienta za pośrednictwem odwrotnego proxy i bezpośrednio na serwer zaplecza). Oznacza to, że określenie pojedynczego adresu URL jest w najlepszym wypadku trudne. Być może istnieje sposób, aby to zrobić, ale będąc w stanie używać dynamicznego ServiceProperties.getService(). Prawdopodobnie dodam jakiś sprawdzian sufiksów serwera, aby upewnić się, że URL nie zostanie w pewnym momencie przejęty.

Oto co zrobiłem, aby uzyskać podstawowe przepływ CAS pracuje niezależnie od adresu URL używanego do uzyskania dostępu do zasobu zabezpieczone ...

  1. przesłonić CasAuthenticationFilter.
  2. Zastąpienie CasAuthenticationProvider.
  3. setAuthenticateAllArtifacts(true) na ServiceProperties.

Oto długa forma mojego wiosennego konfiguracji fasoli:

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

Wystarczy zwykła konfiguracja wiosna fasoli.

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

Niektóre udostępnione parametry konfiguracyjne.

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

Kluczową rzeczą powyżej jest wywołanie setAuthenticateAllArtifacts(true). Spowoduje to, że weryfikator biletów używać realizację AuthenticationDetailsSource zamiast zakodowanej ServiceProperties.getService() rozmowy

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

Standardowy walidatora bilet ..

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

Standardowy hak do istniejącego UserDetailsService

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

Dostawca standardowego uwierzytelnienia

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

Kluczem tutaj jest dynamicServiceResolver() ustawienie ..

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

Dynamicznie tworzy adres URL usługi z metody makeDynamicUrlFromRequest(). Ten bit jest używany do sprawdzania poprawności biletu.

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

Ta część używa tego samego dynamicznego kreatora url, gdy CAS chce przekierować do ekranu logowania.

private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){ 
    return "https://howeverYouBuildYourOwnDynamicUrl.com"; 
} 

To wszystko, co z niego zrobisz. Przekazałem tylko właściwość ServiceProperties, aby przechowywać identyfikator URI usługi, dla której jesteśmy skonfigurowani. Używamy HATEAOS na tylnej stronie i mieć implementację takiego:

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

EDIT: oto co zrobiłem na liście ważnych przyrostków serwera ..

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

Najbardziej przydatne informacje, które mogę znaleźć w tym poście. To jest niesamowite. Naprawdę zmagałem się z implementacją czegoś takiego (z tych samych powodów) w naszym złożonym systemie. – Schaka

Powiązane problemy