0

I try to use the following Annotation to Authenticate in Testing of my Spring Boot 2 Application.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MockAuthorize {
    String[] roles() default {Role.ROLE_ADMIN};

    String username() default "testUser";
}

After research how to call Code before and after custom Annotated Methods I decided to use Spring AOP for that.

@Aspect
public class MockAuthorizeAspect {
    @Around("app.annotation.AspectJPointcutStorage.mockAuthorize()")
    public Object loginBeforeAndLogoutAfter(ProceedingJoinPoint joinPoint, MockAuthorize mockAuthorize) throws Throwable {
        final String[] roles = mockAuthorize.roles();
        final String username = mockAuthorize.username();
        Set<GrantedAuthority> resolvedRoles = Sets.newLinkedHashSet();
        for (String role : roles) {
            resolvedRoles.add(new SimpleGrantedAuthority(role));
        }
        SecurityContextHolder.getContext().setAuthentication(new PreAuthenticatedAuthenticationToken(username, "", resolvedRoles));
        final Object proceed = joinPoint.proceed();
        AuthentificationService.AuthentificationMock.logout();
        return proceed;
    }
}

So I added Config to my pom.xml

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

But when I execute my Testing in Debug Mode...

@SpringBootTest
class UserTest {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private MockAuthorizeAspect mockAuthorizeAspect;
    private User user = null;

    @BeforeEach
    void setUp() {
        try {
            AuthentificationService.AuthentificationMock.loginAsAdminOnly();
            user = new User("hansmeier", "password", "Hans", "Meier", "hansimeier@gmx.net", "+4915465656", null);
            user.setRoles(Sets.newHashSet(roleService.getByName(Role.ROLE_USER).orElse(null)));
            AuthentificationService.AuthentificationMock.logout();
        } catch (Exception e) {
            //Nothing to do here!
        }
    }

    @AfterEach
    void tearDown() {
        try {
            AuthentificationService.AuthentificationMock.loginAsAdminOnly();
            userService.delete(user);
            AuthentificationService.AuthentificationMock.logout();
        } catch (Exception e) {
            //Nothing to do here!
        }
    }

    @Test
    @SneakyThrows
    void testCreate() {
        //GIVEN
        AuthentificationService.AuthentificationMock.loginAsAdminOnly();
        //WHEN
        user = userService.save(user);
        //THEN
        assertEquals(userService.getByUsernameResolved(user.getUsername()).orElse(null), user);
        AuthentificationService.AuthentificationMock.logout();
    }

    @Test
    void testCreateUnauthorized() {
        boolean failed = false;
        //WHEN
        try {
            user = userService.save(user);
        } catch (AuthenticationCredentialsNotFoundException e) {
            failed = true;
        }

        assertTrue(failed);
    }

    @Test
    @SneakyThrows
    void testGetByUsername() {
        //GIVEN
        AuthentificationService.AuthentificationMock.loginAsAdminOnly();
        user = userService.save(user);
        //WHEN
        final User foundUser = userService.getByUsernameResolved(user.getUsername()).orElse(null);
        AuthentificationService.AuthentificationMock.logout();
        //THEN
        assertEquals(user, foundUser);
    }

    @Test
    @MockAuthorize
    void testGetById() {
        //GIVEN
        user = userService.save(user);
        //WHEN
        final User foundUser = userService.getById(user.getId()).orElse(null);
        //THEN
        assertEquals(user, foundUser);
    }

    @Test
    @MockAuthorize
    void testGetAll() {
        //GIVEN
        user = userService.save(user);
        //WHEN
        final Set<User> allResolved = userService.getAllResolved();
        //THEN
        for (User u : allResolved) {
            if (u.equals(user)) {
                assertTrue(true);
                return;
            }
        }
        fail("User is not in all Users, something went wrong");
    }


}

...the AspectClass is not called and no Exception is thrown, except one from Spring Security Authentication because none is Provided.

So I did research what are necessary and I tried adding a Configuration:

@Configuration
@EnableAspectJAutoProxy
public class AOPConfiguration {
    @Bean
    public MockAuthorizeAspect mockAuthorizeAspect(){
        return new MockAuthorizeAspect();
    }
}

