Configuration du Framework Acegi dans application Web JAVA J2EE

Décembre 2016



Etapes de configuration du Framework Acegi dans une application Web JAVA J2EE


La configuration passe par cinq étapes :

Etape 1: Mettre le acegi-security-1.0.0-RC2.jar sous le répertoire lib de votre application:



Etape 2: Configuration des listeners et des filtres:

  • Pour les filtres, il en existe trois à intégrer :
    • LocaleFilter : ce filtre étend le filtre OncePerRequestFilter qui est un filtre de base. Il garantit son exécution seulement une et une seule fois par requête, sur n'importe quel conteneur de servlet. Il fournit une méthode doFilterInternal avec comme arguments HttpServletRequest et HttpServletResponse.
    • LocaleRequestWrapper : ce filtre étend HttpServletRequestWrapper qui permet de configurer le Locale de l’utilisateur.
    • UserSecurityAdvice : ce filtre est un advice utilisé par Acegi. Il permet de renforcer la sécurité : il donne la main seulement à l’administrateur pour qu’il modifie les utilisateurs ou bien il donne la main à l’utilisateur pour qu’il modifie ses informations.
  • Concernant les listeners, nous trouvons :
    • StartupListener : cet écouteur permet d'initialiser les paramètres de sécurité (comme le cryptage de mot de passe) et de charger la liste des rôles possible configurés au niveau de la base de donnée pour accéder à l'application.
    • UserCounterListener : Cet écouteur permet de contrôler le nombre des connexions actives pour chaque utilisateur en se référant au paramètre existant dans le fichier applicationContext-security.xml au niveau de bean déclaré concurrentSessionController.

Etape 3: Configuration des filtres et des listeners dans le fichier web.xml:


Tout d'abord, nous devons mettre en place la logique de filtres d'ACEGI. Pour cela, il faut mettre à jour le fichier web.xml de notre application Web en y définissant un filtre au sens servlet. Ce filtre va charger sa propre configuration depuis les fichiers de configuration Spring.</code>
<filter>
		<filter-name>localeFilter</filter-name>
		<filter-class>com.cleyris.ebee.filter.LocaleFilter</filter-class>
	</filter>
…..
  <listener>
        <listener-class>com.cleyris.ebee.listener.StartupListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.cleyris.ebee.listener.UserCounterListener</listener-class>
    </listener>

Etape 4: Configuration des filtres au niveau du fichier de configuration de Spring:


