Spring Security + Thymeleaf - TemplateInputException after a second login - spring

I have a navbar with LOGIN, PROFILE, and LOGOUT.
My goal is to remove the LOGIN and show only PROFILE and LOGOUT
after the user is logged in.
I have the controller checking for authenticated users:
#Controller
public class LoginController {
#GetMapping("/login")
private String loginRender(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || authentication instanceof AnonymousAuthenticationToken){
return "login";
}else {
return "redirect:/";
}
}
}
The Thymeleaf template looks like this:
<ul class="logout-ul top-links-container">
<li class="top-links-item text-center" sec:authorize="!isAuthenticated()"> <i class="icon-user4" style="color: #e35f5f"></i>Login</li>
<li class="top-links-item text-center" sec:authorize="isAuthenticated()"><i class="icon-user4" style="color: #e35f5f"></i>Profile</li>
<li class="text-center logout" sec:authorize="isAuthenticated()"><form th:action="#{/logout}" th:method="post"><button title="LOGOUT" class="btn" type="submit"><i class="icon-off"></i></button></form></li>
</ul>
Configuration:
http
.authorizeRequests()
.antMatchers("/login", "/", "register",
"/custom-transfers/**", "/about-us", "/information/**", "/support/**").permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers("/admin").hasRole(UserRoleEnum.ADMIN.name())
.antMatchers("/**").authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY)
.passwordParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY)
.defaultSuccessUrl("/user/profile")
//TODO validation page
.failureForwardUrl("/login")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
ISSUE
It all works when the user first logs in. When logouts and log in again, Thymleaf shows a TemplateInputException.
Exception:
ERROR 26304 --- [nio-8080-exec-9] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/login] and exception [An error happened during template parsing (template: "class path resource [templates/login.html]")] as the response has already been committed. As a result, the response may have the wrong status code.
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/login.html]")
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:241) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:666) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:366) ~[thymeleaf-spring5-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:190) ~[thymeleaf-spring5-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1400) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1145) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1084) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.10.jar:5.3.10]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.53.jar:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.10.jar:5.3.10]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.53.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.53.jar:9.0.53]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:121) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115) ~[spring-security-web-5.5.2.jar:5.5.2]
I tried placing sec:authorize="!isAuthenticated()" outside in a <div>, tried to pass a boolean to the template with a RedirectAttribute and Model, but I can't still fix the issue.
It works when I add:
<li class="top-links-item" sec:authorize="!isAuthenticated()">TEST</li>
so I assume is because I use sec:authorize for a login page, which is managed by Spring Security. Could there be any relation?
UPDATE I found that after logout the Cookie destroys and it doesn't creates a new one for a second login. Also the issue is only of logout form in the html when I add sec:authorize=isAuthenticated(). Therefore the problem is connected to the logout instead of the login page. Why could this happen?
This occurs only when I use isAuthenticated() in Thyemleaf.
What am I doing wrong?
I would really appreciate your help.
Thank you.

I think that what happens here is the following:
Your logout form is managed by Thymeleaf which means that under the hood Thymeleaf will try to create a hidden field with the CSRF token inside. This normally works and it requires existing or new session.
The problem is that Thymeleaf is designed to minimize latencies by producing template output as your template is being processed. Thus when a page is longer in size (as it is in your case) by the time when the engine reaches your logout <form> tag some of the output MAY HAVE BEEN streamed to the client and the server starts to consider your response as committed. Once the response is marked as committed Thymeleaf will fail to access/create a session. I guess this is an architectural constraint.
So I'm not sure what exactly to advise you here. Maybe try to output the logout button earlier in your templates or optionally try to eagerly initialize the CSRF token. This setting in HttpSecurity will possibly do it:
.and()
.csrf()
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())

Related

Serving static content results in Request method 'GET' not supported

