webentwicklung-frage-antwort-db.com.de

In Spring Security wird der Zugriff immer verweigert - DenyAllPermissionEvaluator

Ich habe ACL in meiner Spring Boot-Anwendung konfiguriert. Die ACL-Konfiguration lautet wie folgt:

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

Verweise:

und die Sicherheitskonfiguration lautet wie folgt:

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

Meine CustomAuthenticationProvider-Klasse:

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

Hier ist meine 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);
        }
    }
}

Abgesehen von den obigen Ausführungen habe ich CustomAuthenticationFailureHandler, CustomAuthenticationSuccessHandler, CustomNoRedirectStrategy und CustomUsernamePasswordAuthenticationForm, die ich aus Gründen der Länge dieser Frage übersprungen habe.

Und ich verwende das MySQL-Schema, das hier gefunden werden kann.

Ich füge Einträge zu meinen acl-bezogenen Tabellen wie folgt hinzu:

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

(Ich habe einen Benutzer mit dem Benutzernamen 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)

Aber alles was ich bekomme ist:

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

in meinem

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

Ich vermute hier einige Probleme:

  1. Der hasPermission-Ausdruck: Ich habe ihn durch 'READ' und '1' ersetzt, jedoch in keinem Umfang.
  2. Meine Datenbankeinträge sind nicht richtig
  3. Ich implementiere keinen benutzerdefinierten Berechtigungsauswerter. Ist dies erforderlich oder reicht expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService())); aus?

Update

Beispielmethode, bei der @PostFilter verwendet wird:

@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:

Die Frage spiegelt jetzt alles wider, was in Bezug auf Authentifizierung/Autorisierung/ACL eingerichtet wurde.

Update # 3:

Ich bin jetzt sehr nahe, um das Problem zu lösen. Das einzige, was bleibt, ist, dieses Problem zu lösen:

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

Wenn mir jemand bei dieser Frage helfen könnte, kann ich endlich schreiben, was ich durchgemacht habe, um das Problem zu lösen.

18
Hasan Can Saral

Hier ist die lang erwartete Antwort:

Die Dokumentation beschreibt klar:

Um hasPermission () - Ausdrücke verwenden zu können, müssen Sie explizit eine .__ konfigurieren. PermissionEvaluator in Ihrem Anwendungskontext. Das würde aussehen etwas wie das:

im Grunde war ich in meiner AclConfiguration, die GlobalMethodSecurityConfiguration erweitert:

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

Was im Frühling nicht verarbeitet wurde!

Ich musste AclConfig und GlobalMethodSecurityConfiguration trennen. Wenn in letzterem @Beans definiert ist, wird die obige Methode nicht verarbeitet. Dies kann ein Fehler sein (andernfalls ist jegliche Klärung des Themas erwünscht).

3
Hasan Can Saral

Ich habe meine Anwendung aktualisiert, um Spring Security 4.2.1.RELEASE zu verwenden, und danach kam es zu einem unerwarteten Zugriff, der in allen mit @PreAuthorize annotierten Methoden verweigert wurde, was vor dem Upgrade einwandfrei funktionierte Das Problem bestand darin, dass allen zu überprüfenden Rollen eine Standardzeichenfolge "ROLE_" vorangestellt wurde, unabhängig davon, ob ich mein Standardpräfix auf "leer" gesetzt hatte, wie im nachstehenden Code gezeigt.

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

Alle meine Controller-Methoden wurden mit @PreAuthorize("hasRole('my_ldap_group_name')") kommentiert. Das Framework berücksichtigte jedoch nicht meine leere Rollenpräfix-Einstellung und verwendete daher stattdessen ROLE_my_ldap_group_name , um die tatsächliche Rolle zu überprüfen.

Nachdem ich mich eingehend mit dem Code des Frameworks beschäftigt hatte, wurde mir klar, dass für die Klasse org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler noch das Standardrollenpräfix auf "ROLE_" gesetzt war. Ich verfolgte die Quelle des Werts und fand heraus, dass er zuerst nach einer deklarierten Bean der Klasse org.springframework.security.config.core.GrantedAuthorityDefaults suchte, um während der Erstinitialisierung der Bean org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer nach einem Standardpräfix zu suchen. Da diese Initialisierungs-Bean sie jedoch nicht deklariert fand endete es mit dem oben genannten Standardpräfix.

Ich glaube, dass dies kein erwartetes Verhalten ist: Spring Security sollte das gleiche rolePrefix von ldapAuthentication in Betracht gezogen haben. Um dieses Problem zu lösen, war es jedoch notwendig, die Bean org.springframework.security.config.core.GrantedAuthorityDefaults zu meinem Anwendungskontext (ich verwende eine auf Anmerkungen basierende Konfiguration) als folgende:

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

}

Möglicherweise haben Sie das gleiche Problem - ich konnte sehen, dass Sie DefaultMethodSecurityExpressionHandler verwenden, und es wird auch die Bean GrantedAuthorityDefaults verwendet. Wenn Sie also dieselbe Spring Security-Version wie me - 4.2.1.RELEASE verwenden, laufen Sie wahrscheinlich das gleiche Problem.

6
s_bighead

Ihre Daten in der Datenbank und Ihre Konfig sehen gut aus. Ich benutze die ganze Zeit @PostFilter("hasPermission(filterObject, 'READ')")

Ich würde überprüfen, ob Ihre Benutzerklasse, die UserDetails erweitert, denselben Benutzernamen über getUsername () zurückgibt, den Sie in der Datenbank haben. Neben der Überprüfung, ob sich Sicherheit und App im selben Kontext befinden. 

die hasPermission - Methode nimmt ein Authentication -Objekt als ersten Parameter an.

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

Das Authentifizierungsobjekt ist eine implementierende Klasse, normalerweise aus UsernamePasswordAuthenticationToken . Die getPrincipal () -Methode muss also ein Objekt zurückgeben, das über eine getUserName () -Methode verfügt, die dasselbe Ergebnis in Ihrer Datenbank zurückgibt. 

Werfen Sie einen Blick auf 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
denov