Configuration du Framework Acegi dans application Web JAVA J2EE
- 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:
- Etape 3: Configuration des filtres et des listeners dans le fichier web.xml:
- Etape 4: Configuration des filtres au niveau du fichier de configuration de Spring:
- Etape 5: Création de l’interface UserManager, de son implémentation, de userDao et de la classe POJO User:
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.
Ce document intitulé « Configuration du Framework Acegi dans application Web JAVA J2EE » issu de Comment Ça Marche (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.