I want to serve static content from my static directory in my project resources, but I get error: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported.
I have added configured Spring Security to ignore requests starting with "/static/". I have added resource handler to WebMvcConfig to look for the static content in various different locations, I have tried removing the resource handler altogether.
I have tried adding permitAll() to the "/static/" path.
I have googled the error message and various combinations of it to find solutions. This is where I came up with the Spring Security and WebMvcConfigs. Just that these did not solve the problem for me.
Finally I have tried placing static files into different locations. The directory structure is as follows:
\resources\static\hello.css
\resources\static\css\hello.css
\resources\templates\home.html
\resources\templates\error.html
Thymeleaf works correctly, taking the templates from the templates directory.
The WebMvcConfig:
// Config Thymeleaf
private static final String VIEWS = "classpath:templates/";
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
private ISpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix(VIEWS);
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
// Enable static resources
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("/resources/static/", "classpath:static/", "/static/", "classpath:resources/static");
}
The Spring Security config:
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**");
}
I expect to see my css file, but instead I see the error page generated by exception handler with the message "Request method 'GET' not supported"
Here is the debug trace from the point I request the css file:
2019-06-07 11:30:29.694 DEBUG 5398 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/static/css/hello.css'; against '/static/**'
2019-06-07 11:30:29.694 DEBUG 5398 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : /static/css/hello.css has an empty filter list
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:200)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:419)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:365)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:65)
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:401)
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1232)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1015)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:209)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:836)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1747)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
After hours of debugging I found the reason static content was not served!
It was because of a controller with mapping without the path:
#PostMapping()
After adding a path to the mapping, the configuration above works as it should.
Very weird that it messed up the serving of static files!
Big thanks to #Ganesh for his tip in Spring Boot not serving static content
thanks #Avaruuskadetti you saved hours of my time
DEBUG:
package org.springframework.web.servlet >>>> HandlerExecutionChain
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping hm = (HandlerMapping)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

Spring-data-rest-webmvc: Why cannot the ObjectError be rendered directly when handling the exception - MethodArgumentNotValidException?

In light of the DefaultErrorAttributes, where ObjectError can be used to express the error details, I intended to utilize ObjectError to express the error details when handling the exception - MethodArgumentNotValidException` in the implementation of a REST API with pure Spring 5.
I handled MethodArgumentNotValidException by extends ResponseEntityExceptionHandler:
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.WebUtils;
#RestControllerAdvice
public class RestExceptionsHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, status.value(), RequestAttributes.SCOPE_REQUEST);
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, RequestAttributes.SCOPE_REQUEST);
BindingResult re = ((MethodArgumentNotValidException) ex).getBindingResult();
return new ResponseEntity<>(re.getAllErrors().get(0), headers, status);
}
}
In the code above, the BindingResult instance eventually called getAllErrors() to return a List of ObjectErrors. I just tried to get the first element from the List of ObjectErrors, and then presumably would render the ObjectError in JSON format for testing purpose. However, when running it with Jetty, even though the MethodArgumentNotValidException is handled in the mentioned exception handler - handleMethodArgumentNotValid (this is proved with debugger), the JSON for ObjectError could not be rendered in JSON format, instead the default html error page of the Jetty web container is displayed
The error html code generated from Jetty is the following:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 400 Bad Request</title>
</head>
<body>
<h2>HTTP ERROR 400</h2>
<p>Problem accessing /masterdata/cities. Reason:
<pre> Bad Request</pre>
</p>
<h3>Caused by:</h3>
<pre>org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.rx.entity.City com.rx.rest.controller.CityController.createCity(com.rx.entity.City): [Field error in object &apos;city&apos; on field &apos;city&apos;: rejected value [null]; codes [NotNull.city.city,NotNull.city,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [city.city,city]; arguments []; default message [city]]; default message [city cannot be empty]]
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:138)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:166)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:859)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1623)
at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:214)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:540)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:146)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:257)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1588)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1345)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:480)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1557)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1247)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:220)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:126)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:502)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
at java.lang.Thread.run(Thread.java:748)
</pre>
<hr>
Powered by Jetty:// 9.4.14.v20181114
<hr/>
</body>
</html>
Quetion: Why cannot the ObjectError be rendered directly when handling the exception - MethodArgumentNotValidException?

Spring-Boot and DialogFlow : Why I receive an anonymous request on my webhook defined in DialogFlow?

I'm creating a web application with Spring Boot that defines a REST API accessible via OAuth2 authentication for use with Google Assistant
I configured DialogFlow (Webhook fulfillment configured with the URL to the endpoint of my REST API)
I configured Actions on Google: I configured the Account Linking section with OAuth information (client ID, client Secret, Authorization URL, Token URL, Scopes ...)
I tested my application with my smartphone via the Google Home application.
It tells me: "Before I can use "My App", I need to associate your "My App" account with Google. Do you agree with that?"
I say, "Yes."
I then have access to my web application for OAuth authentication.
I validate, and it says: "Perfect! Your "My App" account is now connected to Google"
Then I write the sentence "Turn on my TV", it then calls the fulfillment webhook that calls my REST API.
Only I'm getting a request that doesn't seem right. I have an error indicating that the user is anonymous. It is as if the access-token had not been transmitted in the'Authorization' header.
I can't find a way to get the complete request (Header + Body) that is sent.
I also tested on the Actions on Google Simulator but I only see the request body, not the headers. I looked at Google's logs but I don't have any more details.
Here are the logs :
19:47:41.263 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'POST /api/fulfillment' doesn't match 'GET /**
19:47:41.264 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request '/api/fulfillment' matched by universal pattern '/**'
19:47:41.264 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /api/fulfillment; Attributes: [#oauth2.throwOnError(#oauth2.hasScope('write'))]
19:47:41.264 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#2629f42a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#ffffc434: RemoteIpAddress: 35.184.134.60; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
19:47:41.268 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Insufficient scope for this resource
at org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods.throwOnError(OAuth2SecurityExpressionMethods.java:72)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:120)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:111)
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:54)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:391)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:89)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:116)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:306)
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:26)
at org.springframework.security.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.java:52)
at org.springframework.security.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.java:33)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:63)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90)
at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:77)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:613)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.security.oauth2.common.exceptions.InsufficientScopeException: Insufficient scope for this resource
at org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods.throwOnError(OAuth2SecurityExpressionMethods.java:71)
... 81 common frames omitted
19:47:41.271 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Calling Authentication entry point.
19:47:41.275 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.o.p.e.DefaultOAuth2ExceptionRenderer - Written [error="unauthorized", error_description="Full authentication is required to access this resource"] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#1ff3a97]
19:47:41.276 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
I tested on "OAuth 2.0 Playground" (https://developers.google.com/oauthplayground), and it works perfectly!
Is this due to the fact that my App hasn't been released and I'm still in test mode?
Anybody got any ideas?
This is because Google isn't sending the bearer token in the Authorization header (for various reasons, but at least partially because some services are using this to authorize the service - not the user of the service). It sends it as part of the JSON body.
If you are using the Action SDK, you'll find this in user.accessToken. In Dialogflow, this will be under originalDetectIntentRequest.payload.user.accessToken.
Following Prisoner's answer, I created a custom org.springframework.security.oauth2.provider.authentication.TokenExtractor to manage the access-token present in the request body.
Here is the Kotlin code:
class BodyTokenExtractor : BearerTokenExtractor() {
private val logger = LogFactory.getLog(BodyTokenExtractor::class.java)
override fun extractToken(request: HttpServletRequest): String? {
var token: String? = null
if (HttpMethod.POST.matches(request.method)) {
token = extractBodyToken(request)
}
if (token == null) {
logger.debug("Token not found in body. Trying request headers.")
token = super.extractToken(request)
} else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE)
}
return token
}
/**
* Extract the OAuth token from the request body.
*
* #param request The request.
* #return The token, or null if no OAuth authorization header was supplied.
*/
protected fun extractBodyToken(request: HttpServletRequest): String? {
try {
val requestBody = IOUtils.toString(request.reader)
val request = JacksonFactory().fromString(requestBody, Map::class.java)
val originalDetectIntentRequest = request["originalDetectIntentRequest"] as Map<String, Object>
if (originalDetectIntentRequest != null) {
val payload = originalDetectIntentRequest["payload"] as Map<String, Object>
if (payload != null) {
val user = payload["user"] as Map<String, Object>
if (user != null) {
return user["accessToken"] as String?
}
}
}
} catch (e: IOException) {
logger.debug("An error occurred while reading the request body: " + e.message, e)
}
return null
}
}
This class is then called in the Resource Server :
#Configuration
#EnableResourceServer
class OAuthResourceServerConfig() : ResourceServerConfigurerAdapter() {
...
#Throws(Exception::class)
override fun configure(resources: ResourceServerSecurityConfigurer) {
resources.resourceId(resourceId).tokenStore(tokenStore()).tokenExtractor(tokenExtractor())
}
private fun tokenStore(): TokenStore {
return JdbcTokenStore(dataSource)
}
private fun tokenExtractor(): TokenExtractor {
return BodyTokenExtractor()
}
...
}
It is important to define a custom filter to allow to read multiple times the request body :
#Bean
fun multiReadFilter(): FilterRegistrationBean<*> {
val registrationBean = FilterRegistrationBean<CachedRequestWrapperFilter>()
val multiReadRequestFilter = CachedRequestWrapperFilter()
registrationBean.filter = multiReadRequestFilter
registrationBean.order = SecurityProperties.DEFAULT_FILTER_ORDER - 2
registrationBean.urlPatterns = Arrays.asList("/api/*")
return registrationBean
}
class CachedRequestWrapperFilter : Filter {
#Throws(ServletException::class)
override fun init(config: FilterConfig) {
// nothing goes here
}
#Throws(java.io.IOException::class, ServletException::class)
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
val requestWrapper = CachedHttpServletRequest(request as HttpServletRequest)
// Pass request back down the filter chain
chain.doFilter(requestWrapper, response)
}
override fun destroy() {
/* Called before the Filter instance is removed from service by the web container*/
}
}
public class CachedHttpServletRequestextends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedContent;
public CachedHttpServletRequest(HttpServletRequest request) throws IOException {
// Read the request body and populate the cachedContent
}
#Override
public ServletInputStream getInputStream() throws IOException {
// Create input stream from cachedContent
// and return it
}
#Override
public BufferedReader getReader() throws IOException {
// Create a reader from cachedContent
// and return it
}
}
There are plenty of examples explaining how to wrap the request and read multiple times.
Example: http://www.myjavarecipes.com/tag/how-to-read-request-twice/
By doing that, it works perfectly!