Ce fichier doit de trouver sous /WEB-INF/
Dans notre application, ce fichier est nommé applicationContext-security.xml
Dans notre fichier de configuration Spring, nous allons maintenant déclarer les filtres ACEGI chargés de mettre en oeuvre notre stratégie de sécurisation. Pour faire propre, il faudrait créer plusieurs fichiers de configuration.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
       default-lazy-init="true">

    <!-- ======================== FILTER CHAIN ======================= -->
    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
            </value>
            <!-- Put channelProcessingFilter before securityContextHolderAwareRequestFilter to turn on SSL switching -->
            <!-- It's off by default b/c Canoo WebTest doesn't support SSL out-of-the-box -->
        </property>
    </bean>
    <!-- Pour limiter le nombre des connecteurs à la session en même temps -->
	<bean id="concurrentSessionController"
		class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">
		<property name="maximumSessions">
			<value>1</value>
		</property>
		<property name="sessionRegistry">
			<ref local="sessionRegistry" />
		</property>
	</bean>
	<bean id="sessionRegistry"
		class="org.acegisecurity.concurrent.SessionRegistryImpl" />
    <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
	<!-- Fin -->
    <!-- Changed to use logout.jsp since causes 404 on WebSphere: http://issues.appfuse.org/browse/APF-566 -->
    <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
        <constructor-arg value="/index.jsp"/>
        <constructor-arg>
            <list>
                <ref bean="rememberMeServices"/>
                <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
            </list>
        </constructor-arg>
        <property name="filterProcessesUrl" value="/logout.jsp"/>
    </bean>

    <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationFailureUrl" value="/login.jsp?error=true"/>
        <property name="defaultTargetUrl" value="/"/>
        <property name="filterProcessesUrl" value="/j_security_check"/>
        <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

    <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>

    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

    <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
        <property name="key" value="anonymous"/>
        <property name="userAttribute" value="anonymous,ROLE_ANONYMOUS"/>
    </bean>

    <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
        <property name="authenticationEntryPoint">
            <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                <property name="loginFormUrl" value="/login.jsp"/>
                <property name="forceHttps" value="false"/>
            </bean>
        </property>
    </bean>

    <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
        <property name="objectDefinitionSource">
            <value>
                PATTERN_TYPE_APACHE_ANT
                /passwordHint.html*=ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER
                /signup.html*=ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER
                /a4j.res/*.html*=ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER <!-- APF-737, OK to remove if not using JSF -->
                /**/*.html*=ROLE_ADMIN,ROLE_USER
            </value>
        </property>
    </bean>

    <bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
        <property name="allowIfAllAbstainDecisions" value="false"/>
        <property name="decisionVoters">
            <list>
                <bean class="org.acegisecurity.vote.RoleVoter"/>
            </list>
        </property>
    </bean>

    <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
        <property name="userDetailsService" ref="UserDao"/>
        <property name="key" value="23_*!cdU='612./e;NrI"/>
        <property name="parameter" value="rememberMe"/>
    </bean>

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref local="daoAuthenticationProvider"/>
                <ref local="anonymousAuthenticationProvider"/>
                <ref local="rememberMeAuthenticationProvider"/>
            </list>
        </property>
    </bean>

    <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
         <property name="userDetailsService" ref="UserDao"/>
         <property name="passwordEncoder" ref="passwordEncoder"/>
    </bean>

    <bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
        <property name="key" value="anonymous"/>
    </bean>

    <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
        <property name="key" value="23_*!cdU='612./e;NrI"/>
    </bean>

    <!-- This bean definition must be available to ApplicationContext.getBean() so StartupListener
         can look for it and detect if password encryption is turned on or not -->
    <bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.ShaPasswordEncoder"/>

    <!-- This bean is optional; it isn't used by any other bean as it only listens and logs -->
    <bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>

    <!-- Apply method-level interceptor to userManager bean -->
    <aop:config>
        <aop:advisor id="managerSecurity" advice-ref="methodSecurityInterceptor" pointcut="execution(* com.cleyris.ebee.core.service.UserManager.*(..))"/>
    </aop:config>

    <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
        <property name="objectDefinitionSource">
             <value>
                 com.cleyris.ebee.core.service.UserManager.getUsers=ROLE_ADMIN
                 com.cleyris.ebee.core.service.UserManager.removeUser=ROLE_ADMIN
             </value>
        </property>
    </bean>

    <!-- SSL Switching: to use this, configure it in the filterChainProxy bean -->
    <bean id="channelProcessingFilter" class="org.acegisecurity.securechannel.ChannelProcessingFilter">
        <property name="channelDecisionManager" ref="channelDecisionManager"/>
        <property name="filterInvocationDefinitionSource">
            <value>
                PATTERN_TYPE_APACHE_ANT
                /admin/**=REQUIRES_SECURE_CHANNEL
                /login*=REQUIRES_SECURE_CHANNEL
                /j_security_check*=REQUIRES_SECURE_CHANNEL
                /signup.html*=REQUIRES_SECURE_CHANNEL
                /saveUser.html*=REQUIRES_SECURE_CHANNEL
                /**=REQUIRES_INSECURE_CHANNEL
            </value>
        </property>
    </bean>

    <bean id="channelDecisionManager" class="org.acegisecurity.securechannel.ChannelDecisionManagerImpl">
        <property name="channelProcessors">
            <list>
                <bean class="org.acegisecurity.securechannel.SecureChannelProcessor"/>
                <bean class="org.acegisecurity.securechannel.InsecureChannelProcessor"/>
            </list>
        </property>
    </bean>
</beans>

Au niveau de ce fichier, nous avons défini les pages d’index, de login et de logout, le nombre maximal d’ouverture de session, les différents rôles…

Etape 5: Création de l’interface UserManager, de son implémentation, de userDao et de la classe POJO User:

  • UserManager.java

public interface UserManager extends Manager {
	……
	/**

	 * 
	 * @param user cette methode est utilis�e par acegi pour ajouter un autre utilisateur 
	 * @return
	 * @throws UserExistsException
	 */
	public User saveUser(User user) throws UserExistsException;
	
	/**

	 * cette methode est utilis�e par acegi pour intercepter un utilisateur
	 * @param userId
	 * @return
	 */
	public User getUser(String userId);
	
	/**

	 * cette methode est utilis�e par acegi pour supprimer un utilisateur
	 * @param userId
	 */
	public void removeUser(String userId);
	
	/**

	 * 
	 * @param username cette methode est utilis�e par acegi pour afficher l'utilisateur � travers son login
	 * @return
	 * @throws UsernameNotFoundException
	 */
	public User getUserByUsername(String username) throws UsernameNotFoundException, CleyrisException ;
