package fr.openwide.core.jpa.security.config.spring; import static fr.openwide.core.spring.property.SpringSecurityPropertyIds.PASSWORD_SALT; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.access.intercept.RunAsImplAuthenticationProvider; import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.acls.domain.PermissionFactory; import org.springframework.security.acls.model.Permission; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import fr.openwide.core.jpa.security.access.expression.method.CoreMethodSecurityExpressionHandler; import fr.openwide.core.jpa.security.business.authority.util.CoreAuthorityConstants; import fr.openwide.core.jpa.security.crypto.password.CoreShaPasswordEncoder; import fr.openwide.core.jpa.security.hierarchy.IPermissionHierarchy; import fr.openwide.core.jpa.security.hierarchy.PermissionHierarchyImpl; import fr.openwide.core.jpa.security.model.CorePermissionConstants; import fr.openwide.core.jpa.security.model.NamedPermission; import fr.openwide.core.jpa.security.runas.CoreRunAsManagerImpl; import fr.openwide.core.jpa.security.service.AuthenticationUserNameComparison; import fr.openwide.core.jpa.security.service.CoreAuthenticationServiceImpl; import fr.openwide.core.jpa.security.service.CoreJpaUserDetailsServiceImpl; import fr.openwide.core.jpa.security.service.CoreSecurityServiceImpl; import fr.openwide.core.jpa.security.service.IAuthenticationService; import fr.openwide.core.jpa.security.service.ICorePermissionEvaluator; import fr.openwide.core.jpa.security.service.ISecurityService; import fr.openwide.core.jpa.security.service.NamedPermissionFactory; import fr.openwide.core.spring.property.service.IPropertyService; @Configuration @Import(DefaultJpaSecurityConfig.class) public abstract class AbstractJpaSecurityConfig { private static final Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}"); @Autowired private DefaultJpaSecurityConfig defaultJpaSecurityConfig; @Autowired protected IPropertyService propertyService; /** * N'est pas basculé en configuration car on n'est pas censé basculer d'un * mode à un autre au cours de la vie de l'application. Doit être décidé au * début, avec mise en place des contraintes nécessaires à la création * d'utilisateur si on choisit le mode CASE INSENSITIVE. Cette méthode n'a * pas besoin d'être annotée {@link Bean} * * @see AuthenticationUserNameComparison */ public AuthenticationUserNameComparison authenticationUserNameComparison() { return AuthenticationUserNameComparison.CASE_SENSITIVE; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder() { @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { if (BCRYPT_PATTERN.matcher(encodedPassword).matches()) { return super.matches(rawPassword, encodedPassword); } else { CoreShaPasswordEncoder passwordEncoder = new CoreShaPasswordEncoder(256); passwordEncoder.setSalt(propertyService.get(PASSWORD_SALT)); return passwordEncoder.matches(rawPassword, encodedPassword); } } }; } @Bean public ISecurityService securityService() { return new CoreSecurityServiceImpl(); } @Bean(name = "authenticationService") public IAuthenticationService authenticationService() { return new CoreAuthenticationServiceImpl(); } @Bean public UserDetailsService userDetailsService() { CoreJpaUserDetailsServiceImpl detailsService = new CoreJpaUserDetailsServiceImpl(); detailsService.setAuthenticationUserNameComparison(authenticationUserNameComparison()); return detailsService; } @Bean public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, RunAsImplAuthenticationProvider runAsProvider, PasswordEncoder passwordEncoder) { List<AuthenticationProvider> providers = Lists.newArrayList(); providers.add(runAsProvider); DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService); authenticationProvider.setPasswordEncoder(passwordEncoder); providers.add(authenticationProvider); return new ProviderManager(providers); } public Class<? extends Permission> permissionClass() { return NamedPermission.class; } @Bean public PermissionFactory permissionFactory() { return new NamedPermissionFactory(permissionClass()); } /** * Le {@link ScopedProxyMode} est nécessaire si on désire utiliser les * annotations de sécurité. En effet, l'activation des annotations de * sécurité nécessite la construction du sous-système de sécurité dès le * début de l'instantiation des beans (de manière à pouvoir mettre en place * les intercepteurs de sécurité). Or le système de sécurité provoque le * chargement du entitymanager et d'autres beans alors que leur dépendances * ne sont pas prêtes. La mise en place d'un proxy permet de reporter à plus * tard l'instanciation du système de sécurité. */ @Bean @Scope(proxyMode = ScopedProxyMode.INTERFACES) public abstract ICorePermissionEvaluator permissionEvaluator(); @Bean public MethodSecurityExpressionHandler expressionHandler(ICorePermissionEvaluator corePermissionEvaluator) { CoreMethodSecurityExpressionHandler methodSecurityExpressionHandler = new CoreMethodSecurityExpressionHandler(); methodSecurityExpressionHandler.setCorePermissionEvaluator(corePermissionEvaluator); return methodSecurityExpressionHandler; } protected String roleHierarchyAsString() { return defaultRoleHierarchyAsString(); } @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); roleHierarchy.setHierarchy(roleHierarchyAsString()); return roleHierarchy; } protected String permissionHierarchyAsString() { return defaultPermissionHierarchyAsString(); } @Bean @Autowired public IPermissionHierarchy permissionHierarchy(PermissionFactory permissionFactory) { PermissionHierarchyImpl hierarchy = new PermissionHierarchyImpl(permissionFactory); hierarchy.setHierarchy(permissionHierarchyAsString()); return hierarchy; } @Bean public RunAsManager runAsManager() { CoreRunAsManagerImpl runAsManager = new CoreRunAsManagerImpl(); runAsManager.setKey(defaultJpaSecurityConfig.getRunAsKey()); return runAsManager; } @Bean public RunAsImplAuthenticationProvider runAsAuthenticationProvider() { RunAsImplAuthenticationProvider runAsAuthenticationProvider = new RunAsImplAuthenticationProvider(); runAsAuthenticationProvider.setKey(defaultJpaSecurityConfig.getRunAsKey()); return runAsAuthenticationProvider; } protected static String defaultPermissionHierarchyAsString() { return hierarchyAsStringFromMap(ImmutableMultimap.<String, String>builder() .put(CorePermissionConstants.ADMINISTRATION, CorePermissionConstants.WRITE) .put(CorePermissionConstants.WRITE, CorePermissionConstants.READ) .build() ); } protected static String defaultRoleHierarchyAsString() { return hierarchyAsStringFromMap(ImmutableMultimap.<String, String>builder() .put(CoreAuthorityConstants.ROLE_SYSTEM, CoreAuthorityConstants.ROLE_ADMIN) .put(CoreAuthorityConstants.ROLE_ADMIN, CoreAuthorityConstants.ROLE_AUTHENTICATED) .put(CoreAuthorityConstants.ROLE_AUTHENTICATED, CoreAuthorityConstants.ROLE_ANONYMOUS) .build() ); } protected static String hierarchyAsStringFromMap(Multimap<String, String> multimap) { return hierarchyAsStringFromMap(multimap.asMap()); } protected static String hierarchyAsStringFromMap(Map<String, ? extends Collection<String>> map) { StringBuilder builder = new StringBuilder(); for (Map.Entry<String, ? extends Collection<String>> entry : map.entrySet()) { String parent = entry.getKey(); for (String child : entry.getValue()) { builder.append(parent).append(" > ").append(child).append("\n"); } } return builder.toString(); } }