Default Login Form for Secured Methodes in Spring Security

Viewed 27

Maybe I am thinking completely the wrong way. I want to use Spring Security for my web application and only secure some functions of my Controller class without specifying the URLs.

When I include the dependency spring-boot-starter-security everything is secured by default with a side default login form. So far so good.

Now I activate method security with @EnableGlobalMethodSecurity(securedEnabled = true) and mark some of my methods with @Secured("USER").

To have no security as default I define a custom SecurityFilterChain:

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests((requests) -> requests.anyRequest().permitAll())
                .build();
    }

Of course I have a test user defined:

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                        .username("test")
                        .password("test")
                        .roles("USER")
                        .build();

        return new InMemoryUserDetailsManager(user);
    }

I get a 403 Forbidden on the secured methods. And a 404 Not Found when I add this to the SecurityFilterChain:

                .formLogin((form) -> form
                        .loginPage("/login")
                        .permitAll()

Because the default login form is missing.

What do I need to do to get the default login form for the secured methods?

1 Answers

I want to use Spring Security for my web application and only secure some functions of my Controller class without specifying the URLs.

Spring Security's web support is URL-based, so this does seem to contradict it's purpose. You can use only method-based security, but all of the behavior you're missing is expected because the filter chain is not in play. See Spring Security filter chain and Method Security.

To have no security as default I define a custom SecurityFilterChain:

This would not be a best practice. Consider what happens when a developer forgets to add the @Secured or similar annotation. Defense in depth would be a better approach, which is why Spring Security requires every endpoint to be authenticated by default.

I get a 403 Forbidden on the secured methods. And a 404 Not Found when I add this to the SecurityFilterChain:

At the method level, the ExceptionTranslationFilter does not trigger the AuthenticationEntryPoint. This would explain why you don't get the default behavior of a redirect to /login.

When defining a custom login page with http.formLogin().loginPage("/login"), you are responsible for providing a login page. This would explain why you receive a 404 Not Found.

In order to get the default behavior, you need to specify something that causes requests to be processed appropriately by the filter chain prior to method security. For example, if all of your secured endpoints start with /secured, you should add that as an authorization rule:

http.authorizeHttpRequests((requests) -> requests
    .mvcMatchers("/secured/**").authenticated()
    .anyRequest().permitAll()
);

But this still leaves a gap and does not practice defense in depth. So the best configuration would invert the rules and identify only URLs that should be allowed by default, such as static resources.

http.authorizeHttpRequests((requests) -> requests
    .mvcMatchers("/static/**").permitAll()
    .anyRequest().authenticated()
);

In either case, security exceptions would be thrown by the filter chain while processing requests to @Secured endpoints, and so the AuthenticationEntryPoint will be triggered. Keep in mind however that authentication and authorization are related but separate concepts. In Spring Security, it's the ExceptionTranslationFilter that is tying them together in a way that produces the expected user experience.

Related