Careeroza
Real World Example
The ShopEase SecurityConfig
ShopEase has two kinds of clients: the admin web panel (server-rendered, browser, sessions) and the REST API (used by scripts and, later, the React store). Like many real companies, it uses two chains:
package com.shopease.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
@Order(1)
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/products/**", "/api/categories/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/auth/register").permitAll()
.anyRequest().authenticated())
.httpBasic(basic -> basic.realmName("ShopEase API"))
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable());
return http.build();
}
@Bean
@Order(2)
SecurityFilterChain webChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers("/", "/login", "/actuator/health").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/admin/dashboard", false)
.permitAll())
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID"));
return http.build();
}
}Testing Every Rule
Example
A quick smoke test the team runs after every security change:
curl -s -o /dev/null -w "%{http_code} GET /api/products\n" localhost:8080/api/products
curl -s -o /dev/null -w "%{http_code} POST /api/orders (anonymous)\n" -X POST localhost:8080/api/orders
curl -s -o /dev/null -w "%{http_code} POST /api/orders (asha)\n" -u asha:asha123 -X POST localhost:8080/api/orders -H "Content-Type: application/json" -d '{"items":[{"productId":1,"quantity":1}]}'
curl -s -o /dev/null -w "%{http_code} GET /admin/dashboard (browser)\n" -H "Accept: text/html" localhost:8080/admin/dashboard
curl -s -o /dev/null -w "%{http_code} GET /actuator/health\n" localhost:8080/actuator/healthExample output
200 GET /api/products 401 POST /api/orders (anonymous) 201 POST /api/orders (asha) 302 GET /admin/dashboard (browser) 200 GET /actuator/health
Why Each Choice
- Two chains: API clients get clean 401s; browsers get a login page and redirects.
- STATELESS + CSRF off for
/api/**: no cookies, so no CSRF risk and no memory used for sessions. - CSRF on for the web chain: the admin panel uses a session cookie.
- anyRequest().authenticated() last in both chains: new endpoints are private by default.
- Admin-only rules (
hasRole("ADMIN")) and JWT for the React store come in the medium level.