miércoles, 28 de agosto de 2013

Integrando Spring Security con el Framework ZK

clip_image002

Spring Security es una solución común para el desarrollador para satisfacer las necesidades de seguridad de una aplicación web de Java, que es ampliamente utilizado y es una tecnología probada. Sin embargo, debido a su naturaleza para proteger los recursos por parte de patrones a través de URL, no es obvia para los desarrolladores de aplicaciones para darse cuenta de la Seguridad de Spring se puede adaptar con una petición Ajax mecanismo de manejo específico de un marco de Ajax [1].

Así que en este artículo, voy a presentarles cómo integrar Spring Security con ZK sin problemas por ir a través de la construcción de una aplicación de demostración sencilla (Una publicación del artículo y un sistema de edición).

Recursos para descargar

Puede descargar el código fuente de aquí

El proyecto está basado en Maven, si quieres probar diferentes versiones de ZK o spring, cambie el número de versión en pom.xml

Demostración Detalles de la aplicación

Esta aplicación Demo es un simple artículo publicar y editar sistema que permite tres tipos de usuarios para acceder a:

  • usuario con rol ROLE_USER
  • usuario con rol ROLE_EDITOR
  • usuario anónimo con rol IS_AUTHENTICATED_ANONYMOUSLY

clip_image004

  • El usuario Anónimo puede visitar la página de inicio que contiene la lista de artículos, y se puede ver el contenido de un artículo haciendo clic en el enlace de página de inicio.
  • Al usuario con rol ROLE_USER se le permite publicar artículos nuevos y editar sus propios artículos.
  • El usuario con rol ROLE_EDITOR es el más poderoso, es capaz de editar y borrar cualquier artículo.

Este artículo se basa en los requisitos de implementación de esta aplicación para demostrar la integración de Spring Security con ZK.

Configuración de Spring Security

En primer lugar, vamos a ver cómo configurar nuestro proyecto. Para utilizar Spring Security, tenemos que añadir algunos oyentes y declaraciones de filtro en web.xml

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/applicationContext.xml

/WEB-INF/applicationContext-security.xml

</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<listener>

<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>

</listener>

<listener>

<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>

</listener>

<filter><!-- the filter-name must be preserved, do not change it! -->

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>


Como se puede ver, a pesar de los oyentes de contexto ordinarios de Spring (RequestContextListener y ContextLoaderListener), declaramos HttpSessionEventPublisher y springSecurityFilterChain de Spring Security. Aquí HttpSessionEventPublisher es opcional y está diseñada para Spring Security para hacer el control de sesión simultánea detallada, springSecurityFilterChain es el gancho principal de toda la funcionalidad de Spring Security de la aplicación, es necesario y debe ser nombrado springSecurityFilterChain.

applicationContext-security.xml

Aquí, en este proyecto, separamos el fichero applicationContext.xml de Spring en dos archivos, el original applicationContext.xml es para el backend bean y el service bean declarations y el adicional applicationContext-security.xml es sólo para la configuración de Spring Security.

En applicationContext-security.xml, hay dos elementos importantes que tenemos que configurar, que son el elemento <http> y el elemento <authentication-manager>.

Configuración del elemento Http

El elemento <http> es para decirle a Spring qué tipo de recursos necesita ser asegurado, el puerto que será utilizado por contenedor para conexiones http y https, y qué tipo de inicio de sesión en solución se utiliza en esta aplicación web.

<http auto-config="true">

<port-mappings>

<port-mapping http="8080" https="8443"/>

</port-mappings>

<intercept-url pattern="/zkau/**" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="any"/>

<intercept-url pattern="/login.zul" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https" />

<intercept-url pattern="/newArticle.zul" access="ROLE_USER" requires-channel="https" />

<intercept-url pattern="/j_spring_security_check" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https" />

<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="any" />

<session-management session-fixation-protection="none" />

<form-login login-page="/login.zul"

authentication-failure-url="/login.zul?login_error=1"

login-processing-url="/j_spring_security_check"/>

<logout logout-success-url="/index.zul" invalidate-session="true" />

</http>


Configuración del Authentication Manager

La declaración de elemento <authentication-manager> está diseñado para gestionar authentication-provider, que es el proveedor de la instancia de autenticación de Spring y hará el trabajo real de la autenticación. Puede declarar múltiples authentication-providers, con el fin de aprovechar distintas fuentes de los usuarios (por ejemplo: openidAuthenProvider o LDAPAuthenProvider), en este proyecto se extiende UserDetailsService del authentication-provider predeterminado:

<authentication-manager>

<authentication-provider user-service-ref="myUserDetailsService">

<password-encoder hash="md5" />

</authentication-provider>

</authentication-manager>

<beans:bean id="myUserDetailsService"

class="org.zkoss.demo.springsec.model.MyUserDetailsService"/>