Disabling Spring MVC view resolution for a REST only application

I would like to disable Spring MVC view resolution for my application.
My application only uses Thymeleaf for mail templates and Spring MVC for the REST API. It does not serve Thymleaf pages.
I have configured the following property in my application.properties:
spring.thymeleaf.enabled=false
But I still get errors such as:
20:28:51.851 [http-nio-8080-exec-10] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet dispatcherServlet threw exception
javax.servlet.ServletException: Circular view path [error]: would dispatch back to the current handler URL [/error] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
at org.springframework.web.servlet.view.InternalResourceView.prepareForRendering(InternalResourceView.java:205)
at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:145)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1257)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1037)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:980)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:726)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:471)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:394)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:311)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:395)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:254)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:177)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:784)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:802)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1410)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
How can I use Thymeleaf whilst disabling view resolution for Spring MVC?
edit: Actually the only view I am serving is the SPA's index.html as follows:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")//
.addResourceLocations("classpath:/")//
.setCachePeriod(ONE_YEAR);
registry.addResourceHandler("/")//
.addResourceLocations("classpath:/index.html")//
.setCachePeriod(0);
}
I think problem here is, when spring encounters an error, it is forwarding "user" to /error page. Try adding following in your properties.
spring.resources.add-mappings=false
You probably have a controller which returns some string which cannot be resolved by Spring View resolver. If you want to return JSON object use #RestController annotation for your class
MainController.java
#RestController
public class MainController {
#GetMapping("/")
public TestResponseDto greeting(/*here can be any params*/) {
System.out.println("here");
return new TestResponseDto();
}
}
TestResponse.java
public class TestResponseDto {
public String response = "test";
}

