WebSecurity
WebSecurityConfigurerAdapter
Spring Security
Java
Web Application Security

Correct use of WebSecurity in WebSecurityConfigurerAdapter

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When developers override configure(WebSecurity web) in a WebSecurityConfigurerAdapter, they often use it for the wrong job. The method exists to remove requests from the Spring Security filter chain entirely, which is much more drastic than merely allowing anonymous access. That distinction is the key to using it correctly in legacy Spring Security applications.

What WebSecurity Actually Does

WebSecurityConfigurerAdapter exposes three main customization points:

  • 'configure(HttpSecurity http) defines authorization rules and login behavior.'
  • 'configure(AuthenticationManagerBuilder auth) defines how users are authenticated.'
  • 'configure(WebSecurity web) tells Spring Security to ignore matching requests completely.'

That last point is easy to misuse. If you write web.ignoring().antMatchers("/api/public/**"), those requests skip every security filter. That means no authentication, no authorization decisions, no CSRF checks, no security headers added by Spring Security, and no SecurityContext setup. For static assets, that can be fine. For API endpoints, it is usually the wrong choice.

The Correct Split Between WebSecurity and HttpSecurity

Use WebSecurity for resources that genuinely do not need the filter chain, such as images, stylesheets, JavaScript bundles, or favicon files. Use HttpSecurity for application endpoints, even if they are publicly accessible.

A typical legacy configuration looks like this:

java
1import org.springframework.context.annotation.Configuration;
2import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
3import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4import org.springframework.security.config.annotation.web.builders.WebSecurity;
5import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
6import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
7
8@Configuration
9@EnableWebSecurity
10public class SecurityConfig extends WebSecurityConfigurerAdapter {
11
12    @Override
13    public void configure(WebSecurity web) {
14        web.ignoring().antMatchers(
15            "/assets/**",
16            "/css/**",
17            "/js/**",
18            "/images/**",
19            "/favicon.ico"
20        );
21    }
22
23    @Override
24    protected void configure(HttpSecurity http) throws Exception {
25        http
26            .authorizeRequests()
27                .antMatchers("/", "/login", "/api/public/**").permitAll()
28                .antMatchers("/admin/**").hasRole("ADMIN")
29                .anyRequest().authenticated()
30            .and()
31            .formLogin()
32                .loginPage("/login")
33                .permitAll()
34            .and()
35            .logout()
36                .permitAll();
37    }
38
39    @Override
40    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
41        auth.inMemoryAuthentication()
42            .withUser("admin")
43            .password("{noop}secret")
44            .roles("ADMIN");
45    }
46}

In this version, /api/public/** is still processed by Spring Security, but authorization allows anonymous users through. That is usually what you want, because it preserves consistent behavior for logging, exception handling, and response hardening.

Why permitAll() Is Different From Ignoring

permitAll() means "let this request through after the filter chain evaluates it." Ignoring means "pretend Spring Security does not exist for this request." Those are not equivalent.

Suppose your app serves a public registration endpoint. If you ignore it through WebSecurity, you lose useful framework behavior. If you mark it with permitAll(), the request stays inside the normal processing pipeline and still benefits from the rest of your security setup.

This is especially important for modern applications that rely on:

  • custom exception translation
  • audit logging tied to filters
  • header writers such as cache or frame settings
  • CSRF for browser-submitted forms

For public business endpoints, permitAll() is safer and more maintainable.

A Better Pattern for Static Resources

If your goal is only to make CSS and JavaScript reachable before login, keep the ignore list narrow and obvious. A small ignore list is easier to audit than a broad wildcard such as "/**/public/**", which can accidentally hide sensitive controllers from the filter chain.

You can also express static-resource rules through Spring helpers when available:

java
1import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
2
3@Override
4public void configure(WebSecurity web) {
5    web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
6}

That makes intent clearer and reduces the chance of path mistakes.

Migration Note for Newer Spring Security

WebSecurityConfigurerAdapter is deprecated in newer Spring Security releases. The modern style is to expose a SecurityFilterChain bean and optionally a WebSecurityCustomizer bean. The underlying rule is still the same: ignore only true static resources, and use authorization rules for real endpoints.

Here is the equivalent idea in the component-based style:

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.security.config.Customizer;
4import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
6import org.springframework.security.web.SecurityFilterChain;
7
8@Configuration
9public class ModernSecurityConfig {
10
11    @Bean
12    WebSecurityCustomizer webSecurityCustomizer() {
13        return web -> web.ignoring().antMatchers("/assets/**", "/favicon.ico");
14    }
15
16    @Bean
17    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
18        http
19            .authorizeHttpRequests(auth -> auth
20                .antMatchers("/", "/login", "/api/public/**").permitAll()
21                .anyRequest().authenticated()
22            )
23            .formLogin(Customizer.withDefaults());
24        return http.build();
25    }
26}

Common Pitfalls

The most common mistake is putting public controller routes in web.ignoring(). That bypasses all security filters and often creates behavior that is inconsistent with the rest of the application.

Another common problem is using very broad path patterns for static resources. A pattern that is too loose can accidentally expose endpoints that were supposed to stay protected.

Developers also forget that examples using {noop} passwords are only for demos. In a real application, use a password encoder such as BCrypt and store hashed passwords.

Finally, many teams are still copying WebSecurityConfigurerAdapter examples into projects that no longer need it. If you are starting new code, prefer the bean-based configuration model and keep the same separation of concerns.

Summary

  • 'configure(WebSecurity web) removes matching requests from the security filter chain.'
  • Use it for static resources, not for normal application or API endpoints.
  • Use permitAll() in HttpSecurity when an endpoint should stay public but still pass through security filters.
  • Keep ignore rules narrow so they are easy to reason about and audit.
  • In newer Spring Security versions, the same principle applies even though WebSecurityConfigurerAdapter is deprecated.

Course illustration
Course illustration

All Rights Reserved.