La protección de una solicitud de página

Después de la configuración de Spring Security, ahora, vamos a ver cómo utilizarla para proteger a una solicitud de archivo zul.

Escenario: cuando el usuario solicita un recurso limitado

En nuestro proyecto de demostración, NewArticle.zul es un recurso limitado que sólo pueden acceder los usuarios que ya se ha iniciado sesión, lo que es natural, porque para poder hacer un artículo, tenemos que saber quién es el autor. En Spring Security, para restringir un recurso para cierto usuario (que tiene suficiente permiso) para tener acceso es muy sencillo, simplemente declaramos elemento <intercept-url> bajo <http> en applicationContext-security.xml:

<intercept-url pattern="/newArticle.zul" access="ROLE_USER" requires-channel="https" />

Aquí el atributo patrón se utiliza para determinar qué solicitud esta configuración tendrá efecto, y si la dirección URL de una solicitud coincide con este patrón, el atributo de acceso se utilizará para comprobar autorizaciones del usuario actual.

Como podemos ver, la solicitud de protección de página en Spring Security es muy sencilla e intuitiva, todo se basa en la comparación de patrones de solicitar la ruta url.

Log-in Page Implementation in ZK


Now, after the newArticle.zul has been secured, the log-in page(login.zul) which is required for authentication process has to be implemented for user to perform log in. Here let's see how to implement a log-in page in ZUL:

Implementación de la Página Log-in en ZK

Ahora, después de que el newArticle.zul ha sido asegurado, la página de log-in (login.zul) que se requiere para proceso de autenticación tiene que ser implementada para que usuario pueda acceder:

<html:form id="f" name="f" action="j_spring_security_check" method="POST"

xmlns:html="native">

<grid>

<rows>

<row>User: <textbox id="u" name="j_username"/></row>

<row>Password: <textbox id="p" type="password" name="j_password"/></row>

<row spans="2">

<hbox>

<html:input type="reset" value="Reset"/>

<html:input type="submit" value="Submit Query"/>

<button type="submit" label="Accedi_trendy" mold="trendy"/>

<button type="submit" label="Accedi_os" />

</hbox>

</row>

</rows>

</grid>

</html:form>


  1. Un elemento HTML <form> tiene que ser declarado fuera de las entradas de nombre de usuario y contraseña.
  2. El formulario debe contener dos elementos de entrada denominados j_username y j_passoword. Para aplicar un efecto ajax y las características de estos insumos, se puede utilizar el componente <textbox> en su lugar.
  3. El Atributo action Form tiene que asignar el valor del atributo login-processing-url del elemento <form-login> en applicationContext-security.xml

Si un usuario anónimo, hace clic en el botón Nuevo Artículo en página de inicio, será re-direccionado a nuestro login.zul.

Asegurar Vista parcial mediante el uso de EL en ZUL

Con el fin de asegurar parte de la vista parcial de la página web, Spring Security tiene su propio taglib que brinda soporte básico para el acceso a la información de seguridad y la aplicación de restricciones de seguridad en las páginas JSP. Y aquí la seguridad en ZUL, marco ZK también proporciona métodos para definir la nueva biblioteca de etiquetas. En nuestra aplicación de ejemplo, para el uso de Spring Security a través de EL hicimos nuestra propia biblioteca de etiquetas.

Biblioteca de etiquetas Zul para Spring Security.

Aunque ZK Developer Reference proporciona información muy detallada sobre lo que podemos hacer en un taglib personalizado de ZK, aquí sólo nos centramos en lo que queremos tener para Spring Security.

En primer lugar, vamos a crear un security.tld bajo / WEB-INF /. Sin embargo, para los proyectos más serios, se puede hacer la ruta de clases: Método metainfo / tld / config.xml como se menciona en la Referencia del programador.

En segundo lugar, se definen las funciones de EL / WEB-INF/security.tld, aquí está el ejemplo:

<taglib>

<uri>http://www.zkoss.org/demo/integration/security</uri>

<description>

Methods and actions for ZK + Spring Security

</description>

<function>

<name>isAllGranted</name>

<function-class>org.zkoss.demo.springsec.SecurityUtil</function-class>

<function-signature>

