Spring Security Basics
Spring Security protects your application. Authentication answers who are you, and authorization answers what may you do. Adding the starter secures every endpoint immediately, and you then configure the rules you need.
Default behavior
Just by adding spring-boot-starter-security, all endpoints require login. Spring Boot creates a user named user with a random password printed in the console at startup. You can set your own for testing:
spring.security.user.name=admin
spring.security.user.password=secret123How the security filter chain works
HTTP request
▼
SecurityFilterChain (a chain of servlet filters)
├─ SecurityContextHolderFilter loads the current user (if any)
├─ CorsFilter / CsrfFilter protection against browser attacks
├─ UsernamePasswordAuthenticationFilter / BasicAuthenticationFilter / BearerTokenAuthenticationFilter
│ └─ AuthenticationManager ─► AuthenticationProvider ─► UserDetailsService + PasswordEncoder
├─ ExceptionTranslationFilter 401 or 403 responses
└─ AuthorizationFilter rules: permitAll, hasRole, authenticated
▼
DispatcherServlet ─► Controller| Part | Job |
|---|---|
| SecurityFilterChain | Your bean that defines which URLs need which access |
| AuthenticationManager | Checks the credentials |
| UserDetailsService | Loads a user (name, password hash, roles) from memory or a database |
| PasswordEncoder | Hashes and checks passwords. Use BCrypt |
| SecurityContextHolder | Stores the authenticated user for the current request |
Security configuration
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // OK for stateless APIs, not for browser forms
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/actuator/health").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, "/api/products/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated())
.httpBasic(withDefaults());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService users(PasswordEncoder encoder) {
UserDetails user = User.withUsername("ram").password(encoder.encode("ram123")).roles("USER").build();
UserDetails admin = User.withUsername("admin").password(encoder.encode("admin123")).roles("ADMIN").build();
return new InMemoryUserDetailsManager(user, admin);
}
}Order matters: rules are checked from top to bottom, and the first match wins. Put specific rules first and anyRequest() last.
Method security
@Service
public class ReportService {
@PreAuthorize("hasRole('ADMIN')")
public String secretReport() { return "top secret"; }
@PreAuthorize("#username == authentication.name") // users may read only their own data
public String profile(String username) { return "profile of " + username; }
}| Term | Meaning |
|---|---|
| Role | Coarse group such as ADMIN. Stored as authority ROLE_ADMIN |
| Authority | Fine-grained permission such as products:write |
| hasRole('ADMIN') | Checks the authority ROLE_ADMIN (prefix is added automatically) |
| hasAuthority('ROLE_ADMIN') | Checks the exact authority text |
Users from a database
@Service
public class DbUserDetailsService implements UserDetailsService {
private final UserRepository users;
public DbUserDetailsService(UserRepository users) { this.users = users; }
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AppUser u = users.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("No user " + username));
return User.withUsername(u.getUsername())
.password(u.getPasswordHash()) // must be a BCrypt hash
.roles(u.getRole())
.build();
}
}Password storage rules
- Never store plain text passwords. Store a BCrypt (or Argon2) hash.
- BCrypt adds a random salt and is slow on purpose, which protects against brute force.
- The stored value looks like $2a$10$..., and encoder.matches(raw, hash) checks a login.
- PasswordEncoderFactories.createDelegatingPasswordEncoder() stores an id like {bcrypt} so you can change the algorithm later.
CSRF
CSRF is an attack where another website makes the browser send a request to your site with the user's session cookie. Keep CSRF protection enabled for applications that use browser sessions and forms. For stateless REST APIs that use a Bearer token in a header (not a cookie), it is safe to disable.
Where to go deeper
Spring Security has its own complete course on this site with the default configuration explained line by line, filter chain internals, authentication and authorization, CSRF and CORS, JWT (fundamentals, fayde and nuksan, implementation, hardening and attacks), OAuth2 and Keycloak, testing and a production checklist.
- Default behavior: /springsecurity/fundamentals/default-security-configuration/
- Architecture: /springsecurity/fundamentals/security-architecture/
- JWT deep dive: /springsecurity/jwt/jwt-fundamentals/
- Checklist: /springsecurity/production/security-checklist/
Session or token: quick decision
| Your application | Choose |
|---|---|
| Server-rendered pages (Thymeleaf), one backend | Form login + session cookie + CSRF protection |
| SPA + your own API on the same site | Session cookie (HttpOnly, SameSite) or a backend-for-frontend |
| Mobile app, many services, third parties | OAuth2 / OIDC with short-lived JWT access tokens + refresh tokens |
| Service to service | OAuth2 client credentials or mTLS |
Five mistakes seen in almost every first Spring Security setup
- Disabling CSRF and permitting everything to make an error disappear, then forgetting to undo it.
- Putting anyRequest().permitAll() before specific rules (the first match wins).
- hasRole('ADMIN') with an authority stored as ADMIN instead of ROLE_ADMIN.
- Checking login but not ownership of the object (IDOR).
- Using plain text or fast hashes for passwords instead of BCrypt through PasswordEncoder.
Common mistake: Disabling CSRF and allowing everything with permitAll while testing, then forgetting to remove it. Review the security rules before every release.
Interview questions
What is the difference between authentication and authorization?
Authentication proves who you are (login). Authorization decides what you can do (roles and permissions).
Why did WebSecurityConfigurerAdapter disappear?
It was deprecated in Spring Security 5.7 and removed in 6. You now define a SecurityFilterChain bean and use the lambda DSL.
hasRole or hasAuthority?
hasRole adds the ROLE_ prefix for you. hasAuthority checks the exact text. Pick one style and use it everywhere.
Where can I learn the exact default filter chain?
On the Spring Security course page for the default security configuration: it lists all default filters in order and what each one does.