How it works
The explanation, with examples you can run.
A typical API configuration
@Configuration@EnableMethodSecurityclass SecurityConfig { @Bean @Order(1) SecurityFilterChain api(HttpSecurity http) throws Exception { return http .securityMatcher("/api/**") .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated()) .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults())) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .csrf(csrf -> csrf.disable()) // stateless API using bearer tokens, not cookies .build(); } @Bean @Order(2) SecurityFilterChain web(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) .build(); }}Two chains, two policies: the API validates JWT bearer tokens without sessions, the rest of the site uses a login form with sessions and CSRF protection on. Disable CSRF only where the browser does not send credentials automatically; cookie-based sessions need it.
What the filters do
SecurityContextHolderFilterloads any existing security context (for example from the session) for this request.- Authentication filters (
BearerTokenAuthenticationFilter,UsernamePasswordAuthenticationFilter,BasicAuthenticationFilter) turn credentials into anAuthenticationvia theAuthenticationManagerand store it in theSecurityContextHolder. AnonymousAuthenticationFiltersets an anonymous identity if nothing else did.ExceptionTranslationFiltercatches security exceptions thrown further down and turns them into a 401 (start authentication) or a 403 (access denied).AuthorizationFilterchecks theauthorizeHttpRequestsrules, first match wins.
Start-up logs the exact list for each chain at DEBUG level for org.springframework.security, which is the quickest way to see what is really configured.
After the filters
@EnableMethodSecurity adds a second line of defence on beans, using the same proxies as @Transactional:
@PreAuthorize("hasRole('ADMIN') or #customerId == authentication.name")public Invoice invoice(String customerId) { ... }And because the context is held per request, a controller can simply ask for it:
@GetMapping("/me")Profile me(@AuthenticationPrincipal Jwt jwt) { return profiles.find(jwt.getSubject()); }Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.