beginner_
beginner_

Reputation: 7632

@PreAuthorize: reference property in implementing class

I have service interface

public interface CompoundService<T extends Compound> {

    T getById(final Long id);

    //...
}

and abstract implementation

public abstract class CompoundServiceImpl<T extends Compound>
        implements CompoundService<T> {

    //...   

    private Class<T> compoundClass;

    //...
}

Every implementation of Compound requires it's own service interface which extends CompoundService and it's own service class which extends CompoundServiceImpl.

I would now like to add basic security uisng annotations to my methods in CompoundService. As far as I understood I must add them in the interface not the actual implementation. Since a user can have different roles for different implementations of Compound, i must take this into account. Meaning in @PreAuthorize I would like to get the name of the Compound implementation, eg. compoundClass.getSimpleName(). So that I get something like:

public interface CompoundService<T extends Compound> {

    @PreAuthorize("hasRole('read_' + #root.this.compoundClass.getSimpleName())")
    T getById(final Long id);

    //...
}

This is basically what is mentioned here:

https://jira.springsource.org/browse/SEC-1640

however there is no example and I did not really get the solution. So should i use this? or as above #root.this?

My second question is, since this is in an interface which will be implemented by a proxy (from spring) will the experession this.compoundClass actually evaluate properly?

And last but not least how can I actually test this?*

* I'm not actually creating a finished application but something configurable, like a framework for s specific type of database search. Meaning most authorization and authentication stuff has to come from the implementer.

Upvotes: 2

Views: 1937

Answers (1)

beginner_
beginner_

Reputation: 7632

  1. Unit Testing

see http://www.lancegleason.com/blog/2009/12/07/unit-testing-spring-security-with-annotations

Since that is an old tutorial you might need to change the referenced schema versions. But more importantly the SecurityContext.xml configuration shown there does not work with Spring Security 3. See Spring Security - multiple authentication-providers for a proper configuration.

I did not require the mentioned dependencies:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core-tiger</artifactId>
</dependency>

it worked without them (however did not create an abstract test class)

  1. root.this

This is in fact correct approach

The problem is that you can't use getSimpleName() of a class parameter. For an in-depth discussion see http://forum.springsource.org/showthread.php?98570-Getting-Payload-Classname-in-Header-Enricher-via-SpEL

The workarounds shown there did not help me much. So I came up with this very simple solution:

Just add the string property String compoundClassSimpleName to CompoundServiceImpl and set it in the constructor (which is called by subclasses):

Public abstract class CompoundServiceImpl<T extends Compound>
    implements CompoundService<T> {
    
    private String compoundClassSimpleName;

    //...
    
    public ChemicalCompoundServiceImpl(Class<T> compoundClass) {
        this.compoundClass = compoundClass;
        this.compoundClassSimpleName = compoundClass.getSimpleName();
    }
    
    //...
    
    public String getCompoundClassSimpleName(){
        return compoundClassSimpleName;
    }   
}

and her a Service implementing above abstract service:

public class TestCompoundServiceImpl extends CompoundServiceImpl<TestCompound>
        implements TestCompoundService {

    //...   

    public TestCompoundServiceImpl() {
        super(TestCompound.class);
    }
    
    //...   
    
}

And final the @PreAuthorize annotation usage:

public interface CompoundService<T extends Compound> {

    @PreAuthorize("hasRole('read_' + #root.this.getCompoundClassSimpleName())")
    public T getById(final Long id);
}

For above example the expression will evaluate to a role named "read_TestCompound".

Done!

As often the solution is very simple but getting there is a PITA...

EDIT:

for completeness the test class:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:ApplicationContext.xml",
        "classpath:SecurityContext.xml"
        })
public class CompoundServiceSecurityTest {

    @Autowired
    @Qualifier("testCompoundService")
    private TestCompoundService testCompoundService;

    public CompoundServiceSecurityTest() {
    }
    

    @Before
    public void setUp() {
        SecurityContextHolder.getContext().setAuthentication(
            new UsernamePasswordAuthenticationToken("user_test", "pass1"));
    }

     @Test
     public void testGetById() {
        System.out.println("getById");
        Long id = 1000L;
        TestCompound expResult = new TestCompound(id, "Test Compound");
        TestCompound result = testCompoundService.getById(id);
        assertEquals(expResult, result);
     }
}

Upvotes: 2

Related Questions