소스 검색

Update FormLogin + OTT Sample

Added time-sensitive endpoint
Updated to use new static authority values
Updated to use HttpSecurity customizers
Josh Cummings 17 시간 전
부모
커밋
74935a8003

+ 6 - 1
servlet/spring-boot/java/authentication/mfa/formLogin+ott/README.adoc

@@ -36,9 +36,14 @@ Use this one-time token: 1319c31d-c5e0-4123-9b1f-3ffc34aba673
 ********************************************************
 ----
 
+=== Time-sensitive Endpoints
+
+Navigating to `/profile` is authorized if you have entered your password within the last five minutes.
+Otherwise, you are directed back to the login page.
+
 == Configuring
 
-There are three profiles in this sample; `default`, `custom-pages`, and `elevated-security`.
+There are two profiles in this sample: `default` and `custom-pages`.
 
 `default` is the arrangement described in <<usage>>.
 

+ 0 - 44
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/CustomPagesSecurityConfig.java

@@ -1,44 +0,0 @@
-package example;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-import org.springframework.security.config.annotation.authorization.EnableGlobalMultiFactorAuthentication;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-
-import static org.springframework.security.core.GrantedAuthorities.FACTOR_OTT_AUTHORITY;
-import static org.springframework.security.core.GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY;
-
-@Profile("custom-pages")
-@Configuration(proxyBeanMethods = false)
-@EnableGlobalMultiFactorAuthentication(authorities = { FACTOR_PASSWORD_AUTHORITY, FACTOR_OTT_AUTHORITY })
-public class CustomPagesSecurityConfig {
-
-	@Controller
-	@Profile("custom-pages")
-	static class LoginController {
-		@GetMapping("/auth/{path}")
-		public String auth(@PathVariable("path") String path) {
-			return path;
-		}
-	}
-
-	@Bean
-	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
-		// @formatter:off
-		http
-			.authorizeHttpRequests((authorize) -> authorize
-				.requestMatchers("/auth/**").permitAll()
-				.anyRequest().authenticated()
-			)
-			.formLogin((form) -> form.loginPage("/auth/password"))
-			.oneTimeTokenLogin((ott) -> ott.loginPage("/auth/ott"));
-		// @formatter:on
-		return http.build();
-	}
-
-}

+ 0 - 48
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/DefaultSecurityConfig.java

@@ -1,48 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package example;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-import org.springframework.security.authorization.AuthorizationManagerFactory;
-import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.config.annotation.authorization.EnableGlobalMultiFactorAuthentication;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.web.SecurityFilterChain;
-
-import static org.springframework.security.core.GrantedAuthorities.FACTOR_OTT_AUTHORITY;
-import static org.springframework.security.core.GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY;
-
-@Profile("default")
-@Configuration(proxyBeanMethods = false)
-@EnableGlobalMultiFactorAuthentication(authorities = { FACTOR_PASSWORD_AUTHORITY, FACTOR_OTT_AUTHORITY })
-class DefaultSecurityConfig {
-
-	@Bean
-	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
-		// @formatter:off
-		http
-			.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
-			.formLogin(Customizer.withDefaults())
-			.oneTimeTokenLogin(Customizer.withDefaults());
-		// @formatter:on
-		return http.build();
-	}
-
-}

+ 0 - 41
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/ElevatedSecurityPageSecurityConfig.java

@@ -1,41 +0,0 @@
-package example;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-
-@Profile("elevated-security")
-@Configuration(proxyBeanMethods = false)
-public class ElevatedSecurityPageSecurityConfig {
-
-	@Controller
-	@Profile("elevated-security")
-	static class LoginController {
-		@GetMapping("/auth/{path}")
-		public String auth(@PathVariable("path") String path) {
-			return path;
-		}
-	}
-
-	@Bean
-	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
-		// @formatter:off
-		http
-			.authorizeHttpRequests((authorize) -> authorize
-				.requestMatchers("/auth/**").permitAll()
-				.requestMatchers("/profile").hasAuthority("FACTOR_OTT")
-				.anyRequest().authenticated()
-			)
-			.formLogin((form) -> form.loginPage("/auth/password"))
-			.oneTimeTokenLogin((ott) -> ott.loginPage("/auth/ott"));
-
-		// @formatter:on
-		return http.build();
-	}
-
-}

+ 30 - 0
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/FormLoginConfig.java

@@ -0,0 +1,30 @@
+package example;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Configuration(proxyBeanMethods = false)
+@Controller
+@Profile("custom-pages")
+class FormLoginConfig {
+    static final String PATH = "/auth/password";
+
+    @GetMapping(PATH)
+    String auth() {
+        return "password";
+    }
+
+    @Bean
+    Customizer<HttpSecurity> formLogin() {
+        // @formatter:off
+        return (http) -> http
+            .authorizeHttpRequests((authz) -> authz.requestMatchers(PATH).permitAll())
+            .formLogin((form) -> form.loginPage(PATH));
+        // @formatter:on
+    }
+}

+ 11 - 17
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/FormLoginOttMfaApplication.java

