在 Spring 安全性中始终拒绝访问 - DenyAllPermissionEvaluator

Access is Always Denied in Spring Security - DenyAllPermissionEvaluator

我已经在 Spring 引导应用程序中配置了 ACL。 ACL配置如下:

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

我的CustomAuthenticationProviderclass:

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

除此之外,还有CustomAuthenticationFailureHandlerCustomAuthenticationSuccessHandlerCustomNoRedirectStrategyCustomUsernamePasswordAuthenticationForm,为了这个问题的篇幅,我跳过了

我正在使用 MySQL 可以找到的架构 here

我正在将条目添加到我的 acl 相关表中,如下所示:

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 com.company.project.domain.users.User@4a49e9b4

在我的

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

我怀疑这里有几个问题:

  1. hasPermission 表达式:我已将其替换为 'READ' 和“1”,但没有任何程度。
  2. 我的数据库条目不正确
  3. 我没有实施自定义权限评估器。这是必需的,还是 expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService())); 就足够了?

更新

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

更新#2:

问题现在反映了关于 authentication/authorization/ACL 的所有设置。

更新#3:

我现在非常接近解决这个问题,唯一剩下的就是解决这个问题:

如果有人能帮我解决这个问题,我终于可以写下我为解决这个问题所经历的一切。

我升级了我的应用程序以使用 Spring Security 4.2。1.RELEASE 然后我开始遇到所有 @PreAuthorize 注释方法中的意外访问被拒绝,这在之前工作得很好升级。 我调试了 spring 安全代码,我意识到问题是所有要检查的角色都以默认字符串 "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 改为检查实际角色。

深入研究框架的代码后,我意识到 class org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler 的默认角色前缀仍然设置为 "ROLE_"。我跟踪了它的值的来源,我发现它首先检查 class org.springframework.security.config.core.GrantedAuthorityDefaults 的声明 bean 以在 bean org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer 的第一次初始化期间查找默认前缀, 然而,由于这个初始化器 bean 找不到它声明的,它最终使用了前面提到的默认前缀。

我认为这不是预期的行为:Spring 安全性应该考虑来自 ldapAuthentication 的相同 rolePrefix,但是,为了解决这个问题,有必要将 bean 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 并且它也使用 bean GrantedAuthorityDefaults,所以如果你使用与我相同的 Spring 安全版本 - 4.2 .1.RELEASE 你可能 运行 遇到了同样的问题。

您在数据库中的数据和您的配置看起来不错。我一直使用 @PostFilter("hasPermission(filterObject, 'READ')")

我会检查以确保扩展 UserDetails 的用户 class 与数据库中的 getUsername() 用户名​​相同 return。除了检查以确保您的安全性和应用程序处于同一上下文中。

hasPermission method take an Authentication 对象,因为它是第一个参数。

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

Authentication 对象是 class 的实现,通常是 UsernamePasswordAuthenticationToken。因此 getPrincipal() 方法需要 return 一个具有 getUserName() 方法的对象,该方法 return 与数据库中的对象相同。

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

这是期待已久的答案:

documentation 明确描述:

To use hasPermission() expressions, you have to explicitly configure a PermissionEvaluator in your application context. This would look something like this:

所以基本上我在我的 AclConfiguration 中做了扩展 GlobalMethodSecurityConfiguration:

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

未被 Spring 处理!

我不得不将 AclConfigGlobalMethodSecurityConfiguration 分开。当后者定义了 @Bean 时,上述方法没有得到处理,这可能是一个错误(如果没有,欢迎对主题进行任何澄清)。

很明显,AclPermissionEvaluator不被尊重。我的路上出现了同样的问题,通过2个步骤解决了:

  1. 在重写方法中创建表达式处理程序而不是注入
  2. WebSecurityConfig 是在与 GlobalMethodSecurityConfiguration 相同的 class 中创建的,否则,GlobalMethodSecurityConfiguration 将不起作用,也不会覆盖处理程序。