{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihfwbuucrvqthnmdyv3dk6wbzk2vy325lpguqfuszojfwvy3zfo3y",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3molyucbmawd2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreifpevwqgczrbtdtwiefrbctgnxref3u5wy2ubvirgghrddz554bhq"
},
"mimeType": "image/webp",
"size": 444660
},
"path": "/jamilxt/spring-security-7-mfa-modular-config-and-what-breaks-l1b",
"publishedAt": "2026-06-18T22:50:38.000Z",
"site": "https://dev.to",
"tags": [
"java",
"springboot",
"security",
"springio2026",
"YouTube",
"Spring Security What's New (7.0)",
"Migrating to Spring Security 7",
"Spring AI MCP Security Project",
"@EnableMultiFactorAuthentication",
"@Bean",
"@HttpServiceExchange",
"@GetExchange",
"@RequestParam"
],
"textContent": "Spring Security 7 dropped at Spring I/O 2026. Daniel Erno from the Spring Security team gave a talk covering the biggest changes. Here's what matters if you're building Java apps with Spring Boot.\n\n## Multi-Factor Authentication (MFA) — The Big One\n\nThis feature was requested 12 years ago (issue #2603, opened November 2013). It finally shipped.\n\n**What it does:** You can now enforce MFA at the application level or per-endpoint. Spring Security tracks which authentication factors each user has completed and when they authenticated.\n\n**Key classes:**\n\n * `FactorGrantedAuthority` — records what factor you used (password, one-time token, etc.) and when you used it\n * `@EnableMultiFactorAuthentication` — enables MFA across your app\n * `AllRequiredFactorsAuthorizationManager` — combine multiple MFA rules\n\n\n\n**Site-wide MFA example:**\n\n\n\n @EnableMultiFactorAuthentication(authorities = {\n FactorGrantedAuthority.PASSWORD_AUTHORITY,\n FactorGrantedAuthority.OTT_AUTHORITY\n })\n\n\nNow every protected endpoint requires both password login AND a one-time token.\n\n**Per-endpoint MFA (more practical):**\n\n\n\n @Bean\n SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n var mfa = new AllRequiredFactorsAuthorizationManager.Builder()\n .requiredFactor(FactorGrantedAuthority.PASSWORD_AUTHORITY)\n .requiredFactor(FactorGrantedAuthority.OTT_AUTHORITY)\n .build();\n\n http.authorizeHttpRequests(authZ ->\n authZ\n .requestMatchers(\"/public.html\").permitAll()\n .requestMatchers(\"/admin.html\").access(mfa.hasRole(\"admin\"))\n .anyRequest().authenticated()\n );\n return http.build();\n }\n\n\n**Time-based rules:** You can require that an authentication is recent. For example, force users to re-authenticate before changing their password:\n\n\n\n var recentPassword = AllRequiredFactorsAuthorizationManager.builder()\n .requiredFactor(FactorGrantedAuthority.PASSWORD_AUTHORITY, Duration.ofMinutes(5))\n .build();\n\n http.authorizeHttpRequests(authZ ->\n authZ.requestMatchers(\"/change-password\")\n .access(recentPassword)\n );\n\n\nIf a user logged in 30 minutes ago, they get redirected back to login before accessing the password change page.\n\n**Why this matters:** Before Spring Security 7, you had to implement MFA yourself or use a third-party library. Now it's built into the framework.\n\n## Modular Configuration — Stop Replacing Boot Defaults\n\n**The old problem:** When you created a `SecurityFilterChain` bean, you replaced Spring Boot's entire security configuration. You had to manually add form login, session management, CSRF protection, and OAuth2 defaults. Miss one, and your app breaks.\n\n**The new way:** Instead of a full `SecurityFilterChain` bean, provide a customizer that changes only what you need:\n\n\n\n // OLD: replaces ALL defaults\n @Bean\n SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n http.authorizeHttpRequests(authZ ->\n authZ.requestMatchers(\"/public.html\").permitAll()\n .anyRequest().authenticated()\n );\n return http.build();\n }\n\n // NEW: keeps Boot defaults, only changes what you need\n @Bean\n Customizer<HttpSecurity> httpSecurityCustomizer() {\n return http ->\n http.authorizeHttpRequests(authZ ->\n authZ.requestMatchers(\"/public.html\").permitAll()\n );\n }\n\n\nSpring Boot's form login, CSRF, session management, and OAuth2 Authorization Server defaults stay intact. You only customize the parts you change.\n\n**Targeted customizers:** You can also customize specific configurers without touching the full HTTP security:\n\n\n\n // Change only OAuth2 Authorization Server defaults\n @Bean\n Customizer<OAuth2AuthorizationServerConfigurer> authServerCustomizer() {\n return authz -> authz.oidc(oidc ->\n oidc.providerConfiguration(pc ->\n pc.claims(c -> c.claim(\"conference\", \"Spring I/O 2026\"))\n )\n );\n }\n\n\nThis is cleaner. No more `SecurityFilterChain` beans unless you really need to override everything.\n\n## What Got Removed\n\n**OAuth2 Password Grant — gone.** If you're using password grant type, migrate to authorization code. The OAuth 2.1 spec calls password grant \"legacy.\" Spring Security removed it from the client library.\n\n**`and()` DSL — gone.** The old chain-style configuration with `.and()` is removed. Use lambdas instead:\n\n\n\n // OLD (removed)\n http\n .authorizeHttpRequests(authZ -> authZ\n .requestMatchers(\"/admin\").hasRole(\"ADMIN\")\n .anyRequest().authenticated())\n .and()\n .formLogin();\n\n // NEW (lambda style)\n http\n .authorizeHttpRequests(authZ -> authZ\n .requestMatchers(\"/admin\").hasRole(\"ADMIN\")\n .anyRequest().authenticated())\n .formLogin(Customizer.withDefaults());\n\n\nOpen Rewrite can automate this migration for you.\n\n**Module reorganization:** `AccessDecisionVoterManager` moved to `spring-security-access` package. Kerberos and Authorization Server modules now share the same version number (7.x).\n\n## OAuth2 Changes\n\n**PKCE is now the default.** The Authorization Server enforces Proof Key for Code Exchange. Clients must send a code challenge. If you have legacy clients that can't support PKCE, you can turn it off:\n\n\n\n http.oauth2AuthorizationServer(authz ->\n authz.tokenEndpoint(token -> token\n .requireProofKey(false) // disable PKCE (not recommended)\n )\n );\n\n\n**Dynamic Client Registration.** Authorization Servers can now register clients through a REST endpoint (`/oauth2/register`). This was added mainly for MCP (Model Context Protocol) support, where AI tools like Claude Desktop need to dynamically register themselves as clients.\n\nThe endpoint requires a token with `client.create` scope. Each token can register exactly one client. After registration, the token is discarded.\n\n**Warning:** Don't open this endpoint without access control. The spec authors recommend authenticated registration, not open registration. Open registration is a denial-of-service risk.\n\n## HTTP Service Clients with OAuth2\n\nSpring Framework 7 introduced HTTP Service Clients (declarative, interface-based HTTP clients). Spring Security 7 adds OAuth2 token injection support.\n\n\n\n // Define as an interface\n @HttpServiceExchange(url = \"http://localhost:8090/api/hugo\")\n interface HugoService {\n @GetExchange\n List<String> getNominees(@RequestParam int year);\n }\n\n // Register the bean with OAuth2 support\n @Bean\n HugoService hugoService(HttpServiceProxyFactory factory, OAuth2AuthorizedClientManager manager) {\n return factory.createClient(HugoService.class);\n }\n\n // Enable OAuth2 token injection\n @Bean\n OAuth2RestClientHttpServiceGroupConfigurer oauth2Configurer(\n OAuth2AuthorizedClientManager manager) {\n return OAuth2RestClientHttpServiceGroupConfigurer.fromOAuth2AuthorizedClientManager(manager)\n .clientRegistrationId(\"default-client\");\n }\n\n\nNo more manual `RestClient` request interceptors. The token injection happens automatically.\n\n## MCP Security\n\nModel Context Protocol (MCP) gets security support through Spring AI, not Spring Security core. The MCP spec changes too frequently for Spring Security's release cadence.\n\n**Start with Spring Initializr:** Add the MCP server starter with security:\n\n\n\n spring-ai-starter-mcp-server-security\n\n\nFor clients:\n\n\n\n spring-ai-starter-mcp-client-security\n\n\nBoth depend on the Authorization Server for dynamic client registration and token management. The security flow is OAuth2-based: metadata discovery, client registration, authorization code flow, token exchange.\n\n## Should You Upgrade?\n\n**If you're on Spring Security 6:** Plan the migration now. The `and()` DSL removal and `SecurityFilterChain` changes will require code updates. Open Rewrite handles most of the migration automatically.\n\n**If you're starting a new project:** Spring Security 7 is the right choice. MFA is built in, configuration is simpler, and OAuth2 defaults follow current best practices (PKCE enforced, no password grant).\n\n**If you use Kerberos or OAuth2 Authorization Server:** Versions are now aligned with Spring Security 7. No more separate version tracking.\n\n## Sources\n\n * Spring I/O 2026 — Daniel Erno, \"Spring Security 7\" talk (YouTube)\n * Spring Security What's New (7.0)\n * Migrating to Spring Security 7\n * Spring AI MCP Security Project\n\n",
"title": "Spring Security 7: MFA, Modular Config, and What Breaks"
}