β¬ οΈ Back to Config Overview
Spring Configuration Classes
Overview
Spring @Configuration classes define
beans β managed objects that implement
application behavior. In the Inventory Service, configuration
classes primarily handle:
- Security: OAuth2 login, authorization rules, CORS
- Properties: Loading custom app settings (demo mode, frontend URLs)
- Filters: Request detection (API vs browser)
- SpEL integration: Allowing security expressions to access configuration
Configuration Classes
AppProperties
File:
src/main/java/.../config/AppProperties.java
Purpose: Custom application properties, not part of Spring Framework defaults.
Beans created: None directly; instead, binds properties to a managed object.
Key Properties:
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private boolean isDemoReadonly = false; // Demo read-only mode
private final Frontend frontend = new Frontend();
public static class Frontend {
private String baseUrl = "http://localhost:8081";
private String landingPath = "/auth";
}
}Usage in the codebase:
SecurityConfig: Checksprops.isDemoReadonly()to decide whether to permit unauthenticated GET requestsOAuth2LoginSuccessHandler: Usesprops.getFrontend().getBaseUrl()to redirect after login
Configuration sources:
app:
demo-readonly: ${APP_DEMO_READONLY:true}
frontend:
base-url: ${APP_FRONTEND_BASE_URL:https://localhost:5173}
landing-path: /authSecurityConfig
File:
src/main/java/.../config/SecurityConfig.java
Purpose: Core OAuth2 and method-level security configuration.
Key Beans:
1.
securityFilterChain()
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> {...})
.oauth2Login(oauth2 -> {...})
.logout(logout -> {...})
.sessionManagement(session -> {...});
return http.build();
}What it does:
- Enables CORS (cross-origin requests)
- Configures which endpoints require authentication
- Enables Google OAuth2 login
- Manages sessions with secure cookies
- Supports demo mode read-only access
Related helpers: Uses
SecurityAuthorizationHelper,
SecurityEntryPointHelper,
SecurityFilterHelper
2.
corsConfigurationSource()
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:5173", ...));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowCredentials(true); // Cookies allowed
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}What it does:
- Allows frontend to make cross-origin API calls
- Permits credentials (OAuth2 session cookies)
- Restricts to approved methods and origins
3.
cookieSerializer()
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
serializer.setUseHttpOnlyCookie(true);
serializer.setUseBase64Encoding(true);
return serializer;
}What it does:
- Configures session cookie security (HttpOnly, Base64 encoding)
- Prevents JavaScript access to session tokens
SecuritySpelBridgeConfig
File:
src/main/java/.../config/SecuritySpelBridgeConfig.java
Purpose: Exposes AppProperties
to SpEL (Spring Expression Language) for method-level
security.
Bean:
@Bean("appProperties")
@Primary
public AppProperties appPropertiesPrimary(AppProperties props) {
return props; // Alias for SpEL access
}Usage in annotations:
@PreAuthorize("hasRole('ADMIN') || @appProperties.demoReadonly()")
public void deleteSupplier(String id) {
// Allows deletion if user is ADMIN OR demo mode is enabled
}Why itβs needed:
- SpEL expressions donβt have direct access to autowired properties
- This bean makes
AppPropertiesavailable by name (@appProperties) - Enables declarative security rules that reference configuration
Helper Classes
These are @Component classes (automatically
created beans) that break down security configuration into
manageable pieces.
SecurityAuthorizationHelper
@Component
public class SecurityAuthorizationHelper {
public void configureAuthorization(
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry auth,
boolean isDemoReadonly) {
// 1. Public endpoints
auth.requestMatchers("/actuator/health", "/actuator/info").permitAll();
auth.requestMatchers("/", "/index.html", "/error").permitAll();
auth.requestMatchers("/oauth2/**", "/login/**", "/logout/**").permitAll();
// 2. Inventory & Supplier API (read): authenticated users
auth.requestMatchers(HttpMethod.GET, "/api/inventory", "/api/inventory/**")
.authenticated();
auth.requestMatchers(HttpMethod.GET, "/api/suppliers", "/api/suppliers/**")
.authenticated();
// 3. Inventory & Supplier mutations: USER or ADMIN role required
// Demo mode write protection via @PreAuthorize method-level security
auth.requestMatchers(HttpMethod.POST, "/api/inventory/**")
.hasAnyRole("USER", "ADMIN");
auth.requestMatchers(HttpMethod.PUT, "/api/inventory/**")
.hasAnyRole("USER", "ADMIN");
auth.requestMatchers(HttpMethod.PATCH, "/api/inventory/**")
.hasAnyRole("USER", "ADMIN");
auth.requestMatchers(HttpMethod.DELETE, "/api/inventory/**")
.hasAnyRole("USER", "ADMIN");
auth.requestMatchers(HttpMethod.POST, "/api/suppliers/**")
.hasAnyRole("USER", "ADMIN");
auth.requestMatchers(HttpMethod.PUT, "/api/suppliers/**")
.hasAnyRole("USER", "ADMIN");
auth.requestMatchers(HttpMethod.PATCH, "/api/suppliers/**")
.hasAnyRole("USER", "ADMIN");
auth.requestMatchers(HttpMethod.DELETE, "/api/suppliers/**")
.hasAnyRole("USER", "ADMIN");
// 4. All other /api/** endpoints: authenticated
auth.requestMatchers("/api/**").authenticated();
// 5. Default: everything else authenticated
auth.anyRequest().authenticated();
}
}Key Points:
- URL rules are simple and role-based (no complex SpEL expressions)
- Demo mode write protection is exclusively
at the method level via
@PreAuthorize - READ endpoints are always allowed for authenticated users (including demo users)
- WRITE endpoints check both role
(
hasAnyRole('USER','ADMIN')) AND demo status (!@securityService.isDemo())
SecurityEntryPointHelper
Handles authentication failures:
- API requests β return JSON error
(
401 Unauthorized) - Browser requests β redirect to login page
@Component
public class SecurityEntryPointHelper {
public AuthenticationEntryPoint createApiEntryPoint() {
return (request, response, exception) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
};
}
public AuthenticationEntryPoint createWebEntryPoint(String frontendUrl) {
return (request, response, exception) -> {
response.sendRedirect(frontendUrl + "/login");
};
}
}SecurityFilterHelper
Creates a filter that marks API requests:
@Component
public class SecurityFilterHelper {
public OncePerRequestFilter createApiDetectionFilter() {
return new OncePerRequestFilter() {
protected void doFilterInternal(HttpServletRequest request, ...) {
boolean isApi = request.getRequestURI().startsWith("/api/");
request.setAttribute("IS_API_REQUEST", isApi);
filterChain.doFilter(request, response);
}
};
}
}Why this matters:
- The
SecurityEntryPointHelperuses theIS_API_REQUESTattribute to decide how to handle auth failures - API errors are JSON; web errors redirect to login
Bean Dependency Graph
AppProperties
β
ββ Used by: SecurityConfig
β SecurityAuthorizationHelper
β
ββ Aliased by: SecuritySpelBridgeConfig
β (exposes as @appProperties for SpEL)
SecurityConfig
β
ββ Uses: AppProperties
ββ Uses: SecurityAuthorizationHelper
ββ Uses: SecurityEntryPointHelper
ββ Uses: SecurityFilterHelper
ββ Uses: CustomOAuth2UserService
ββ Uses: CustomOidcUserService
β
ββ Creates: SecurityFilterChain (main bean)
CorsConfigurationSource
CookieSerializer
How Configuration Classes Are Loaded
- Classpath scanning: Spring detects
@Configurationclasses at startup - Bean creation: Methods marked
@Beanare invoked to create beans - Dependency injection: Dependencies (like
AppProperties) are autowired - Bean lifecycle: Beans are stored in the application context and injected where needed
- Property binding:
@ConfigurationPropertiesclasses bind YAML/env values to fields
Key configuration points that activate this:
# In application.yml
spring:
main:
allow-bean-definition-overriding: true # Allow multiple configsTesting Configuration Classes
Test configuration can override beans:
@SpringBootTest(properties = {
"app.demo-readonly=false",
"app.frontend.base-url=http://localhost:3000"
})
public class SecurityConfigTest {
@Autowired
private AppProperties props;
@Test
void testDemoModeDisabledInTest() {
assertThat(props.isDemoReadonly()).isFalse();
}
}Summary
| Class | Purpose | Main Beans |
|---|---|---|
| AppProperties | Custom app settings | (none; used by SpEL) |
| SecurityConfig | OAuth2 & authorization | SecurityFilterChain,
CorsConfigurationSource |
| SecuritySpelBridgeConfig | SpEL integration | appProperties alias |
| SecurityAuthorizationHelper | Authorization rules | (helper; no public beans) |
| SecurityEntryPointHelper | Auth failure handling | (helper; no public beans) |
| SecurityFilterHelper | Request detection | (helper; no public beans) |