Search code examples
springspring-mvcspring-security

Can't write correct Spring Security rules for Spring MVC View


I am trying to run new app on Spring Boot 3.1.1 with Spring Security and MVC views and can't figure how to correctly write security rules for this. I have my controller as follows:

@Controller
@RequestMapping("/")
public class StubController {
    
    @GetMapping("test")
    public ModelAndView test() {
        return new ModelAndView("test");
    }
}

I've tried multiple options with security config. Below is not working option (gives 403 error), unless I add /WEB-INF/view/test.jsp:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(requests -> requests
                        .requestMatchers("/login*").permitAll()
                        .requestMatchers("/test").hasAuthority(Permission.TEST.getAuthority()))
                .formLogin(Customizer.withDefaults())
                .logout(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Based on that, I've found that I require two mappings to get it working: to have both "/test" and "/WEB-INF/view/test.jsp" pages listed in requestMatchers (I've set .jsp as mvc views suffix in config for test and /WEB-INF/view/ as a prefix). I made some search and discover there is an interface for the MVC handling in Spring Security, so I've tried it also this way:

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
        MvcRequestMatcher mvcMatcher = new MvcRequestMatcher.Builder(introspector).pattern("/test");

        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(requests -> requests
                        .requestMatchers("/login*").permitAll()
                        .requestMatchers(mvcMatcher).hasAuthority(Permission.TEST.getAuthority()))
                .formLogin(Customizer.withDefaults())
                .logout(Customizer.withDefaults());

        return http.build();
    }

It is not working also the same way as with previous code snippet.

So what am I asking for - how to correctly write rules for this case avoiding wildcards or patterns dublication with ".jsp" suffix? If you can please also explain how it works. I am completely lost at this point as I found very few helpfull sources on this subject, so any help would be greatly appreciated!

update: added trace log (also replace original .html with .jsp for testing and changed description a bit)