@@ -19,6 +19,7 @@ package example;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.authorization.EnableGlobalMultiFactorAuthentication;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
@@ -26,6 +27,9 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
 
+import static org.springframework.security.core.authority.FactorGrantedAuthority.OTT_AUTHORITY;
+import static org.springframework.security.core.authority.FactorGrantedAuthority.PASSWORD_AUTHORITY;
+
 @SpringBootApplication
 public class FormLoginOttMfaApplication {
 
@@ -33,21 +37,11 @@ public class FormLoginOttMfaApplication {
 		SpringApplication.run(FormLoginOttMfaApplication.class, args);
 	}
 
-	@Controller
-	static class AppController {
-		@GetMapping("/profile")
-		String profile() {
-			return "profile";
-		}
-	}
-
-	@Bean
-	UserDetailsService users() {
-		UserDetails user = User.withDefaultPasswordEncoder()
-			.username("user")
-			.password("password")
-			.authorities("app")
-			.build();
-		return new InMemoryUserDetailsManager(user);
-	}
+    @Controller
+    static class AppController {
+        @GetMapping("/profile")
+        String profile() {
+            return "profile";
+        }
+    }
 }

+ 0 - 49
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/LoggingOneTimeTokenGenerationSuccessHandler.java

@@ -1,49 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package example;
-
-import java.io.IOException;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import org.springframework.security.authentication.ott.OneTimeToken;
-import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
-import org.springframework.stereotype.Component;
-
-@Component
-public class LoggingOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
-
-	private static final String TOKEN_TEMPLATE = """
-		********************************************************
-		
-		Use this one-time token: %s
-		
-		********************************************************""";
-
-	private final Log logger = LogFactory.getLog(this.getClass());
-
-	@Override
-	public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken)
-			throws IOException {
-		this.logger.info(String.format(TOKEN_TEMPLATE, oneTimeToken.getTokenValue()));
-		response.sendRedirect("/login/ott");
-	}
-
-}

+ 31 - 0
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/OttLoginConfig.java

@@ -0,0 +1,31 @@
+package example;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Configuration(proxyBeanMethods = false)
+@Controller
+@Profile("custom-pages")
+class OttLoginConfig {
+    static final String PATH = "/auth/ott";
+
+    @GetMapping(PATH)
+    String auth() {
+        return "ott";
+    }
+
+    @Bean
+    Customizer<HttpSecurity> ottLogin() {
+        // @formatter:off
+        return (http) -> http
+            .authorizeHttpRequests((authz) -> authz.requestMatchers(PATH).permitAll())
+            .oneTimeTokenLogin((ott) -> ott.loginPage(PATH));
+        // @formatter:on
+    }
+
+}

+ 86 - 0
servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/example/SecurityDefaultsConfig.java

@@ -0,0 +1,86 @@
+package example;
+
+import java.io.IOException;
+import java.time.Duration;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.ott.OneTimeToken;
+import org.springframework.security.authorization.AllRequiredFactorsAuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactories;
+import org.springframework.security.authorization.AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.authorization.EnableGlobalMultiFactorAuthentication;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
+
+import static org.springframework.security.core.authority.FactorGrantedAuthority.OTT_AUTHORITY;
+import static org.springframework.security.core.authority.FactorGrantedAuthority.PASSWORD_AUTHORITY;
+
+@Configuration(proxyBeanMethods = false)
+@EnableGlobalMultiFactorAuthentication(authorities = { PASSWORD_AUTHORITY, OTT_AUTHORITY })
+class SecurityDefaultsConfig {
+    @Bean
+    SecurityFilterChain app(HttpSecurity http, AuthorizationManager<Object> passwordIn5m) {
+        http
+            .authorizeHttpRequests((authz) -> authz
+                .requestMatchers("/profile").access(passwordIn5m)
+                .anyRequest().authenticated()
+            )
+            .formLogin(Customizer.withDefaults())
+            .oneTimeTokenLogin(Customizer.withDefaults());
+        return http.build();
+    }
+
+    @Bean
+    AuthorizationManager<Object> passwordIn5m() {
+        return AuthorizationManagerFactories.multiFactor()
+                .requireFactor((f) -> f.passwordAuthority().validDuration(Duration.ofMinutes(5)))
+                .requireFactor((f) -> f.ottAuthority()).build().authenticated();
+    }
+
+    @Bean
+    UserDetailsService users() {
+        return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
+                .username("user")
+                .password("password")
+                .authorities("app")
+                .build());
+    }
+
+    @Bean
+    OneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
+        return new LoggingOneTimeTokenGenerationSuccessHandler();
+    }
+
+    static final class LoggingOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
+
+        private static final String TOKEN_TEMPLATE = """
+		********************************************************
+		
+		Use this one-time token: %s
+		
+		********************************************************""";
+
+        private final Log logger = LogFactory.getLog(this.getClass());
+
+        @Override
+        public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken)
+                throws IOException {
+            this.logger.info(String.format(TOKEN_TEMPLATE, oneTimeToken.getTokenValue()));
+            response.sendRedirect("/login/ott");
+        }
+
+    }
+
+}