1

I am trying to toggle/bypass/disable Spring Security (Authentication and Authorization) for all the requests having particular Request Header.

For example, if a request url is hit with that Request Header, Spring Security should be bypassed, if not it should not be bypassed.

For this, I am using following requestMatchers Spring Security config:

@Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring()
        .antMatchers(HttpMethod.GET)
        .antMatchers(HttpMethod.OPTIONS)
        .requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
  }

My remaining Security Config is :

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity (prePostEnabled = true)
@ConditionalOnProperty (name = "security.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private SecurityProps securityProps;

  @Autowired
  private MyUserDetailsService myUserDetailsService;

  @Autowired
  private MyAuthenticationEntryPoint myAuthenticationEntryPoint;

  @Autowired
  private MyCORSFilter myCORSFilter;

  public SecurityConfig() {
    SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
  }


  @Override
  protected void configure(HttpSecurity http) throws Exception {

    http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .csrf().disable()
        .addFilterBefore(myCORSFilter, SessionManagementFilter.class)
        .addFilterBefore(requestHeaderFilter(), RequestHeaderAuthenticationFilter.class)
        .authenticationProvider(preauthAuthProvider())
        .authorizeRequests()
          .antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
          .antMatchers(HttpMethod.OPTIONS, securityProps.getNoAuthOptionsPattern()).permitAll()
          .requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE")).permitAll()
          .anyRequest().authenticated()
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(myAuthenticationEntryPoint);
  }

  @Autowired
  @Override
  protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(preauthAuthProvider());
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring()
        .antMatchers(HttpMethod.GET)
        .antMatchers(HttpMethod.OPTIONS)
        .requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
  }

  public RequestHeaderAuthenticationFilter requestHeaderFilter() throws Exception {
    RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
    requestHeaderAuthenticationFilter.setPrincipalRequestHeader(MySecurityConstants.LOGIN_HEADER);
    requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager());
    requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(false);
    requestHeaderAuthenticationFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
      @Override
      public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
          AuthenticationException exception) throws IOException, ServletException {
        if (exception instanceof MySecurityException) {
          myAuthenticationEntryPoint.commenceMySecurityException(request, response, (MySecurityException) exception);
        } else if (exception instanceof UsernameNotFoundException) {
          myAuthenticationEntryPoint.commenceUsernameNotFoundException(request, response,
              (UsernameNotFoundException) exception);
        } else if (exception instanceof PreAuthenticatedCredentialsNotFoundException) {
          myAuthenticationEntryPoint.commence(request, response, exception);
        }
      }
    });
    return requestHeaderAuthenticationFilter;
  }

  @Bean
  public PreAuthenticatedAuthenticationProvider preauthAuthProvider() throws Exception {
    PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
    authProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
    return authProvider;
  }

  @Bean
  public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper()
      throws Exception {
    UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
        new UserDetailsByNameServiceWrapper<>();
    wrapper.setUserDetailsService(ivyUserDetailsService);
    return wrapper;
  }

}

With the above settings, I am unable to disable/bypass Spring Security and I am getting the AuthenticationCredentialsNotFoundException exception:

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

Can anyone help me by identifying what am I doing wrong? Is my approach correct or I need to do something else to achieve this?

EDIT : I am getting this exception in org.springframework.security.access.intercept.AbstractSecurityInterceptor class in beforeInvocation() method where it tries to get the authentication object from SecurityContextHolder. AbstractSecurityInterceptor is invoked by its subclass MethodSecurityInterceptor which is invoked from my Spring Controller which is annotated with @PreAuthorize.

Sahil Chhabra
  • 10,621
  • 4
  • 63
  • 62
  • Where do you get the exception? Could you add the whole stacktrace? – dur Jul 25 '18 at 07:13
  • @dur I have updated the question with details where I am getting the exception. The stacktraceElement array is of size 0 so stack trace is not available. Just getting the exception message that I have mentioned in question. – Sahil Chhabra Jul 25 '18 at 07:35
  • Are your `GET` methods annotated with `PreAuthorize`, too? You should get the same exception for `GET` methods. – dur Jul 25 '18 at 07:41
  • No. GET methods are not annotated with PreAuthorize. – Sahil Chhabra Jul 25 '18 at 08:29
  • That's the reason, why it is working with `GET`methods. If you disable Spring Security for some URLs, you cannot use method security for that URLs. Instead of ignoring the URLs you have to permit these URLs. – dur Jul 25 '18 at 08:46
  • I understood why GET is working. But, what I need is only request with particular Request Header should bypass security, if that Header is not passed it should not bypass security. I will update the question for the same. – Sahil Chhabra Jul 25 '18 at 08:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176699/discussion-between-sahil-chhabra-and-dur). – Sahil Chhabra Jul 25 '18 at 08:54

1 Answers1

0

I think your bypass is working fine. Its skipping the check.

The security's authorization check part gets the authenticated object from SecurityContext, which will be set when a request gets through the spring security filter.

So when you skip security filter SecurityContext is not set yet thus the error

You can do something like this to set it manually for your Custom Header Case

try {
    SecurityContext ctx = SecurityContextHolder.createEmptyContext();
    SecurityContextHolder.setContext(ctx);
    ctx.setAuthentication(event.getAuthentication());
} finally {
    SecurityContextHolder.clearContext();
}

Edit 1:

Answering all the queries.

But if thats the case, then I guess all GET call should also have failed, but my GET calls are working fine.

Since you have added this line All your GET calls are skipped from security check.

.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()

where can I add the code you have mentioned? Any particular filter or somewhere else ?

I have done something like this in a Filter. Refer Here

Look at TokenAuthenticationFilter Class in Answer. Where am manually setting.

Note: Its JWT implementation but good to refer

UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
    // create authentication
    TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
    authentication.setToken(authToken);
    SecurityContextHolder.getContext().setAuthentication(authentication);
}

What is event in your answer?

I just got that case from Some Answer, cant find its link now. But you can setAuthentication like this or like above

Authentication authentication = new PreAuthenticatedAuthenticationToken("system", null);
authentication.setAuthenticated(true);
context.setAuthentication(authentication);
Sahil Chhabra
  • 10,621
  • 4
  • 63
  • 62
MyTwoCents
  • 7,284
  • 3
  • 24
  • 52
  • Thanks for your answer. But if thats the case, then I guess all GET call should also have failed, but my GET calls are working fine. Anyways, where can I add the code you have mentioned? Any particular filter or somewhere else? What is event in your answer? – Sahil Chhabra Jul 25 '18 at 06:00
  • Thanks again. I will give it a try. But, still as per your reason why it is working for GET and not with Header, if I add permitAll() for .requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE")) in HttpSecurity config just like for the GET. Its giving me the same error. Updated the question for reference. – Sahil Chhabra Jul 25 '18 at 07:04
  • That's good Point.... As per this it should allow. https://docs.spring.io/spring-security/site/docs/4.2.6.RELEASE/apidocs/org/springframework/security/web/util/matcher/RequestHeaderRequestMatcher.html ... – MyTwoCents Jul 25 '18 at 07:27