3

This is a spring boot 2.0.6 application using an MVC controller using @PreAuthorize and #oauth2.hasScope to secure a controller endpoint. I have written tests using @WebMvcTest to write controller tests. It appears that the requests are being made however when #oauth2.hasScope is being called it's not given a value to compare against somewhere. What do I have wrong?

Exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Failed to evaluate expression '#oauth2.hasScope('foo')'

    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
    at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:71)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:166)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
...
Caused by: java.lang.IllegalArgumentException: Failed to evaluate expression '#oauth2.hasScope('eq.distributor')'
    at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:30)
    at org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice.before(ExpressionBasedPreInvocationAdvice.java:59)
    at org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter.vote(PreInvocationAuthorizationAdviceVoter.java:72)
    ... 72 more
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method hasScope(java.lang.String) on null context object
    at org.springframework.expression.spel.ast.MethodReference.throwIfNotNullSafe(MethodReference.java:153)
    at org.springframework.expression.spel.ast.MethodReference.getValueRef(MethodReference.java:82)
    ... 94 more

Test class

@RunWith(SpringRunner.class)
@WebMvcTest
public class MockMvcTestBase {
    @Autowired
    protected WebApplicationContext context;

    private ObjectMapper objectMapper;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(documentationConfiguration(this.restDocumentation)
                        .uris()
                        .withScheme("http")
                        .withHost("development.com")
                        .withPort(80))
                .alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
                .apply(springSecurity())
                .build();

        this.objectMapper = new ObjectMapper();
    }


@Test
@WithMockUser(username = "testclient", authorities = "foo")
public void givenValidUser_whenGetOrderDetailsWebEC_thenGetOrder() throws Exception {
    getMockMvc().perform(get("/ok")
            .header("Authorization", authorizationHeader()))
            .andExpect(status().isOk())
            .andExpect(content().string("ok"));
}
}

Auth Configuration

@Configuration
@EnableAuthorizationServer
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SuppressWarnings("static-method")
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Bean
    public static PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("testclient")
                .secret("password")
                .authorizedGrantTypes("client_credentials")
                .accessTokenValiditySeconds(0)
                .scopes("foo")
                .and()
                .withClient("bar")
                .secret("password")
                .authorizedGrantTypes("client_credentials")
                .accessTokenValiditySeconds(0)
                .scopes("eq.distributor");
    }
}

Controller

@PreAuthorize("#oauth2.hasScope('foo')")
@RequestMapping(value = "/ok", method = RequestMethod.GET)
public String ok() {
    return "ok";
}

Spring Security Debug Output

org.springframework.mock.web.MockHttpServletRequest@54704b46

servletPath:
pathInfo:/ok
headers: 
Authorization: Bearer 57962883-e379-433b-b613-1f888471fb84
Accept: application/json;charset=UTF-8


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  LogoutFilter
  OAuth2AuthenticationProcessingFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]

Any assistance on what I am missing in my setup would be greatly helpful. Something does seem to be wrong as if it was actually attempting to authenticate there would be a 401/403 returned. Not an exception. Minus the changes required to make this compile, this worked under Spring Boot 1.5.9.

If I swap out the mock mvc annotation for @SpringBootTest this works however I don't need the database on these tests so I would like to set this up to not need to load that.

Rig
  • 1,276
  • 3
  • 22
  • 43
  • Hi Rig, have you try to solve this issue? I'm facing the same one, and didn't find any useful solution yet. – Blangero Jan 02 '19 at 09:42
  • I never had it working Blangero. One solution was to disable security but I wanted my controllers but I wanted to verify their security was working too. The effort was more or less abandoned when I then ran into problems with Feign communications between 1.5.x and 2.0 apps using my common interface libraries. I might be addressing this again in the new year though so maybe I'll find the solution. – Rig Jan 08 '19 at 16:34

1 Answers1

1

This happens, because Spring is using the DefaultMethodSecurityExpressionHandler. See this: #oauth2 security expressions on method level

To solve the issue do this:

  1. remove @EnableGlobalMethodSecurity from AuthorizationServerConfig
  2. add the following new configuration class

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return new OAuth2MethodSecurityExpressionHandler();
    }
}

an important thing for me was, that I already hat the @EnableGlobalMethodSecurity already on another configuration, so spring initially ignored the new config class

David Steiman
  • 3,055
  • 2
  • 16
  • 22