0

Spring Boot here. I have the following web security config:

@EnableWebSecurity
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {

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

        // enable CSRF
        httpSecurity.csrf().disable();

        // add CORS filter
        httpSecurity.cors();

        // add anonoymous/permitted paths (that is: what paths are allowed to bypass authentication)
        httpSecurity.authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            .antMatchers(HttpMethod.GET, "/actuator/health").permitAll();

        // restrict all other paths and set them to authenticated
        httpSecurity.authorizeRequests().anyRequest().permitAll();// TODO: authenticated();

    }

}

When I start up the app, I can open up the actuator health check URL just fine (http://localhost:9200/actuator/health), but if I try to make an HTTP POST to any URL, I get a 403 Access Denied exception.

I would think that httpSecurity.authorizeRequests().anyRequest().permitAll(); would permit requests to any other request, including a POST to, say, http://localhost:9200/api/users. But no, I get 403s. Can anyone spot why?

I should mention, at this stage in development, I want Spring Security activated and running in the background, but not actively enforcing authentication on any URLs, hence my desire for any request being permitted. That will change shortly in weeks to come.

Update

I have disabled CSRF (see above), and configured Spring Security to log DEBUG mode (here's a new snippet from my application.yml:

spring:
  logging:
    level:
      org:
        springframework:
          security: DEBUG

And here is my curl:

curl -ik -H "Accept: application/json" -H "Content-Type: application/json" -d '{"name":"ACME, Inc.","website":"http://example.com"' -X POST 'http://127.0.0.1:9200/ap1/v1/users/'
HTTP/1.1 400 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Mon, 06 Mar 2023 13:26:32 GMT
Connection: close

And here is what Spring Security logged:

************************************************************

Request received for POST '/ap1/v1/users/':

org.apache.catalina.connector.RequestFacade@14836146

servletPath:/ap1/v1/users/
pathInfo:null
headers: 
host: 127.0.0.1:9200
user-agent: curl/7.64.1
accept: application/json
content-type: application/json
content-length: 223


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


************************************************************


2023-03-06 01:29:15.150  INFO 28967 --- [nio-9200-exec-1] Spring Security Debugger                 : 

************************************************************

New HTTP session created: AAB00E991E9392DCC8799310F1CE82E6

Call stack: 

    at org.springframework.security.web.debug.Logger.info(Logger.java:46)
    at org.springframework.security.web.debug.DebugFilter$DebugRequestWrapper.getSession(DebugFilter.java:167)
    at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:229)
    at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:229)
    at org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.saveToken(HttpSessionCsrfTokenRepository.java:58)
    at org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.saveTokenIfNecessary(LazyCsrfTokenRepository.java:168)
    at org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.getToken(LazyCsrfTokenRepository.java:125)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:124)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
    at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90)
    at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:78)
    at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:67)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:836)


************************************************************


2023-03-06 01:29:15.157  INFO 28967 --- [nio-9200-exec-1] Spring Security Debugger                 : 

************************************************************

Request received for POST '/error':

org.apache.catalina.core.ApplicationHttpRequest@1ae30ce

servletPath:/error
pathInfo:null
headers: 
host: 127.0.0.1:9200
user-agent: curl/7.64.1
accept: application/json
content-type: application/json
content-length: 223


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


************************************************************
hotmeatballsoup
  • 385
  • 6
  • 58
  • 136
  • 1
    you do know you can just enable debug logs and read in the logs yourself what the reason for the 403 is? it is most likely CSRF, since you have it enabled and i assume you are not posting a CSRF token during your post request. But you havn't taken the time to even post your request or your debug logs so im only guessing based on the little information you have posted. – Toerktumlare Mar 06 '23 at 01:05
  • Thanks so much for the _nudge_ @Torktumare (+1), please see my updates I have included all of the information you have requested. – hotmeatballsoup Mar 06 '23 at 01:20
  • look at your logs, they only say `INFO` as i mentioned above, you nedd to ENABLE debug logs to be able to read them. You can also disable CSRF protection and see if the request goes through or the error message changes. Then you know its a CSRF issue, and i suggest you read up on what CSRF is. – Toerktumlare Mar 06 '23 at 01:25
  • I have commented-out/removed CSRF and enabled DEBUG logging @Torktumare but still the same problem persists. I suspect I am using anyRequests incorrectly – hotmeatballsoup Mar 06 '23 at 01:33
  • still you havn't posted spring security debug logs read this please https://stackoverflow.com/a/47729991/1840146 and post your FULL debug logs, not a snippet. Also you need to allow `/error` GET requests – Toerktumlare Mar 06 '23 at 02:03

1 Answers1

1

Yes. It is because CSRF protection is enabled by default and your HTTP request does not provide a valid CSRF token and so it returns 403.

And just commenting httpSecurity.csrf() does not mean you disable CSRF . It just means you do not want to change any CSRF default setting and so you can see that the request still goes through CsrfFilter from the logs.

To disable CSRF , you have to do :

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    http.csrf().disable();
}

Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • Thanks so much @Ken, I made your change (`http.csrf().disable()`) and now I am getting the same Security Filter logs as above but the request is coming back as an empty HTTP 400. Any thoughts on how I could debug? – hotmeatballsoup Mar 06 '23 at 13:30
  • please update the latest log when you get HTTP 400. If you disable CSRF , there will be no `CsrfFilter` in the log that you show. – Ken Chan Mar 06 '23 at 13:35
  • And 400 is more related to your request has bad format but not related to security stuff – Ken Chan Mar 06 '23 at 13:36
  • 1
    Nevermind @Ken you are absolutely correct, I had a `@Validation` annotation on my request body entity/DTO and the validation is what is now failing and causing the latest 400. Thank you so much for your help here, you have been a _true_ **life saver**!!! – hotmeatballsoup Mar 06 '23 at 13:39