2010-12-29 13 views
8

gesetzt, wenn mit Spring Security + CAS arbeite Ich behalte das Schlagen eine kleine Straßensperre mit der Callback-URL, die zu CAS, dh die Service-Eigenschaft gesendet wird. Ich habe an einer Reihe von Beispielen sieht wie this und this aber sie alle verwenden hartcodierte URLs (auch Spring's CAS docs). Ein typischer schnipp sieht ungefähr so ​​aus ...Wie richtig die Dienst-URL in Spring CAS Service-Eigenschaften

<bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties"> 
    <property name="service" value="http://localhost:8080/click/j_spring_cas_security_check" /> 
    </bean> 

Zuerst will ich nicht zu hart Code der Servernamen oder den Port, da ich diesen Krieg will überall sein einsetzbar und ich will nicht meine Bewerbung gebunden an einen bestimmten DNS-Eintrag zur Kompilierzeit. Zweitens verstehe ich nicht, warum Spring den Kontext meiner Anwendung und die URL der Anfrage automatisch erkennen kann, um die URL automatisch zu erstellen. Der erste Teil dieser Aussage stehen bleiben, sondern als Raghuram unten mit this link wies darauf hin, können wir nicht dem HTTP-Host Header vom Client aus Sicherheitsgründen vertrauen.

Idealerweise möchte ich, dass die Service-URL genau das ist, was der Benutzer angefordert hat (solange die Anfrage gültig ist, wie eine Subdomain von mycompany.com), so dass sie nahtlos ist oder ich zumindest nur angeben möchte ein Pfad relativ zu meinem Anwendungskontext root und lässt Spring die Dienst-URL im laufenden Betrieb bestimmen. So etwas wie die folgenden ...

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

ODER ...

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

Ist irgendetwas davon möglich oder einfach oder habe ich verpasst, das Offensichtliche?

+0

Ich verwende Feder 3; notieren Sie den Link zum Frühling Sicherheit 3 ​​Dokumente –

+0

Vielleicht ist [dieser Link] (https://jira.springsource.org/browse/SEC-1374) verwandt und gibt einige Einblicke in Ihre Anforderung/Problem? – Raghuram

+0

Nun, ich habe sicherlich etwas gelernt und eine mögliche Lösung beseitigt. Da ich mich nicht auf die HTTP-Anfrage verlassen kann, möchte ich den Dienst dennoch über einige abgeleitete Werte zur Bereitstellungszeit festlegen, die sicher sein sollte. –

Antwort

4

Im Frühjahr 2.6.5 Frühling Sie org.springframework.security.ui.cas.ServiceProperties

Im Frühjahr 3 ist das Verfahren endgültig verlängern könnte man dieses Problem umgehen, könnte die CasAuthenticationProvider und CasEntryPoint von Subklassen und verwenden Sie dann mit Ihre eigene Version von ServiceProperties und überschreiben Sie die getService() -Methode mit einer dynamischeren Implementierung.

Sie können die Host-Header verwenden, um die die gewünschte Domain zu berechnen und sie sicherer zu machen, indem die Validierung, dass nur Domains/Subdomains unter Ihrer Kontrolle verwendet werden. Dann fügen Sie diesem einen konfigurierbaren Wert hinzu.

Natürlich würden Sie gefährdet sein, dass Ihre Implementierung allerdings unsicher war ... so vorsichtig sein.

Es könnte am Ende aussehen wie:

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

Wie im verlinkten Dokument verwende ich Spring Security 3, die alle diese Methoden als endgültig markiert hat (http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/ security/cas/ServiceProperties.html) –

+0

Also, wie das Problem angibt, können Sie CasAuthenticationProvider und CasEntryPoint nur unterklassifizieren und bieten Sie eine eigene Version der Diensteigenschaften. Ich habe die Antwort aktualisiert, um es expliziter zu machen – Pablojim

+0

Ich denke, dass Sie wahrscheinlich richtig sind. Ich habe noch keine Gelegenheit gehabt, das auszuprobieren, aber wenn ich das tue, und es keine bessere Antwort gibt, dann sieht es so aus, als würde es die beste Antwort sein. –

2

Verwendung Maven, eine Eigenschaft Platzhalter hinzufügen und konfigurieren Sie es in Ihrem Build-Prozess

+0

oder verwenden Sie Spring-Profile – chrismarx

0

Ich habe versucht, CasAuthenticationProvider als Pablojim vorschlagen, aber die Lösung ist sehr viel einfacher! Mit Spring Expression Language (SPEL) können Sie die URL dynamisch abrufen.

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

+0

Nur um für jeden, der sich von dieser Antwort begeistert, zu klären, wird der Hostname, der diesen aufruft, der Name des tatsächlichen Servers sein, nicht der Hostname aus der Anfrage. Wenn Sie verschiedene Versionen der App auf verschiedenen physischen Servern ausführen, kann dies ideal sein – chrismarx

5

Ich weiß, das ist ein bisschen alt, aber ich hatte gerade genau dieses Problem zu lösen, und nicht wirklich etwas in den neueren Stapeln finden kann.

Wir haben mehrere Umgebungen, die den gleichen CAS-Dienst teilen (think dev, qa, uat und lokale Entwicklungsumgebungen); Wir haben die Möglichkeit, jede Umgebung von mehr als einer URL aus zu erreichen (über den Client-seitigen Webserver über einen Reverse-Proxy und direkt zum Back-End-Server selbst). Dies bedeutet, dass die Angabe einer einzelnen URL bestenfalls schwierig ist. Vielleicht gibt es einen Weg, dies zu tun, aber in der Lage, eine dynamische ServiceProperties.getService() zu verwenden. Ich werde wahrscheinlich eine Art Server-Suffix-Prüfung hinzufügen, um sicherzustellen, dass die URL irgendwann nicht entführt wird.

Hier ist, was ich getan habe, unabhängig von der die gesicherten Ressource für den Zugriff auf URL arbeiten, um den Grund CAS Fluss zu bekommen ...

  1. Aufschalten die CasAuthenticationFilter.
  2. Überschreiben Sie die CasAuthenticationProvider.
  3. setAuthenticateAllArtifacts(true) über die ServiceProperties.

Hier ist die lange Form meiner Feder Konfigurations-Bean:

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

Nur die übliche Federkonfiguration Bohne.

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

Einige externe Konfigurationsparameter.

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

Der Schlüssel oben ist der setAuthenticateAllArtifacts(true) Aufruf. Dies wird der Service-Ticket-Validator die AuthenticationDetailsSource Implementierung eher als ein hartcodierte ServiceProperties.getService() Anruf

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

Standard-Ticket-Validator ..

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

Standard-Haken zu einem bestehenden UserDetailsService verwenden macht

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

Standardauthentifizierungsanbieter

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

Key ist hier die dynamicServiceResolver() Einstellung ..

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

dynamisch Service URL aus der makeDynamicUrlFromRequest() Methode erstellt. Dieses Bit wird bei der Ticketvalidierung verwendet.

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

Dieser Teil verwendet denselben dynamischen URL-Ersteller, wenn CAS zum Anmeldebildschirm umleiten möchte.

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

Das ist was auch immer Sie daraus machen. Ich habe nur die ServiceProperties übergeben, um den URI des Dienstes zu speichern, für den wir konfiguriert sind. Wir verwenden HATEAOS auf der Rückseite und haben eine Implementierung wie:

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

Edit: hier ist das, was ich für die Liste der gültigen Server-Suffixe haben ..

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

Die nützlichsten Informationen, die ich auf diesem Post finden konnte. Das ist großartig. Ich hatte wirklich Probleme damit, so etwas in unserem komplexen System zu implementieren. – Schaka