2017-01-26 2 views
18

Я настроил ACL в моем приложении Spring Boot. Конфигурации ACL выглядит следующим образом:Доступ всегда отрицается весной Безопасность - DenyAllPermissionEvaluator

@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; 
    } 
} 

Литература:

и конфигурация безопасности выглядит следующим образом:

@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(); 
    } 
} 

Мой 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); 
    } 
} 

Вот мой 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); 
     } 
    } 
} 

Помимо вышеперечисленного, у меня есть CustomAuthenticationFailureHandler, CustomAuthenticationSuccessHandler, CustomNoRedirectStrategy и CustomUsernamePasswordAuthenticationForm, которые я пропустил ради длины этого вопроса.

И я использую схему MySQL, которая может быть найдена here.

Я добавляю записи в моих связанных таблиц аКЛ следующим образом:

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

(У меня есть пользователь с именем demo)

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) 

Но все, что я получаю:

Denying user demo permission 'READ' on object [email protected] 

в моих

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

Я заподозрить несколько вопросов здесь:

  1. Выражение hasPermission: Я заменили его «читать» и «1», но ни в коей мере.
  2. Мои записи в базе данных не являются правильными
  3. Я не внедряю специализированный оценщик разрешений. Это необходимо, или expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService())); достаточно?

Update

метод проб, где @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); 
    } 

Update # 2:

Вопрос теперь отражает все установки в отношении аутентификации/авторизации/ACL.

Update # 3:

теперь я очень близко, чтобы решить эту проблему, единственное, что осталось, это решить эту проблему:

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

Если кто-то может мне помочь с этим вопросом , Я могу, наконец, написать о том, что я сделал, чтобы решить эту проблему.

+0

- метод, который @PostFilter находится на публике, которая реализует интерфейс? – denov

+0

Нет, это на '@ RestController' или' @ Controller'. Я действительно подозреваю записи в базе данных или компонент, который не присутствует. –

+0

Как выглядит метод, где вы помещаете аннотацию @PostFilter? Вы получаете любую Stacktrace в своем журнале сервера? –

ответ

2

Вот долго ждали ответа:

documentation четко описывает:

Чтобы использовать hasPermission() выражения, вы должны явно сконфигурируйте PermissionEvaluator в контексте вашего приложения. Это будет выглядеть что-то вроде этого:

поэтому в основном я делал в моем AclConfiguration, которая простирается GlobalMethodSecurityConfiguration:

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

Который не получал обработаны весной!

Мне пришлось отделить AclConfig и GlobalMethodSecurityConfiguration. Когда в последнем определены @Bean s, указанный выше метод не обрабатывается, что может быть ошибкой (если нет, то любое разъяснение по теме приветствуется).

2

Я обновил мое приложение, чтобы использовать Spring Security 4.2.1.RELEASE, после чего я начал испытывать неожиданный доступ, запрещенный всеми @PreAuthorize аннотированными методами, который работал отлично до обновления. Я отлаживал защитный код весны, и я понял, что проблема в том, что все роли, которые нужно проверить, имеют префикс строки по умолчанию «ROLE_», независимо от того, что я установил префикс по умолчанию для пустого, как показано в приведенном ниже коде ,

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); 

Все мои методы контроллера были аннотированный с @PreAuthorize("hasRole('my_ldap_group_name')"), однако, система была не принимая мою пустую роль настройки во внимание префикс и, таким образом, он использует ROLE_my_ldap_group_name проверить фактическую роль вместо этого.

После того, как я углубился в код фреймворка, я понял, что класс org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler по-прежнему имеет префикс роли по умолчанию, установленный на "ROLE_". Я отслеживал источник его значения, и я выяснил, что он сначала проверял объявленный bean-класс класса org.springframework.security.config.core.GrantedAuthorityDefaults на поиск префикса по умолчанию при первой инициализации компонента org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer, однако, поскольку этот компонент инициализатора не смог найти его объявленным , он в конечном итоге использовал вышеупомянутый префикс по умолчанию.

Я считаю, что это не ожидаемое поведение: Spring Security должна была рассмотреть один и тот же параметр rolePrefix из ldapAuthentication, однако, чтобы решить эту проблему, необходимо было добавить компонент org.springframework.security.config.core.GrantedAuthorityDefaults в контекст моего приложения (я использую аннотацию на основе конфигурации), следующим образом:

@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); 
    } 

} 

может быть, вы получаете ту же самую проблему - я мог видеть, что вы используете DefaultMethodSecurityExpressionHandler и он также использует боб GrantedAuthorityDefaults, так что если вы используете ту же версию Spring Security как я - 4.2.1.RELEASE, вы, вероятно, сталкиваетесь с той же проблемой.

+0

Спасибо за ответ, я ценю это. Я знаю, что Spring требует префикса «ROLE_», и все мои роли начинаются с «ROLE_». Поэтому я вряд ли думаю, что это должно иметь какое-либо отношение к именам ролей. Кроме того, я не фильтрую и не авторизуюсь на основе роли, но вместо этого я использую acl для проверки разрешений на основе объекта/пользователя. –

+1

Итак, это то, что я должен был поделиться, надеюсь, вы поймете проблему, я никогда не использую ACL ... –

0

Ваши данные в БД и вашей конфигурации выглядят хорошо. Я использую @PostFilter("hasPermission(filterObject, 'READ')") все время.

Я бы проверял, чтобы ваш пользовательский класс, который расширяет UserDetails, возвращает одно и то же имя пользователя через getUsername(), который у вас есть в db. Наряду с проверкой, чтобы убедиться, что безопасность и приложение находятся в одном контексте.

Метод hasPermission принимает объект Authentication как его первый параметр.

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

Объект по аутентификации является класс реализации, как правило, UsernamePasswordAuthenticationToken. Поэтому метод getPrincipal() должен возвращать объект с методом getUserName(), который возвращает то же самое, что и в вашей БД.

Посмотрите на 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(); 
    } 
} 
+0

Я не совсем использую 'UserDetails' и' UserDetailsService'. Я только возвращаю 'UsernamePasswordAuthenticationToken' из моего класса, который« реализует AuthenticationProvider ». Должен ли я для этого работать? –

+0

добавить более подробную информацию. похоже, что у вас нет настройки объекта аутентификации правильно. – denov

+0

Согласен. Я обновил вопрос, чтобы отразить все, что я настроил для аутентификации/авторизации и ACL. –

Смежные вопросы