Spring Security exclude url patterns in security annotation configurartion

I have spring web application with Spring security configured using java config approach. I want to exclude some URL patterns from authentication(eg: static resources etc..). I have done this earlier with spring security xml config but couldn't figure out with java config as adding antmatchers doesn't help.
The following is my code added in security config class extending WebSecurityConfigurerAdapter
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/authFailure")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.and()
.authenticationProvider(_provider)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(authFilter())
.addFilterAfter(executionContextFilter(),
TokenBasedSecurityFilter.class).csrf().disable();
}
The spring security version that I use is 3.2.0.
Edit:
The stacktrace that I got while hitting the excluded URL,
org.springframework.security.authentication.AuthenticationServiceException: Authorization Header is not available in the request
at com.inventory.ricemill.tba.spring.TokenBasedSecurityFilter.doFilter(TokenBasedSecurityFilter.java:59)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
Apr 01, 2014 10:18:41 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [Inventory] in context with path [/ricemill] threw exception [Request processing failed; nested exception is org.springframework.security.authentication.AuthenticationServiceException: Authorization Header is not available in the request] with root cause
org.springframework.security.authentication.AuthenticationServiceException: Authorization Header is not available in the request
at com.inventory.ricemill.tba.spring.TokenBasedSecurityFilter.doFilter(TokenBasedSecurityFilter.java:59)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
The request goes through the filters registered in spring security filter chain, while it shouldn't as the request is ignored with antmatcher
Found the solution in Spring security examples posted in Github.
WebSecurityConfigurerAdapter has a overloaded configure message that takes WebSecurity as argument which accepts ant matchers on requests to be ignored.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/authFailure");
}
See Spring Security Samples for more details
Where are you configuring your authenticated URL pattern(s)? I only see one uri in your code.
Do you have multiple configure(HttpSecurity) methods or just one? It looks like you need all your URIs in the one method.
I have a site which requires authentication to access everything so I want to protect /*. However in order to authenticate I obviously want to not protect /login. I also have static assets I'd like to allow access to (so I can make the login page pretty) and a healthcheck page that shouldn't require auth.
In addition I have a resource, /admin, which requires higher privledges than the rest of the site.
The following is working for me.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/healthcheck**").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.antMatchers("/**").access("hasRole('ROLE_USER')")
.and()
.formLogin().loginPage("/login").failureUrl("/login?error")
.usernameParameter("username").passwordParameter("password")
.and()
.logout().logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
NOTE: This is a first match wins so you may need to play with the order.
For example, I originally had /** first:
.antMatchers("/**").access("hasRole('ROLE_USER')")
.antMatchers("/login**").permitAll()
.antMatchers("/healthcheck**").permitAll()
Which caused the site to continually redirect all requests for /login back to /login. Likewise I had /admin/** last:
.antMatchers("/**").access("hasRole('ROLE_USER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
Which resulted in my unprivledged test user "guest" having access to the admin interface (yikes!)
When you say adding antMatchers doesnt help - what do you mean? antMatchers is exactly how you do it. Something like the following should work (obviously changing your URL appropriately):
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/authFailure").permitAll()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
If you are still not having any joy, then you will need to provide more details/stacktrace etc.
Details of XML to Java config switch is here
specifying the "antMatcher" before "authorizeRequests()" like below will restrict the authenticaiton to only those URLs specified in "antMatcher"
http.csrf().disable()
.antMatcher("/apiurlneedsauth/**").authorizeRequests().
And in more modern way it's should looks like:
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/authFailure");
}
I found all of the solutions above outdated...
This is the solution I ended up with
#Configuration
#EnableWebSecurity/*(debug = true)*/
#RequiredArgsConstructor
#EnableMethodSecurity
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// #formatter:off
return http
.csrf()
.disable()
.headers().frameOptions().disable()
.and()
// Enable CORS at Spring Security level
.cors()
.and()
.authorizeHttpRequests()
.requestMatchers("/login/**", "/api/noAuth/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter(new UserDetailsConverter()))
.and()
.and().build();
// #formatter:on
}
}
Addition to answers already given here.
There seems to be a lot of confusion about the difference between authentication and authorization.
Spring Security executes authentication first, only then authorization, which makes sense.
This means that it will first execute the authentication filters, followed by verification of the authorized endpoints only after.
To circumvent custom authentication filters, permitAll will not work, because this concerns authorization, and NOT authentication.
Spring Security 5.7.0-M2 deprecated the WebSecurityConfigurerAdapter.
Authentication filters can be ignored using:
#Bean
public WebSecurityCustomizer ignoringCustomizer() {
return (web) -> web.ignoring().requestMatchers("/ignore1", "/ignore2");
}
Or by using antMatchers.

Resources