Hasta Julio de 2021 Fundeweb sólo soportaba el login CAS a través de cuenta UM. A partir de esta fecha, CAS soporta login mediante diversos mecanimos (Certificado, Cl@ave, etc.). Para poder soportar desde aplicaciones Fundeweb que CAS utilice diferentes métodos de autenticación y poder reaccionar si alguna de nuestras aplicaciones no soportara algún método indicando los soportados deberemos seguir esta guía.
| Nota | ||
|---|---|---|
| ||
Si estás trabajando con una aplicación FundeWeb 1.x debes leer la guía ... |
Pasos a seguir
...
| title | Requisitos |
|---|
Para poder aplicar estos cambios en tu aplicación
...
necesitas FundeWeb IDE 2.0
...
o 2.1
...
.
Si no cumples los requisitos puedes ...
Hay que modificar o añadir las siguientes carpetas y ficheros:
...
AuthenticationManagerBean.java
Añadir esta clase:
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
package es.um.atica.apium.security.authentication;
public class AuthenticationMethodNotSupportedException extends RuntimeException {
private static final long serialVersionUID = -7140000370097499128L;
private static final String MENSAJE = "No se soporta el método de autenticación: ";
public AuthenticationMethodNotSupportedException( String message ) {
super( MENSAJE + message );
}
public AuthenticationMethodNotSupportedException( Throwable cause ) {
super( MENSAJE, cause );
}
public AuthenticationMethodNotSupportedException( String message, Throwable cause ) {
super( MENSAJE + message, cause );
}
} |
AuthenticationMethodSSO.java
- Asegurar que AuthenticationManagerBean extiende la clase AbstractAuthenticationManagerBean.
- Añadir los métodos de autenticación validos que ofrece el SSO.
Este es un ejemplo en el que solo admitimos la autenticación mediante correo del SSO para acceder a la aplicaciónAñadir esta clase que es idéntica a es.um.atica.seam.security.authentication.method.AuthenticationMethodSSO.java salvo porque sobrescribe al final el método preAuthenticate() (línea 93):
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
package es.um.atica.apiumXXXX.security.authentication; import java.nio.charset.StandardCharsets; import java.util.Map; import org.jboss.seam.logannotations.LogFactory; import org.jboss.seam.log.Logging; import org.jboss.seam.util.Strings; import org.umu.atica.servicios.gesper.gente.entity.Persona; import org.umu.atica.servicios.gesper.gente.exceptions.PersonaException; import org.umu.atica.servicios.gesper.gente.exceptions.PersonaNotFoundException; import buscador.servicios.exceptions.ServiceNotFoundException; import es.um.atica.seam.security.authentication.Constants; import es.um.atica.seam.security.authentication.method.AuthenticationMethod; import es.um.atica.seam.utils.CasClient3UtilSSOAuthenticationMethods; /** @Name( * Clase para definir el metodo de autenticación por SSO mediante CAS de la UMU. * * @author juanmiguelbg * @version 0.0.3 */"authenticationManagerBean" ) @Scope( SESSION ) @Install( precedence = FRAMEWORK ) @BypassInterceptors @Startup public class AuthenticationMethodSSOAuthenticationManagerBean extends AuthenticationMethodAbstractAuthenticationManagerBean { private// staticDeclaracion finalde Loglos LOGmetodos = Logging.getLog(AuthenticationMethodSSO.class); private static final String CLIENT_NAME_KEY = "clientName"; de autenticacion validos por SSO private static final StringSSOAuthenticationMethods[] CLAVEVALIDS_CLIENT_NAME = "Cl@ve"; private static final String CERT_CLIENT_NAME = "Cert"; private static final String FIRST_NAME_KEY = "FirstName"; private static final String FAMILY_NAME_KEY = "FamilyName"; /* * (non-Javadoc) * * @see es.um.atica.util.FundeWebManager#getLog() */ @Override protected Log getLog() { return LOG; } @Override public boolean authenticate()SSO_AUTHENTICATION_METHODS = { LOG.info("Autenticando a: #0", getCredentials().getUsername()); getStatusMessages().clearGlobalMessages(); try { if ( isDni( getCredentials().getUsername() ) ) { // Autenticacion Clave - DNI loadPersonaByDniCAS( getCredentials().getUsername() ); } else { // Autenticacion Correo UMU - Ticarum loadUser( getCredentials().getUsername() ); } return true; } catch (ServiceNotFoundException snfe) { LOG.error("Error al buscar el servicio de Gente", snfe); } catch (PersonaException pe) { LOG.error("Error: al obtener los datos del Usuario en GENTE.", pe); processErrorMessage(); } catch (PersonaNotFoundException pnfe) { LOG.error("Error: el usuario no se encuentra en GENTE.", pnfe); processErrorMessage(); } catch ( Throwable t ) { LOG.error("Error inesperado.", t); } return false; } private void loadPersonaByDniCAS( String username ) throws PersonaException, PersonaNotFoundException, ServiceNotFoundException { try { loadPersonaByIdentificador( username ); } catch ( PersonaNotFoundException pnfe ) { LOG.warn( "El usuario no se encuentra en GENTE, completamos con datos del CAS.", pnfe ); loadPersonaByDniClave( username ); } } private void loadPersonaByDniClave( String username ) { Map<String, Object> atributos = CasClient3Util.getPrincipalAttributes( CasClient3Util.getCasClient3Principal() ); LOG.info( "Atributos: #0", atributos ); String client = (String) atributos.get( CLIENT_NAME_KEY ); if ( !Strings.isEmpty( client ) && ( CLAVE_CLIENT_NAME.equals( client ) || CERT_CLIENT_NAME.equals( client ) ) ) { String nombre = new String( ( ( String ) atributos.get( FIRST_NAME_KEY ) ).getBytes(), StandardCharsets.UTF_8 ); String apellidos = new String( ( ( String ) atributos.get( FAMILY_NAME_KEY ) ).getBytes(), StandardCharsets.UTF_8 ); Persona persona = new Persona( username, nombre, apellidos, null ); getUmuIdentity().setPersona( persona ); } } @Override public void preAuthenticate() { Map<String, Object> atributos = CasClient3Util.getPrincipalAttributes( CasClient3Util.getCasClient3Principal() ); String client = ( String ) atributos.get( CLIENT_NAME_KEY ); if ( !Strings.isEmpty( client ) && CLAVE_CLIENT_NAME.equals( client ) ) { throw new AuthenticationMethodNotSupportedException( CLAVE_CLIENT_NAME ); } } } |
AuthenticationFactorySSO.java
Añadir esta clase asegurándonos que en el return del método createAuthenticationMethod() ponemos el nombre completo de la clase anterior (línea 11):
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
package es.um.atica.apium.security.authentication;
import es.um.atica.seam.security.authentication.credentials.CredentialsDefaultUmu;
import es.um.atica.seam.security.authentication.credentials.CredentialsUmu;
import es.um.atica.seam.security.authentication.factories.AuthenticationFactory;
import es.um.atica.seam.security.authentication.method.AuthenticationMethod;
public class AuthenticationFactorySSO implements AuthenticationFactory {
public AuthenticationMethod createAuthenticationMethod() {
return new es.um.atica.apium.security.authentication.AuthenticationMethodSSO();
}
public CredentialsUmu createCredentials() {
return new CredentialsDefaultUmu();
}
} |
AuthenticationManagerBean.java
SSOAuthenticationMethods.SSO_CORREO
};
...
// Forma de hacer accesible los metodos de autenticacion validos por SSO
@Factory( Constants.SSO_AUTHENTICATION_METHODS_COMPONENT_NAME )
public SSOAuthenticationMethods[] getValidsSSOAuthenticationMethods() {
return VALIDS_SSO_AUTHENTICATION_METHODS;
}
} |
Los métodos de autenticación posibles son:
| Bloque de código |
|---|
SOAuthenticationMethods[]: SSO_CORREO, SSO_MFA_CORREO_OTP, SSO_CLAVE, SSO_CLAVE_CERT, SSO_CLAVE_EIDAS, SSO_CLAVE_SEGSOC, SSO_CLAVE_PIN24H, SSO_CLAVE_PIN24H_MOVIL, SSO_CMN |
Añadir SSO_CORREO implica, que se aceptan SSO_CORREO y SSO_MFA_CORREO_OTP.
Añadir SSO_CLAVE implica, que se aceptan todos los métodos de Cl@ve: SSO_CLAVE_CERT, SSO_CLAVE_EIDAS, SSO_CLAVE_SEGSOC, SSO_CLAVE_PIN24H y SSO_CLAVE_PIN24H_MOVIL.
Sino se usa la anotación @Factory, podemos declarar la factoría en el fichero components.xml:
| Bloque de código |
|---|
<factory name="es.um.atica.security.authentication.ssoAuthenticationMethods" value="#{authenticationManagerBean.validsSSOAuthenticationMethods}"/> |
o
| Bloque de código |
|---|
<factory name="es.um.atica.security.authentication.ssoAuthenticationMethods" method="#{authenticationManagerBean.getValidsSSOAuthenticationMethods}"/> |
AuthenticatorAction.java
- Asegurar que AuthenticatorAction extiende la clase AbstractAuthenticatorAction.
La clase normalmente, suele parecerse al siguiente ejemploModificar esta clase haciendo que extienda a AbstractAuthenticationManagerBean en lugar de a FundeWebManagerBean (línea 37) y asegurándonos que el return del método getFactoria() en el caso SSO ponemos el nombre completo de la clase anterior (línea 119):
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
package es.um.atica.apiumXXXX.security.authentication; import static org.jboss.seam.ScopeType.SESSION; import static org.jboss.seam.annotations.Install.FRAMEWORK; import java.io.Serializable; import java.util.MissingResourceException; import java.util.ResourceBundle; import javax.faces.model.SelectItem; import org.jboss.seam.Component; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.Startup; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.core.SeamResourceBundle; import org.jboss.seam.log.Log; import org.jboss.seam.log.Logging; import es.um.atica.apium.security.authentication.ws.AuthenticationFactoryCorreo; import es.um.atica.seam.security.CredentialsAdapter; import es.um.atica.seam.security.UmuIdentity; import es.um.atica.seam.security.authentication.AbstractAuthenticationManagerBean; import es.um.atica.seam.security.authentication.credentials.CredentialsUmu; import es.um.atica.seam.security.authentication.factories.AuthenticationFactory; import es.um.atica.seam.security.authentication.method.AuthenticationMethodAbstractAuthenticatorAction; @Name( "authenticationManagerBeanauthenticator" ) @Scope( SESSION ) @Install( precedence = FRAMEWORK ) @BypassInterceptors @Startup public class AuthenticationManagerBeanAuthenticatorAction extends AbstractAuthenticationManagerBeanAbstractAuthenticatorAction implements Serializable { /** * serialVersionUID generado automaticamente */ private static final long serialVersionUID = -6064182119922723132L; /** Logger de la clase */ private static final Log LOG = Logging.getLog( AuthenticationManagerBeanAuthenticatorAction.class ); protected SelectItem[] selectItemsAutentication; /** Credencial actual */@Override protected CredentialsAdapter credentialsAdapter; protected AuthenticationType authenticationType; public enum AuthenticationType { CORREO, SSO } private static final String ERROR_FIRMA = "0"; public AuthenticationManagerBeanAbstractAuthenticationManagerBean getAuthenticationManagerBean() { // Por defecto CORREO this.credentialsAdapter = ( CredentialsAdapter ) this.getCredentials(); this.authenticationType = AuthenticationType.CORREO; this.activateCredentialsUmu(); int idx = 0; selectItemsAutentication = new SelectItem[AuthenticationType.values().length]; for ( AuthenticationType type : AuthenticationType.values() ) { selectItemsAutentication[idx++] = new SelectItem( type.name(), getAuthenticationTypeLabel( type ) ); } } public void activateCredentialsUmu() { LOG.info( "Entrar en activateCredentialsUmu: #0", this.authenticationType.name() ); this.credentialsAdapter.setCredentialsUmu( getFactoria( this.authenticationType ).createCredentials() )return AuthenticationManagerBean.instance(); } /** * Metodo para activar una credencial.<br />(non-Javadoc) * Si la que se desea activar, es la que est� actualmente, no se hace nada y se devuelve false. En otro caso se * devolver� true. * * @param credencial * Clase de Credencial a activar. * @return Si => se creo una nueva credencial. No => ya estaba esa misma credencial activa.@see es.um.atica.util.FundeWebManagerBean#getLog() */ public boolean activateCredentialsUmu( AuthenticationType authenticationType ) { LOG.info( "Entrar en activateCredentialsUmu: #0", ( authenticationType != null ? authenticationType.name() : "" ) ); if ( ( this.getCredentialsUmu() != null ) && ( this.authenticationType == authenticationType ) ) { if ( LOG.isDebugEnabled() ) { LOG.debug( "La credencial actual y la pedida son iguales, luego no se crear� una nueva: #0.", this.authenticationType ); } return false; } if ( authenticationType != null ) { this.setAuthenticationType( authenticationType ); } else { // Por defecto CORREO this.setAuthenticationType( AuthenticationType.CORREO ); } this.credentialsAdapter.setCredentialsUmu( getFactoria( this.authenticationType ).createCredentials() ); return true; } public AuthenticationMethod getAuthenticationMethod() { return this.getFactoria( this.authenticationType ).createAuthenticationMethod(); } /** * @param authenticationType * - parametro de Seam por defecto * @return */ protected AuthenticationFactory getFactoria( AuthenticationType authenticationType ) { if ( this.authenticationType == null ) { activateCredentialsUmu( AuthenticationType.CORREO ); } switch ( this.authenticationType ) { case SSO: // case SSO return new es.um.atica.apium.security.authentication.AuthenticationFactorySSO(); case CORREO: // case CORREO return new AuthenticationFactoryCorreo(); default: return new es.um.atica.apium.security.authentication.AuthenticationFactoryRadius(); } } protected String getAuthenticationTypeLabel( AuthenticationType authenticationType ) { ResourceBundle srb = SeamResourceBundle.getBundle(); try { switch ( authenticationType ) { case CORREO: return srb.getString( "label.tipo_acceso_correo" ); case SSO: return srb.getString( "label.tipo_acceso_sso" ); default: return srb.getString( "tipo no identificado" ); } } catch ( MissingResourceException mre ) { LOG.error( "Error al obtener las etiquetas para los tipos de autenticacion.", mre ); } return ""; } /** * Obtiene la credencial actual. */ public CredentialsUmu getCredentialsUmu() { return this.credentialsAdapter.getCredentialsUmu(); } public AuthenticationType getAuthenticationType() { return authenticationType; } public void setAuthenticationType( AuthenticationType authenticationType ) { LOG.debug( "Entra en setAuthenticationType: #0 - #1", authenticationType.hashCode(), authenticationType.name() ); this.authenticationType = authenticationType; } public boolean isCorreoAuthentication() { return this.authenticationType == AuthenticationType.CORREO; } public boolean isSsoAuthentication() { return this.authenticationType == AuthenticationType.SSO; } public SelectItem[] getSelectItemsAutentication() { return selectItemsAutentication; } @Observer( UmuIdentity.EVENT_AUTHENTICATING_BY_CAS ) public void activarAuthenticacionSSO() { LOG.debug( "Entra en activarAuthenticacionSSO" ); this.authenticationType = AuthenticationType.SSO; this.activateCredentialsUmu(); } /* * (non-Javadoc) * @see es.um.atica.util.FundeWebManager#getLog() */ @Override protected Log getLog() { return LOG; } public static AuthenticationManagerBean instance() { if ( !Contexts.isSessionContextActive() ) { throw new IllegalStateException( "no session context active" ); } return ( AuthenticationManagerBean ) Component.getInstance( AuthenticationManagerBean.class ); } public static String getErrorfirma() { return ERROR_FIRMA; } } |
AuthenticatorAction.java
Sustituir la clase completa:
@Override
protected Log getLog() {
return LOG;
}
} |
pages.xml
Añadir la siguiente regla de navegación al final de la declaración de <page view-id="*">:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
<navigation from-action="#{identity.relogByCAS}">
<redirect url="https://${cas.server.url}.um.es/cas/logout">
<param name="service" value="https://${cas.application.url}#{request.contextPath}"/>
</redirect>
</navigation> |
Añadir la siguiente excepción:
| Bloque de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
<exception class=" | ||||||
| Bloque de código | ||||||
| ||||||
package es.um.atica.apium.security.authentication; import static org.jboss.seam.annotations.Install.FRAMEWORK; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.log.Log; import org.jboss.seam.log.Logging; import es.um.atica.seam.security.authentication.AbstractAuthenticationManagerBean; import es.um.atica.seam.security.authentication.AbstractAuthenticatorAction; @Name( "authenticator" ) @Install( precedence = FRAMEWORK ) @BypassInterceptors public class AuthenticatorAction extends AbstractAuthenticatorAction { private static final Log LOG = Logging.getLog( AuthenticatorAction.class ); @Override protected AbstractAuthenticationManagerBean getAuthenticationManagerBean() { return AuthenticationManagerBean.instance(); } /* * (non-Javadoc) * @see es.um.atica.util.FundeWebManagerBean#getLog() */ @Override protected Log getLog() { return LOG; } } |
messages_en.properties
Añadir al final del fichero las siguientes variables.
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
es.um.atica.security.authentication.AuthenticationMethodNotSupportedException=Authentication method not supported |
messages_es.properties
Añadir al final del fichero las siguientes variables.
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
es.um.atica.security.authentication.AuthenticationMethodNotSupportedException=M\u00E9todo de autenticaci\u00F3n no soportado |
pages.xml
exceptions.AuthenticationMethodNotSupportedException">
<redirect view-id="/fundeweb/error_auth_method.xhtml">
<message severity="error">#{messages['es.um.atica.security.authentication.AuthenticationMethodNotSupportedException']}</message>
</redirect>
</exception> |
Modificar la excepción para la clase org.jboss.seam.security.AuthorizationExceptionAñadir la siguiente excepción:
| Bloque de código | ||||
|---|---|---|---|---|
| ||||
<exception class="esorg.umjboss.aticaseam.apium.security.authentication.AuthenticationMethodNotSupportedExceptionAuthorizationException"> <redirect view-id="/fundeweb/error_no_auth.xhtml"> <message severity="error">#{messages['esorg.umjboss.aticaseam.security.authentication.AuthenticationMethodNotSupportedExceptionAuthorizationException']}</message> </redirect> </exception> |
Comprobación
Es necesaria realizar la comprobación de la existencia del fichero recomendaciones.xhtml que se tiene que encontrar en la carpeta src/main/webapp/layout del módulo WEB. Sino existe la podéis descargar de recomendaciones.xhtml