2010-12-29 18 views
8

Cuando trabajo con Spring Security + CAS sigo golpeando un pequeño bloque con la URL de devolución de llamada que se envía a CAS, es decir, la propiedad del servicio. He visto un montón de ejemplos como this y this, pero todos usan URL codificadas (incluso Spring's CAS docs). Un recorte típica se ve algo como esto ...Cómo establecer correctamente la URL del servicio en las propiedades de servicio CAS de 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> 

En primer lugar, no quiero a codificar el nombre del servidor o el puerto ya que quiero esta guerra para poder desplegarse en cualquier lugar y no quiero que mi solicitud vinculado a una entrada de DNS particular en tiempo de compilación. En segundo lugar, no entiendo por qué Spring no puede detectar automáticamente el contexto de mi aplicación y la URL de la solicitud para construir automágicamente la URL. La primera parte de esa afirmación sigue en pie, pero como Raghuram señaló a continuación con this link, no podemos confiar en el Encabezado de host HTTP del cliente por razones de seguridad.

Idealmente, me gustaría que la URL del servicio sea exactamente lo que el usuario solicitó (siempre que la solicitud sea válida, como un subdominio de mycompany.com) por lo que es transparente o, al menos, me gustaría especificar solo alguna ruta relativa a mi raíz de contexto de aplicaciones y que Spring determine la URL del servicio sobre la marcha. Algo parecido a lo siguiente ...

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

O ...

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

Es todo esto es posible o fácil o que han perdido el obvio?

+0

Estoy usando spring 3; tenga en cuenta el enlace a la seguridad de primavera 3 documentos –

+0

Quizás [este enlace] (https://jira.springsource.org/browse/SEC-1374) esté relacionado y proporcione algunas ideas sobre su requisito/problema. – Raghuram

+0

Bueno, ciertamente aprendí algo y eliminé una posible solución. Como no puedo confiar en la solicitud de HTTP, me gustaría configurar el servicio a través de algunos valores derivados en el momento del despliegue, lo que debería ser seguro. –

Respuesta

4

En primavera 2.6.5 primavera se podría extender org.springframework.security.ui.cas.ServiceProperties

En la primavera 3 el método es definitiva podría obtener alrededor de esta subclase del CasAuthenticationProvider y CasEntryPoint y luego utilizar con su propia versión de ServiceProperties y anula el método getService() con una implementación más dinámica.

Puede usar el encabezado de host para calcular el dominio requerido y hacerlo más seguro al validar que solo se usan dominios/subdominios bajo su control. A continuación, agregue a esto algún valor configurable.

Por supuesto, usted correría el riesgo de que su implementación fuera insegura ... así que tenga cuidado.

que podría terminar pareciéndose a:

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

Según el documento vinculado, estoy utilizando Spring Security 3, que tiene todos los métodos etiquetados como final (http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/ security/cas/ServiceProperties.html) –

+0

Entonces, como dice el problema, podría subclasificar CasAuthenticationProvider y CasEntryPoint y proporcionarle una versión de las propiedades del servicio. He actualizado la respuesta para que sea más explícita – Pablojim

+0

Creo que probablemente sea correcta. No he tenido la oportunidad de probar esto todavía, pero cuando lo haga, y a menos que surja una mejor respuesta, parece que va a ser la mejor respuesta. –

2

uso experto, añadir un marcador de posición de la propiedad, y configurarlo en su proceso de construcción

+0

o use los perfiles de resorte – chrismarx

0

Intenté la subclase CasAuthenticationProvider como sugiere Pablojim, pero la solución es muy fácil. con Spring Expression Language (SPEL) puede obtener la url dinamicamente.

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

+0

Solo para aclarar a cualquiera que se emocione con esta respuesta, el hostName que recupera será el nombre del servidor real, no el nombre de host de la solicitud. Si tiene diferentes versiones de la aplicación que se ejecutan en diferentes servidores físicos, esto puede ser ideal – chrismarx

5

sé que esto es un poco viejo, pero yo sólo tenía que resolver este mismo problema y realmente no pude encontrar nada en las pilas nuevas.

Tenemos varios entornos que comparten el mismo servicio CAS (piense en entornos de desarrollo dev, qa, uat y locales); tenemos la capacidad de acceder a cada entorno desde más de una url (a través del servidor web del lado del cliente a través de un proxy inverso y directamente al servidor de fondo mismo). Esto significa que especificar una sola URL es difícil en el mejor de los casos. Tal vez hay una manera de hacer esto pero poder usar un ServiceProperties.getService() dinámico. Probablemente agregue algún tipo de comprobación de sufijo de servidor para garantizar que la url no sea secuestrada en algún momento.

Esto es lo que hice para conseguir el flujo básico CAS trabajar independientemente de la URL utilizada para acceder al recurso protegido ...

  1. Sustituir el CasAuthenticationFilter.
  2. Anule CasAuthenticationProvider.
  3. setAuthenticateAllArtifacts(true) en el ServiceProperties.

Aquí está la forma larga de mi grano de configuración del resorte:

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

Sólo el grano de configuración del resorte de costumbre.

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

Algunos parámetros de configuración externalizados.

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

La clave anterior es la llamada setAuthenticateAllArtifacts(true). Esto hará que el validador ticket de servicio utilice la aplicación AuthenticationDetailsSource en lugar de un hard-coded ServiceProperties.getService() llamada

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

validador billete estándar ..

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

gancho estándar a una UserDetailsService existente

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

proveedor de autenticación estándar

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

clave aquí es la configuración 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; 
       } 
      }; 
     } 
    }; 
} 

crea dinámicamente la URL del servicio a partir del método makeDynamicUrlFromRequest(). Este bit se usa al validar el ticket.

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

Esta parte utiliza el mismo creador de url dinámico cuando CAS desea redirigir a la pantalla de inicio de sesión.

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

Esto es lo que hacemos de ella. Solo pasé en las propiedades de servicio para albergar el URI del servicio para el que estamos configurados. Utilizamos HATEAOS en el lado posterior y tienen una aplicación como:

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

Editar: esto es lo que hice para la lista de sufijos de servidores válidos ..

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

La información más útil que pude encontrar en esta publicación. Esto es increíble. Realmente estaba luchando con la implementación de algo así (por las mismas razones que usted) en nuestro complejo sistema. – Schaka

Cuestiones relacionadas