……………
}
  • UserManagerImpl.java

public class UserManagerImpl extends BaseManager implements UserManager{

	//c'est la partie Acegi
	
    @SuppressWarnings("unchecked")
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<UserDetails> users = userDAO.setHqlQuery("from User where username=:username").setParameter("username",username).list();
        if (users == null || users.isEmpty()) {
            throw new UsernameNotFoundException("user '" + username + "' not found...");
        } else {
            return (UserDetails) users.get(0);
        }
    }
    
    public User getUser(String userId) {
        return userDao.get(new Long(userId));
    }
    
    public User saveUser(User user) throws UserExistsException {
        // if new user, lowercase userId
        if (user.getUserId()== 0) {
            user.setUsername(user.getUsername().toLowerCase());
        }

        try {
            return userDao.saveUser(user);
        } catch (DataIntegrityViolationException e) {
           
            if (log.isErrorEnabled())
				log
						.error("Exception occurs class:com.cleyris.ebee.core.service.UserManagerImpl.java  method:saveUser: "
								+ e.getMessage() + ".");
            throw new UserExistsException(Constants.UserExists);
        } catch (EntityExistsException e) { // needed for JPA
           
            if (log.isErrorEnabled())
				log
						.error("Exception occurs class:com.cleyris.ebee.core.service.UserManagerImpl.java  method:saveUser: "
								+ e.getMessage() + ".");
            throw new UserExistsException(Constants.UserExists);
        }
    }

    public void removeUser(String userId) {
        log.debug("removing user: " + userId);
        userDao.remove(new Long(userId));
    }
    
      public User getUserByUsername(String username) throws UsernameNotFoundException, CleyrisException {
    	User user= (User) userDao.loadUserByUsername(username);
        return user;
    }
    public List<User> getUsers(User user) {
        return userDao.getUsers();
    }
}
  • UserDao.java

package com.cleyris.ebee.core.dao;

import java.util.List;

import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;

import com.cleyris.ebee.core.modele.User;

/**

 * @author mlaouini
 */
public interface UserDao extends BaseDao<User, Long> {

    /**

     * charger les informations d'utilisateurs basé sur login .
     * @param username the user's username
     * @return userDetails populated userDetails object
     * @throws org.acegisecurity.userdetails.UsernameNotFoundException thrown when user not found in database
     */
    @Transactional
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

    /**

     * charger une liste d'utilisateurs trier par le login en appliquant le majuscule sur le login ==>username.
     *

     * @return List populated list of users
     */
    List<User> getUsers();

    /**

     * sauvegarder un utilisateur.
     * @param user the object to be saved
     * @return the persisted User object
     */
    User saveUser(User user);
}
  • UserDaoImpl.java

public class UserDaoImpl extends BaseDaoImpl<User, Long> implements UserDao, UserDetailsService {

    public UserDaoImpl() {
        super(User.class);
    }

    /**

     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public List<User> getUsers() {
        return getHibernateTemplate().find("from User u order by upper(u.username)");
    }

    /**

     * {@inheritDoc}
     */
    public User saveUser(User user) {
        log.debug("user's id: " + user.getUserId());
        getHibernateTemplate().saveOrUpdate(user);
        // necessary to throw a DataIntegrityViolation and catch it in UserManager
        getHibernateTemplate().flush();
        return user;
    }