My POM:

<dependencies>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
           <exclusion>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-logging</artifactId>
           </exclusion>
         </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt</artifactId>
       <version>0.9.1</version>
     </dependency>
     <dependency>
       <groupId>javax.xml.bind</groupId>
       <artifactId>jaxb-api</artifactId>
     </dependency>
     <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
     </dependency>
     <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
     </dependency>
     <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
     </dependency>
     <dependency>
        <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.18</version>
     </dependency>
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-aop</artifactId>
     </dependency>
     <dependency>
        <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.18.10</version>
         <scope>provided</scope>
     </dependency>
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
     </dependency>
     <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
     </dependency>
     <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>28.1-jre</version>
     </dependency>
     <dependency>
        <groupId>org.jetbrains</groupId>
        <artifactId>annotations</artifactId>
        <version>16.0.2</version>
        <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
       <version>3.9</version>
     </dependency>
     <dependency>
       <groupId>javax.annotation</groupId>
       <artifactId>jsr250-api</artifactId>
       <version>1.0</version>
     </dependency>
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
     </dependency>
</dependencies>

<build>
  <plugins>
     <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
     </plugin>
     <plugin>
       <groupId>org.codehaus.mojo</groupId>
       <artifactId>aspectj-maven-plugin</artifactId>
       <version>1.4</version>
       <executions>
          <execution>
              <goals>
                 <goal>compile</goal>
                 <goal>test-compile</goal>
              </goals>
          </execution>
       </executions>
       <configuration>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.target}</target>
      </configuration>
   </plugin>
   <plugin>
      <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <version>2.5.1</version>
       <configuration>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.target}</target>
       </configuration>
    </plugin>
 </plugins>
</build>

Sadly there were no changes in behavior, so now I hope anyone had faced a similar problem or has an Idea what I missed.

Willey3x37
  • 310
  • 2
  • 12

2 Answers2

1

From Documentation

== Spring AOP Capabilities and Goals

Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans)

This means that for Spring AOP to work here, the class with method testGetAll() should be Spring managed bean. Please confirm if that is the case

Also

1.Aspect should be annotated with @Component and made available in component scan. ( Update : missed that you have the @Bean declared in configuration class. That should do)

2.The point cut expression for annotation would be as follows

@Around("@annotation(app.annotation.AspectJPointcutStorage.MockAuthorize)")
R.G
  • 6,436
  • 3
  • 19
  • 28
  • The Classes the Aspect take Affect are @SpringBootTest Cases so that should be no Problem – Willey3x37 Dec 30 '19 at 07:37
  • Tried to Autowire the the Aspect as well with no Problem but still no execution, I'm really confused, btw I changed the @Around your way and it causes an Error, so I think my implementation is somehow correct idk – Willey3x37 Dec 30 '19 at 11:30
  • 1
    SpringBootTest annotation will not make the test class a bean . Could you please share the complete code of the Test class – R.G Dec 30 '19 at 11:32
  • 1
    UserTest here is not a Spring Container managed bean. You will not be able to advice the Test class using Spring AOP. Please refer : https://stackoverflow.com/questions/39619982/spring-aop-pointcut-before-advice-for-test-methods-does-not-work – R.G Dec 30 '19 at 12:02
  • Do you have an Idea how I can process the annotation in a different way so that it works? – Willey3x37 Dec 31 '19 at 09:15
  • How about junit Rules ? https://stackoverflow.com/questions/13489388/how-does-junit-rule-work – R.G Dec 31 '19 at 12:11
  • Could be a solution but I want it Annotation based and I want to use the Annotation in the Application as well, so that would be a downgrade – Willey3x37 Dec 31 '19 at 17:57
  • 1
    You may ask this as a fresh question in the community so that people knowledgeable can help you out. – R.G Jan 01 '20 at 04:28
0

Would it be possible to use normal Guice or AspectJ witjout Spring Context, because the SecurityContextHolder Class is not a Spring Bean only a Static Value Holding Class used by Spring Security?

Willey3x37
  • 310
  • 2
  • 12