2023-06-29T01:47:49.991+03:00  INFO 16428 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-06-29T01:47:49.991+03:00  INFO 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-06-29T01:47:49.991+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.multipart.support.StandardServletMultipartResolver@5dd369cc
2023-06-29T01:47:49.991+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver@20e61bf
2023-06-29T01:47:49.991+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.theme.FixedThemeResolver@1ce7b45c
2023-06-29T01:47:49.991+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected DefaultRequestToViewNameTranslator
2023-06-29T01:47:49.993+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected SessionFlashMapManager
2023-06-29T01:47:49.993+03:00 DEBUG 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2023-06-29T01:47:49.993+03:00  INFO 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
2023-06-29T01:47:50.005+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@7fddfcb1, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7a03309b, org.springframework.security.web.context.SecurityContextHolderFilter@6f3fb6bb, org.springframework.security.web.header.HeaderWriterFilter@6edddb96, org.springframework.security.web.authentication.logout.LogoutFilter@91b5ed5, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4bd9d049, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3c4a5564, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@16073dd1, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@46befeb1, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@19e01051, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4df8278e, org.springframework.security.web.access.ExceptionTranslationFilter@4653896, org.springframework.security.web.access.intercept.AuthorizationFilter@502b2f45]] (1/1)
2023-06-29T01:47:50.006+03:00 DEBUG 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing GET /test
2023-06-29T01:47:50.007+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/13)
2023-06-29T01:47:50.007+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/13)
2023-06-29T01:47:50.010+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/13)
2023-06-29T01:47:50.011+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/13)
2023-06-29T01:47:50.014+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (5/13)
2023-06-29T01:47:50.014+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking UsernamePasswordAuthenticationFilter (6/13)
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DefaultLoginPageGeneratingFilter (7/13)
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DefaultLogoutPageGeneratingFilter (8/13)
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] .w.a.u.DefaultLogoutPageGeneratingFilter : Did not render default logout page since request did not match [Ant [pattern='/logout', GET]]
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (9/13)
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2023-06-29T01:47:50.015+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (10/13)
2023-06-29T01:47:50.017+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (11/13)
2023-06-29T01:47:50.017+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (12/13)
2023-06-29T01:47:50.017+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking AuthorizationFilter (13/13)
2023-06-29T01:47:50.017+03:00 TRACE 16428 --- [nio-8080-exec-1] estMatcherDelegatingAuthorizationManager : Authorizing SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@268cfcc4]
2023-06-29T01:47:50.029+03:00 TRACE 16428 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to ru.trainithard.learningapp.controller.StubController#test()
2023-06-29T01:47:50.031+03:00 TRACE 16428 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to ru.trainithard.learningapp.controller.StubController#test()
2023-06-29T01:47:50.032+03:00 TRACE 16428 --- [nio-8080-exec-1] estMatcherDelegatingAuthorizationManager : Checking authorization on SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@268cfcc4] using AuthorityAuthorizationManager[authorities=[TEST]]
2023-06-29T01:47:50.034+03:00 TRACE 16428 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=ru.trainithard.learningapp.model.User@54391ce, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=B02D0007163E6B581F541EA6F79511C1], Granted Authorities=[MANAGE_USERS, TEST]]] from SPRING_SECURITY_CONTEXT
2023-06-29T01:47:50.034+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Did not set SecurityContextHolder since already authenticated UsernamePasswordAuthenticationToken [Principal=ru.trainithard.learningapp.model.User@54391ce, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=B02D0007163E6B581F541EA6F79511C1], Granted Authorities=[MANAGE_USERS, TEST]]
2023-06-29T01:47:50.035+03:00 DEBUG 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Secured GET /test
2023-06-29T01:47:50.037+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : GET "/test", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2023-06-29T01:47:50.039+03:00 TRACE 16428 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to ru.trainithard.learningapp.controller.StubController#test()
2023-06-29T01:47:50.054+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.method.HandlerMethod             : Arguments: []
2023-06-29T01:47:50.056+03:00 TRACE 16428 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerAdapter : Applying default cacheSeconds=-1
2023-06-29T01:47:50.065+03:00 DEBUG 16428 --- [nio-8080-exec-1] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, */*;q=0.8, application/signed-exchange;v=b3;q=0.7]
2023-06-29T01:47:50.065+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'test'; URL [/WEB-INF/view/test.jsp]] 
2023-06-29T01:47:50.065+03:00 DEBUG 16428 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView  : View name 'test', model {}
2023-06-29T01:47:50.068+03:00 DEBUG 16428 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView  : Forwarding to [/WEB-INF/view/test.jsp]
2023-06-29T01:47:50.070+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@7fddfcb1, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7a03309b, org.springframework.security.web.context.SecurityContextHolderFilter@6f3fb6bb, org.springframework.security.web.header.HeaderWriterFilter@6edddb96, org.springframework.security.web.authentication.logout.LogoutFilter@91b5ed5, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4bd9d049, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3c4a5564, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@16073dd1, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@46befeb1, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@19e01051, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4df8278e, org.springframework.security.web.access.ExceptionTranslationFilter@4653896, org.springframework.security.web.access.intercept.AuthorizationFilter@502b2f45]] (1/1)
2023-06-29T01:47:50.070+03:00 DEBUG 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing GET /WEB-INF/view/test.jsp
2023-06-29T01:47:50.070+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/13)
2023-06-29T01:47:50.070+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/13)
2023-06-29T01:47:50.070+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/13)
2023-06-29T01:47:50.071+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/13)
2023-06-29T01:47:50.071+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (5/13)
2023-06-29T01:47:50.071+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
2023-06-29T01:47:50.071+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking UsernamePasswordAuthenticationFilter (6/13)
2023-06-29T01:47:50.071+03:00 TRACE 16428 --- [nio-8080-exec-1] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2023-06-29T01:47:50.071+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DefaultLoginPageGeneratingFilter (7/13)
2023-06-29T01:47:50.071+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DefaultLogoutPageGeneratingFilter (8/13)
2023-06-29T01:47:50.072+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (9/13)
2023-06-29T01:47:50.072+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2023-06-29T01:47:50.072+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (10/13)
2023-06-29T01:47:50.072+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (11/13)
2023-06-29T01:47:50.072+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (12/13)
2023-06-29T01:47:50.072+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking AuthorizationFilter (13/13)
2023-06-29T01:47:50.072+03:00 TRACE 16428 --- [nio-8080-exec-1] estMatcherDelegatingAuthorizationManager : Authorizing SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@268cfcc4]]]
2023-06-29T01:47:50.077+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]] and 4 interceptors
2023-06-29T01:47:50.078+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]] and 4 interceptors
2023-06-29T01:47:50.080+03:00 TRACE 16428 --- [nio-8080-exec-1] estMatcherDelegatingAuthorizationManager : Denying request since did not find matching RequestMatcher
2023-06-29T01:47:50.080+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Did not set SecurityContextHolder since already authenticated UsernamePasswordAuthenticationToken [Principal=ru.trainithard.learningapp.model.User@54391ce, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=B02D0007163E6B581F541EA6F79511C1], Granted Authorities=[MANAGE_USERS, TEST]]
2023-06-29T01:47:50.080+03:00 TRACE 16428 --- [nio-8080-exec-1] o.s.s.w.a.ExceptionTranslationFilter     : Sending UsernamePasswordAuthenticationToken [Principal=ru.trainithard.learningapp.model.User@54391ce, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=B02D0007163E6B581F541EA6F79511C1], Granted Authorities=[MANAGE_USERS, TEST]] to access denied handler since access is denied

----- at this point exception acess denied is thrown -----

Solution

  • Finally, I've found the solution. According documentation after Spring 6.0 Spring Security started to evaluate all servlets requests by default. I've solved the problem adding .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll() row to the security config.