    public User save(User user) {
        return this.saveUser(user);
    }

    /** 

     * {@inheritDoc}
    */
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List users = getHibernateTemplate().find("from User where username=?", username);
        if (users == null || users.isEmpty()) {
            throw new UsernameNotFoundException("user '" + username + "' not found...");
        } else {
            return (UserDetails) users.get(0);
        }
    }
}
  • User.java

/**

 * @author mlaouini
 */
@Entity
@javax.persistence.SequenceGenerator(name="SEQ_STORE",sequenceName="sequence_users")
@Table(name = "users", schema = "ebee")
public class User extends BaseObject implements UserDetails {

	…………
	private String username; // required
	private String password; // required
	private String confirmPassword;
	private String passwordHint;
	private Set<Role> roles = new HashSet<Role>();
	private boolean enabled;
	private boolean accountExpired;
	private boolean accountLocked;
	private boolean credentialsExpired;

…………………
	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "user_role", schema = "ebee", joinColumns = { @JoinColumn(name = "userid", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "roleid", nullable = false, updatable = false) })
	@NotNull
	public Set<Role> getRoles() {
		return this.roles;
	}

	public void setRoles(Set<Role> roles) {
		this.roles = roles;
	}

	
	/**

	 * @see org.acegisecurity.userdetails.UserDetails#getAuthorities()
	 * @return GrantedAuthority[] an array of roles.
	 */
	@Transient
	public GrantedAuthority[] getAuthorities() {
		return roles.toArray(new GrantedAuthority[0]);
	}

	@Column(name = "account_enabled")
	public boolean isEnabled() {
		return enabled;
	}

	@Column(name = "account_expired", nullable = false)
	public boolean isAccountExpired() {
		return accountExpired;
	}

	/**

	 * @see org.acegisecurity.userdetails.UserDetails#isAccountNonExpired()
	 */
	@Transient
	public boolean isAccountNonExpired() {
		return !isAccountExpired();
	}

	@Column(name = "account_locked", nullable = false)
	public boolean isAccountLocked() {
		return accountLocked;
	}

	/**

	 * @see org.acegisecurity.userdetails.UserDetails#isAccountNonLocked()
	 */
	@Transient
	public boolean isAccountNonLocked() {
		return !isAccountLocked();
	}

	@Column(name = "credentials_expired", nullable = false)
	public boolean isCredentialsExpired() {
		return credentialsExpired;
	}

	/**

	 * @see org.acegisecurity.userdetails.UserDetails#isCredentialsNonExpired()
	 */
	@Transient
	public boolean isCredentialsNonExpired() {
		return !credentialsExpired;
	}

	@Column(nullable = false, length = 50, unique = true)
	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Column(nullable = false)
	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Transient
	public String getConfirmPassword() {
		return confirmPassword;
	}

	public void setConfirmPassword(String confirmPassword) {
		this.confirmPassword = confirmPassword;
	}

	@Column(name = "password_hint")
	public String getPasswordHint() {
		return passwordHint;
	}

	public void setPasswordHint(String passwordHint) {
		this.passwordHint = passwordHint;
	}

	
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public void setAccountExpired(boolean accountExpired) {
		this.accountExpired = accountExpired;
	}

	public void setAccountLocked(boolean accountLocked) {
		this.accountLocked = accountLocked;
	}

	public void setCredentialsExpired(boolean credentialsExpired) {
		this.credentialsExpired = credentialsExpired;
	}
	
}

La classe User.java implémente UserDetails. Cette interface fournit des informations de base sur l'utilisateur. Les implémentations ne sont pas directement utilisées par Acegi pour des raisons de sécurité. Elles stockent tout simplement des informations sur l'utilisateur qui sont ensuite encapsulées dans des objets d'authentification.

A voir également :

Ce document intitulé «  Configuration du Framework Acegi dans application Web JAVA J2EE  » issu de CommentCaMarche (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.