with tutorial personalizado page mvc loginprocessingurl example ejemplo custom java spring spring-boot spring-security acl

java - tutorial - spring security login personalizado



El acceso siempre se deniega en Spring Security: DenyAllPermissionEvaluator (3)

He configurado ACL en mi aplicación Spring Boot. La configuración de ACL es la siguiente:

@Configuration @ComponentScan(basePackages = "com.company") @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class ACLConfigration extends GlobalMethodSecurityConfiguration { @Autowired DataSource dataSource; @Bean public EhCacheBasedAclCache aclCache() { return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy()); } @Bean public EhCacheFactoryBean aclEhCacheFactoryBean() { EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean(); ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject()); ehCacheFactoryBean.setCacheName("aclCache"); return ehCacheFactoryBean; } @Bean public EhCacheManagerFactoryBean aclCacheManager() { return new EhCacheManagerFactoryBean(); } @Bean public DefaultPermissionGrantingStrategy permissionGrantingStrategy() { ConsoleAuditLogger consoleAuditLogger = new ConsoleAuditLogger(); return new DefaultPermissionGrantingStrategy(consoleAuditLogger); } @Bean public AclAuthorizationStrategy aclAuthorizationStrategy() { return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ACL_ADMIN")); } @Bean public LookupStrategy lookupStrategy() { return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger()); } @Bean public JdbcMutableAclService aclService() { return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache()); } @Bean public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() { return new DefaultMethodSecurityExpressionHandler(); } @Override public MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler = defaultMethodSecurityExpressionHandler(); expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService())); expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService())); return expressionHandler; } }

Referencias:

y la configuración de seguridad es la siguiente:

@Configuration @EnableWebSecurity public class CustomSecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public AuthenticationEntryPoint entryPoint() { return new LoginUrlAuthenticationEntryPoint("/authenticate"); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .authorizeRequests() .antMatchers("/authenticate/**").permitAll() .anyRequest().fullyAuthenticated() .and().requestCache().requestCache(new NullRequestCache()) .and().addFilterBefore(authenticationFilter(), CustomUsernamePasswordAuthenticationFilter.class); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**"); } @Bean public CustomUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception { CustomUsernamePasswordAuthenticationFilter authenticationFilter = new CustomUsernamePasswordAuthenticationFilter(); authenticationFilter.setUsernameParameter("username"); authenticationFilter.setPasswordParameter("password"); authenticationFilter.setFilterProcessesUrl("/authenticate"); authenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler()); authenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler()); authenticationFilter.setAuthenticationManager(authenticationManagerBean()); return authenticationFilter; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }

Mi clase CustomAuthenticationProvider :

@Component public class CustomAuthenticationProvider implements AuthenticationProvider { @Autowired private UsersService usersService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); User user = usersService.findOne(username); if(user != null && usersService.comparePassword(user, password)){ return new UsernamePasswordAuthenticationToken( user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList( user.getUserRoles().stream().collect(Collectors.joining(",")))); } else { return null; } } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }

Aquí está mi CustomUsernamePasswordAuthenticationToken :

public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if(!request.getMethod().equals("POST")) throw new AuthenticationServiceException(String.format("Authentication method not supported: %s", request.getMethod())); try { CustomUsernamePasswordAuthenticationForm form = new ObjectMapper().readValue(request.getReader(), CustomUsernamePasswordAuthenticationForm.class); String username = form.getUsername(); String password = form.getPassword(); if(username == null) username = ""; if(password == null) password = ""; UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, token); return getAuthenticationManager().authenticate(token); } catch (IOException exception) { throw new CustomAuthenticationException(exception); } } private class CustomAuthenticationException extends RuntimeException { private CustomAuthenticationException(Throwable throwable) { super(throwable); } } }

Aparte de lo anterior, tengo CustomAuthenticationFailureHandler , CustomAuthenticationSuccessHandler , CustomNoRedirectStrategy y CustomUsernamePasswordAuthenticationForm que CustomUsernamePasswordAuthenticationForm por la longitud de esta pregunta.

Y estoy usando el esquema MySQL que se puede encontrar here .

Estoy agregando entradas a mis tablas relacionadas con acl de la siguiente manera:

INSERT INTO acl_class VALUES (1, com.company.project.domain.users.User) INSERT INTO acl_sid VALUES (1, 1, "demo")

(Tengo un usuario con demo usuario)

INSERT INTO acl_object_identity VALUES (1, 1, 1, NULL, 1, 0) INSERT INTO acl_entry VALUES (1, 1, 1, 1, 1, 1, 1, 1)

Pero todo lo que estoy consiguiendo es:

Denying user demo permission ''READ'' on object com.company.project.domain.users.User@4a49e9b4

en mi

@PostFilter("hasPermission(filterObject, ''READ'')")

Estoy sospechando de varios problemas aquí:

  1. La expresión hasPermission : la he sustituido con ''READ'' y ''1'', pero en ningún caso.
  2. Las entradas de mi base de datos no son correctas
  3. No estoy implementando un evaluador de permisos personalizado. ¿Es esto requerido, o es expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService())); ¿suficiente?

Actualizar

Método de muestra donde se usa @PostFilter :