boolean isAllGranted(java.lang.String authorities) {

</function-signature>

<description>

Return true if the authenticated principal is granted authorities

of ALL the specified roles.

</description>

</function>

...

Como podemos ver, el documento debe comenzar con un elemento raíz: <taglib> con un elemento <uri> en ella. A continuación, se puede declarar funciones. En la declaración de la función, mapeamos un método (isAllGranted ()) de org.zkoss.demo.springsec.SecurityUtil a nuestra función EL isAllGranted, sobre la SecurityUtil, la aplicación de la misma se basa en SecurityContextHolder de Spring Security.

Ahora vamos a ver cómo utilizar taglib personalizado Spring Security en ZUL.

Modo de Uso: Protección de Acción

En nuestro proyecto de demostración, de acuerdo con los casos de uso más arriba, tenemos una función "eliminar artículo" que sólo usuario con rol: ROLE_EDITOR puede acceder. Ahora, con nuestras bibliotecas de etiquetas personalizadas para Spring Security, podemos establecer algunas limitaciones de esta manera:

<?taglib uri="/WEB-INF/security.tld" prefix="sec"?>

...

<button id="deleteBtn" label="Delete"

if="${sec:isAllGranted('ROLE_USER')}"

disabled="${not sec:isAllGranted('ROLE_EDITOR')}"/>

...

Atributos Especiales del componente ZK para la Seguridad

En el código de ejemplo anterior, se ha utilizado el atributo personalizado EL + disabled del botón de ZK para proteger el clic del usuario, ZK también ofrece otras características que pueden ser aplicables para la restricción de seguridad tales como visible y de sólo lectura (para combobox). Estos atributos que se basan en efectos html son muy útiles para cumplir con los requisitos de la escalada de permiso para acceder a la interfaz de usuario.

Protegiendo la solicitud de ZK Ajax

En un marco como el Ajax ZK, hay dos tipos de solicitudes. Ahora, vamos a hablar de la parte más difícil de esta integración es decir, cómo hacer frente a la petición de Ajax ZK usando Spring Security?

En primer lugar, tenemos que echar un vistazo a cómo proteger una solicitud de página de una manera más programática.

Haga Security Check en Listener de ZK

En algunas situaciones, puede ser que necesite el usuario para hacer control de seguridad en el código java no en ZUL, por ejemplo, en nuestro accessDeniedExTest.zul, tenemos un iniciador que hacer el registro de seguridad en el método public void doInit(Page page, Map<String, Object> args) throws Exception.

public class AccessDeniedExInit extends GenericInitiator {

public void doInit(Page page, Map<String, Object> args) throws Exception {

if(SecurityUtil.isNoneGranted("ROLE_EDITOR")){

throw new AccessDeniedException("this is a test of AccessDeniedException!");

}

}

}

El AccessDeniedException es clave para la cadena de filtros de Spring Security para manejar, y todo funciona bien en una situación de solicitud de la página, pero lo que si tiramos la excepción en una acción que dice EventListener de ZK?

Chequeo de Seguridad en Acciones de ZK.

Lanzando AccessDeniedException durante una solicitud Ajax no activar el proceso de autenticación de Spring Securit. Esta autorización se debe a la perspectiva del ZK AU Engine, que es imposible para cualquier otro outsider de org.zkoss.zk.au.http. DHtmlUpdateServlet saber cómo componer una respuesta significativa y correctamente estructurada para el motor del cliente en el navegador. Por lo que cualquier excepción que debe ser manejado dentro de ZK AU Engine.

Para hacer esto tenemos que convertir petición Ajax ZK a una petición de una página normal, así que podemos dejar Spring Security para manejar la AccessDeniedException en una solicitud de página normal. la puesta en práctica de esta idea incluye 3 pasos:

PASO 1: Convertir Ajax Request to Request

En primer lugar, tenemos que decirle ZK queremos encargo del mecanismo de control de errores para AccessDeniedException. Para ello es muy simple, en zk.xml añadimos esta sección:

<error-page>

<exception-type>org.springframework.security.access.AccessDeniedException</exception-type>

<location>security_process.zul</location>

</error-page>

Manejo de errores de ZK es muy sencillo, se asigna un tipo de error a una página ZUL, la página ZUL se analizará con base en el escritorio actual, si ocurre tal error. En nuestro security_process.zul simplemente tenemos un iniciador dentro de manejar AccessDeniedException:

<?init class="org.zkoss.demo.springsec.ui.error.SpringSecurityHandleInit"?>

<zk>

<!-- DO NOTHING! -->

</zk>

Luego, en SpringSecurityHandleInit, podemos comprobar si se trata de una petición Ajax, almacenamos todos los datos necesarios en la sesión y le pedimos al motor del cliente para hacer el cambio de dirección de nuevo a este security_process.zul nuevo.

if(exec.isAsyncUpdate(null) ){

//STEP 1: convert Ajax Request to Page Request(Error Handling Page Request)

System.out.println(">>>> Security Process: STEP 1");

if(ex instanceof AccessDeniedException){

sess.setAttribute(VAR_DESKTOP_REQ_URI, getOriginalDesktopUri());// for login-success-url

sess.setAttribute(VAR_SPRING_SECURITY_ERROR, ex);

Executions.sendRedirect(toSecurityProcessUrl((AccessDeniedException) ex));// GOTO STEP 2 by redirection.

}else{

throw new IllegalArgumentException(

"How come an unexpected Exception type will be mapped to this handler? please correct it in your zk.xml");

}

PASO 2: Lanzar AccessDeniedException de redirección para adaptarse Proceso de seguridad Resorte

El cambio de dirección será de nuevo a este security_process.zul nuevo, y porque esta vez estamos dentro de una solicitud de página, se puede obtener la excepción de regresar de HttpSession y tírelo a la basura para que la cadena de filtros de Spring Security para manejarlo.

Exception err = (Exception) sess.getAttribute(VAR_SPRING_SECURITY_ERROR);

String dtPath = (String) sess.getAttribute(VAR_DESKTOP_REQ_URI);

if(err!=null){

//STEP 2: throw Error in Error Handling Page Request.

System.out.println(">>>> Security Process: STEP 2");

sess.removeAttribute(VAR_SPRING_SECURITY_ERROR);

throw err;// we suppose Spring Security Error Filter Chain will handle this properly.

}

Ahora, Spring Security comprobará principio del usuario actual y hacer el proceso de autenticación o autorización. En el proceso de autenticación, el usuario será redirigido a la login.zul como lo hemos configurado, y si log-in es el éxito, el usuario será redirigido a la ubicación original donde se produjo esta excepción y la ubicación aquí será nuestra security_process.zul que es no es el sitio original en el que la petición Ajax causó el error. Así que tenemos el tercer paso para manejar esta situación.

PASO 3: Maneje el Login Success redirección

Vamos a volver a ver el código de ejemplo de paso 1, el código

sess.setAttribute(VAR_DESKTOP_REQ_URI, getOriginalDesktopUri());

reservado la ruta de solicitud original del ZUL que generó el escritorio, y que se prepara la información para STEP 3, la aplicación de la forma de recuperar la ruta original del escritorio (impl de getOriginalDesktopUri ()) es la siguiente:

private static String getOriginalDesktopUri(){

// developer may implement this part to adapt to PushState or any other Page based Framework, that might have interference to request URI.

String str = Executions.getCurrent().getDesktop().getRequestPath();

String qs = Executions.getCurrent().getDesktop().getQueryString();

System.out.println(">>>security Process: Desktop path= "+str);

return str+"?"+qs;

}

Y ahora en el PASO 3, simplemente redirigir usuarios de nuevo a nuestro camino escritorio:




else if(dtPath!=null){

System.out.println(">>>> Security Process: STEP 3");

//STEP 3: if Spring Security Authentication was triggered at STEP 2,

//then we need STEP 3 to redirect back to original URI the very first desktop belongs to.

sess.removeAttribute(VAR_DESKTOP_REQ_URI);

exec.sendRedirect(dtPath);

}

Para más detalles, por favor, eche un vistazo al código fuente de SpringSecurityHandleInit.

Uso: Proteja botón Abrir Editor

Después de tejer ZK petición Ajax al Spring Security, vamos a ver cómo aplicar restricción de seguridad a la acción del usuario en nuestro proyecto de demostración, en primer lugar, en uno de nuestros casos de uso del usuario escenario se le permite editar un artículo si es el autor o tiene 'ROLE_EDITOR , por lo que el botón de editor abierto en ArticleContentViewCtrl para articleContent.zul se implementa como esto:

@Listen("onClick=#openEditorBtn")

public void edit(){

//ownership & permission check.

if(!isOwner() && SecurityUtil.isNoneGranted("ROLE_EDITOR")){

throw new AccessDeniedException(

"The user is neither the author, nor a privileged user.");

}

ArticleEditor editor = new ArticleEditor();

editor.setParent(container);

editor.doHighlighted();

}

Como se muestra, tiramos AccessDeniedException directamente en un detector de eventos, y podemos hacer lo mismo en la acción ZK Ver Modelo:

public class TestVModel {

...

@Command

@NotifyChange("fullName")

public void doChange(){

if(SecurityUtil.isNoneGranted("ROLE_EDITOR")){

throw new AccessDeniedException("you are not an editor!");

}

}

}

Si está utilizando un bean de Spring con anotación @ Secured etiquetado de algunos métodos, cuando el usuario no pasa el control de seguridad, el AccessDeniedException lanzado será tratada de la misma manera.

Resumen

En este artículo, hablamos de cómo utilizar Spring Security en una aplicación web ZK, y también mostró una solución de adaptar petición Ajax ZK de cadena de filtros de Spring Security.

Esperamos que le sea útil y quedamos al tanto de sus comentarios.





¿Te ha gustado este Post? Compártelo con tus amigos.

2 comentarios:

  1. Puede integrarse zk con spring sin utilizar maven?

    ResponderEliminar
  2. \springsecdemo\src\main\java\org\zkoss\demo\springsec\ui\NewArticleViewCtrl.java:[ , ] error: package org.zkoss.zk.ui does not exist podria ayudarme con este error soy novato

    ResponderEliminar

IconIconIcon