Search code examples
authenticationspring-securityauthorizationhttp-status-code-403access-denied

Spring security custom FilterInvocationSecurityMetadataSource implementation 403 forbidden issue


To make things short I'm trying to implement a custom FilterInvocationSecurityMetadataSource in order to secure/authorize certain parts/URL endpoints dynamically in my web app using spring security 5.0.6 and Spring Boot 2.0.3.

The issue is that no matter what Role I use it always gives me the forbidden page.

I have tried several things with different role names and (believe me) I have searched the whole internet even on spring security 5.0.6 books but nothing seems to work.

This issue may be similar to this: Spring Security issue with securing URLs dynamically

Below the relevant parts of the custom FilterInvocationSecurityMetadataSource

public class DbFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

public Collection<ConfigAttribute> getAttributes(Object object)
        throws IllegalArgumentException {
    FilterInvocation fi=(FilterInvocation)object;
    String url=fi.getRequestUrl();

    System.out.println("URL requested: " + url);

    String[] stockArr = new String[]{"ROLE_ADMIN"};

    return SecurityConfig.createList(stockArr);
}

Below the relevant parts of the custom implementation of securitywebconfigAdapter

@Configuration
public class Security extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .anyRequest().authenticated()
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            public <O extends FilterSecurityInterceptor> O postProcess(
                    O fsi) {
                FilterInvocationSecurityMetadataSource newSource = new DbFilterInvocationSecurityMetadataSource();
                fsi.setSecurityMetadataSource(newSource);
                return fsi;
            }
        })
        .and()
        .formLogin()
        .permitAll();
}

Below the relevant parts for custom userDetails authorities. The user has the role: ROLE_ADMIN in database.

public class CustomUserDetails extends User implements UserDetails {

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    List<String> dbRoles=new ArrayList<String>();
    for (Role userRole : super.getRoles()) {
        dbRoles.add(userRole.getType());
    }

    List<SimpleGrantedAuthority> authorities=new ArrayList<SimpleGrantedAuthority>();
    for (String role : dbRoles) {
        authorities.add(new SimpleGrantedAuthority(role));
    }
    return authorities;
}

What am I doing wrong??
If more code is needed just comment below.
If you have even good books where I can learn this dynamic part of Spring security authorization comment below. Thanks!


Solution

  • I managed to get into the security flow by debugging and it seems that by creating ConfigAttributes of this SecurityConfig class is the 'culprit'

    return SecurityConfig.createList(stockArr);
    public static List<ConfigAttribute> createList(String... attributeNames) {
        Assert.notNull(attributeNames, "You must supply an array of attribute names");
        List<ConfigAttribute> attributes = new ArrayList(attributeNames.length);
        String[] var2 = attributeNames;
        int var3 = attributeNames.length;
    
        for(int var4 = 0; var4 < var3; ++var4) {
            String attribute = var2[var4];
            attributes.add(new SecurityConfig(attribute.trim()));
        }
    
        return attributes;
    }
    

    Above is the actual implementation of the method where you can see

    attributes.add(new SecurityConfig(attribute.trim()));
    

    And this always creates an instance of SecurityConfig type.

    And below you can actually see where and how the decision is being made.

    private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
        Iterator var2 = attributes.iterator();
    
        ConfigAttribute attribute;
        do {
            if (!var2.hasNext()) {
                return null;
            }
    
            attribute = (ConfigAttribute)var2.next();
        } while(!(attribute instanceof WebExpressionConfigAttribute));
    
        return (WebExpressionConfigAttribute)attribute;
    }
    

    So in order for it to actually return a configattribute for checking it must be of type WebExpressionConfigAttribute which is never going to be the case because of this

    attributes.add(new SecurityConfig(attribute.trim()));
    

    So the way I fixed it is to create my own accessDecisionManager the following way

    public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        if(configAttributes == null){
            return  ;
        }
        Iterator<ConfigAttribute> ite = configAttributes.iterator();
        while(ite.hasNext()){
    
            ConfigAttribute ca = ite.next();
    
            String needRole = ((SecurityConfig)ca).getAttribute();
    
            for(GrantedAuthority grantedAuthority : authentication.getAuthorities()){
                if(needRole.trim().equals(grantedAuthority.getAuthority().trim())){
                    return;
                }
            }
        }
        throw new AccessDeniedException("Access is denied");
    }
    

    And registering as above now setting the accessdecisionManager with my custom one

    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            public <O extends FilterSecurityInterceptor> O postProcess(
                    O fsi) {
                FilterInvocationSecurityMetadataSource newSource = new DbFilterInvocationSecurityMetadataSource();
                fsi.setSecurityMetadataSource(newSource);
                fsi.setAccessDecisionManager(new MyAccessDecisionManager());
                return fsi;
            }