@RequestMapping(method = RequestMethod.GET) @PostFilter("hasPermission(filterObject, ''READ'')") List<User> find(@Min(0) @RequestParam(value = "limit", required = false, defaultValue = "10") Integer limit, @Min(0) @RequestParam(value = "page", required = false, defaultValue = "0") Integer page, @RequestParam(value = "email", required = false) String email, @RequestParam(value = "firstName", required = false) String firstName, @RequestParam(value = "lastName", required = false) String lastName, @RequestParam(value = "userRole", required = false) String userRole) { return usersService.find( limit, page, email, firstName, lastName, userRole); }

Actualización # 2:

La pregunta ahora refleja todo lo configurado en lo que respecta a la autenticación / autorización / ACL.

Actualización # 3:

Ahora estoy muy cerca de resolver el problema, lo único que queda es resolver esto:

https://stackoverflow.com/questions/42996579/custom-permissionevaluator-not-called-although-set-as-permissionevaluator-deny

Si alguien me puede ayudar con esa pregunta, finalmente puedo hacer una reseña de lo que he pasado para resolver esto.


Actualicé mi aplicación para usar Spring Security 4.2.1.RELEASE y luego comencé a experimentar un acceso inesperado denegado en todos los métodos anotados de @PreAuthorize , que funcionaba bien antes de la actualización. Depuré el código de seguridad de Spring y me di cuenta de que el problema era que todas las funciones que se verificarían tenían un prefijo con una cadena predeterminada "ROLE_", independientemente del hecho de que había configurado mi prefijo predeterminado como vacío, como se muestra en el código a continuación.

auth.ldapAuthentication() .groupSearchBase(ldapProperties.getProperty("groupSearchBase")) .groupRoleAttribute(ldapProperties.getProperty("groupRoleAttribute")) .groupSearchFilter(ldapProperties.getProperty("groupSearchFilter")) //this call used to be plenty to override the default prefix .rolePrefix("") .userSearchBase(ldapProperties.getProperty("userSearchBase")) .userSearchFilter(ldapProperties.getProperty("userSearchFilter")) .contextSource(this.ldapContextSource);

Sin embargo, todos los métodos de mi controlador se anotaron con @PreAuthorize("hasRole(''my_ldap_group_name'')") , sin embargo, el marco no tenía en cuenta la configuración del prefijo de mi función vacía y, por lo tanto, estaba usando ROLE_my_ldap_group_name para verificar la función real.

Después de profundizar en el código del marco, me di cuenta de que la clase org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler todavía tenía el prefijo de rol predeterminado establecido en "ROLE_" . Seguí la fuente de su valor y descubrí que primero estaba buscando un bean declarado de la clase org.springframework.security.config.core.GrantedAuthorityDefaults para buscar un prefijo predeterminado durante la primera inicialización del bean org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer embargo, como este bean de inicialización no pudo encontrarlo declarado, terminó usando el prefijo predeterminado mencionado anteriormente .security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.

Creo que este no es un comportamiento esperado: Spring Security debería haber considerado el mismo rolPrefix de ldapAuthentication; sin embargo, para resolver este problema, era necesario agregar el bean org.springframework.security.config.core.GrantedAuthorityDefaults al contexto de mi aplicación ( Estoy usando la configuración basada en anotaciones), como sigue:

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class CesSecurityConfiguration extends WebSecurityConfigurerAdapter { private static final String ROLE_PREFIX = ""; //... ommited code ... @Bean public GrantedAuthorityDefaults grantedAuthorityDefaults() { return new GrantedAuthorityDefaults(ROLE_PREFIX); } }

Tal vez esté teniendo el mismo problema: podría ver que está usando DefaultMethodSecurityExpressionHandler y también usa los valores predeterminados GrantedAuthorityDefaults, por lo que si está usando la misma versión de Spring Security que yo: 4.2.1. el mismo problema


Aquí está la respuesta tan esperada:

La documentation describe claramente:

Para usar las expresiones hasPermission (), debe configurar explícitamente un PermissionEvaluator en el contexto de su aplicación. Esto se vería algo como esto:

así que básicamente lo estaba haciendo en mi AclConfiguration que extiende GlobalMethodSecurityConfiguration :

@Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService())); expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService())); return expressionHandler; }

Lo cual no fue procesado por la primavera!

Tuve que separar AclConfig y GlobalMethodSecurityConfiguration . Cuando hay @Bean s definido en este último, el método anterior no se procesa, lo que podría ser un error (de lo contrario, cualquier aclaración sobre el tema es bienvenida).


Sus datos en la base de datos y su configuración se ve bien. Utilizo @PostFilter("hasPermission(filterObject, ''READ'')") todo el tiempo.

Me aseguraría de que su clase de usuario que extiende UserDetails esté devolviendo el mismo nombre de usuario a través de getUsername () que tiene en la base de datos. Junto con la verificación para asegurarse de que la seguridad y la aplicación están en el mismo contexto.

el método hasPermission toma un objeto de Authentication como su primer parámetro.

boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission)

El objeto de autenticación es una clase de implementación, generalmente de UsernamePasswordAuthenticationToken . Por lo tanto, el método getPrincipal () debe devolver un objeto que tenga un método getUserName () que devuelva lo mismo que tiene en su base de datos.

Echa un vistazo a PrincipalSid

public PrincipalSid(Authentication authentication) { Assert.notNull(authentication, "Authentication required"); Assert.notNull(authentication.getPrincipal(), "Principal required"); if (authentication.getPrincipal() instanceof UserDetails) { this.principal = ((UserDetails) authentication.getPrincipal()).getUsername(); } else { this.principal = authentication.getPrincipal().toString(); } }