Skip to content

Commit b977133

Browse files
committed
Update formLogin+ott To Latest
1 parent 5f22333 commit b977133

File tree

11 files changed

+106
-150
lines changed

11 files changed

+106
-150
lines changed

servlet/spring-boot/java/authentication/mfa/formLogin+ott/build.gradle

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@ dependencies {
2121
implementation 'org.springframework.boot:spring-boot-starter-security'
2222
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
2323
implementation 'org.springframework.boot:spring-boot-starter-web'
24-
implementation 'org.springframework.boot:spring-boot-starter-mail'
2524
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
2625
testImplementation 'org.springframework.boot:spring-boot-starter-test'
2726
testImplementation 'org.springframework.security:spring-security-test'
28-
testImplementation 'com.icegreen:greenmail-junit5:2.0.1'
2927
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
30-
runtimeOnly 'org.springframework.boot:spring-boot-docker-compose'
3128
}
3229

3330
tasks.named('test') {

servlet/spring-boot/java/authentication/mfa/formLogin+ott/compose.yml

Lines changed: 0 additions & 6 deletions
This file was deleted.

servlet/spring-boot/java/authentication/mfa/formLogin+ott/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.example.magiclink;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.function.Supplier;
23+
24+
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
25+
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
26+
import org.springframework.security.authorization.AuthorityAuthorizationManager;
27+
import org.springframework.security.authorization.AuthorizationDecision;
28+
import org.springframework.security.authorization.AuthorizationManager;
29+
import org.springframework.security.authorization.AuthorizationManagers;
30+
import org.springframework.security.authorization.AuthorizationResult;
31+
import org.springframework.security.core.Authentication;
32+
import org.springframework.security.core.GrantedAuthority;
33+
import org.springframework.security.core.authority.AuthorityUtils;
34+
35+
public final class AuthorizationManagerFactory {
36+
37+
private final Collection<String> authorities;
38+
39+
public AuthorizationManagerFactory(String... authorities) {
40+
this.authorities = List.of(authorities);
41+
}
42+
43+
public <T> AuthorizationManager<T> authenticated() {
44+
AuthenticatedAuthorizationManager<T> authenticated = AuthenticatedAuthorizationManager.authenticated();
45+
return AuthorizationManagers.allOf(new AuthorizationDecision(false), this::factors, authenticated);
46+
}
47+
48+
public <T> AuthorizationManager<T> hasAuthority(String authority) {
49+
AuthorityAuthorizationManager<T> authorized = AuthorityAuthorizationManager.hasAuthority(authority);
50+
return AuthorizationManagers.allOf(new AuthorizationDecision(false), this::factors, authorized);
51+
}
52+
53+
private AuthorizationResult factors(Supplier<? extends Authentication> authentication, Object context) {
54+
List<String> authorities = authentication.get()
55+
.getAuthorities()
56+
.stream()
57+
.map(GrantedAuthority::getAuthority)
58+
.toList();
59+
List<String> needed = new ArrayList<>(this.authorities);
60+
needed.removeIf(authorities::contains);
61+
return new AuthorityAuthorizationDecision(needed.isEmpty(), AuthorityUtils.createAuthorityList(needed));
62+
}
63+
64+
}

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import org.springframework.context.annotation.Bean;
44
import org.springframework.context.annotation.Configuration;
55
import org.springframework.context.annotation.Profile;
6-
import org.springframework.security.config.Customizer;
76
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
87
import org.springframework.security.web.SecurityFilterChain;
98
import org.springframework.stereotype.Controller;
@@ -28,19 +27,18 @@ public String ott() {
2827
}
2928

3029
@Bean
31-
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
30+
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthorizationManagerFactory authz) throws Exception {
3231
// @formatter:off
3332
http
34-
.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
35-
.formLogin((form) -> form
36-
.loginPage("/login/form").permitAll()
37-
.factor(Customizer.withDefaults())
38-
)
39-
.oneTimeTokenLogin((ott) -> ott
40-
.loginPage("/login/ott").permitAll()
41-
.factor(Customizer.withDefaults())
42-
);
33+
.authorizeHttpRequests((authorize) -> authorize.anyRequest().access(authz.authenticated()))
34+
.formLogin((form) -> form.loginPage("/login/form").permitAll())
35+
.oneTimeTokenLogin((ott) -> ott.loginPage("/login/ott").permitAll());
4336
// @formatter:on
4437
return http.build();
4538
}
39+
40+
@Bean
41+
AuthorizationManagerFactory authz() {
42+
return new AuthorizationManagerFactory("FACTOR_PASSWORD", "FACTOR_OTT");
43+
}
4644
}

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,23 @@
2828
class DefaultSecurityConfig {
2929

3030
@Bean
31-
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
31+
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthorizationManagerFactory authz) throws Exception {
3232
// @formatter:off
3333
http
34-
.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
35-
.formLogin((form) -> form.factor(Customizer.withDefaults()))
36-
.oneTimeTokenLogin((ott) -> ott.factor(Customizer.withDefaults()));
34+
.authorizeHttpRequests((authorize) -> authorize
35+
.anyRequest().access(authz.authenticated())
36+
)
37+
.formLogin(Customizer.withDefaults())
38+
.oneTimeTokenLogin(Customizer.withDefaults());
3739
// @formatter:on
3840
return http.build();
3941
}
4042

43+
@Bean
44+
AuthorizationManagerFactory authz() {
45+
return new AuthorizationManagerFactory("FACTOR_PASSWORD", "FACTOR_OTT");
46+
}
47+
48+
4149

4250
}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
11
package org.example.magiclink;
22

3-
import java.time.Duration;
4-
53
import org.springframework.context.annotation.Bean;
64
import org.springframework.context.annotation.Configuration;
75
import org.springframework.context.annotation.Profile;
8-
import org.springframework.http.HttpMethod;
9-
import org.springframework.security.config.Customizer;
10-
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
11-
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
126
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13-
import org.springframework.security.config.annotation.web.configurers.MfaConfigurer;
14-
import org.springframework.security.web.DefaultSecurityFilterChain;
157
import org.springframework.security.web.SecurityFilterChain;
16-
import org.springframework.security.web.authentication.AuthenticationFilter;
17-
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
18-
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
198
import org.springframework.stereotype.Controller;
209
import org.springframework.web.bind.annotation.GetMapping;
2110

22-
import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern;
23-
2411
@Profile("elevated-security")
2512
@Configuration(proxyBeanMethods = false)
2613
public class ElevatedSecurityPageSecurityConfig {
@@ -40,24 +27,21 @@ public String ott() {
4027
}
4128

4229
@Bean
43-
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
30+
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthorizationManagerFactory authz) throws Exception {
4431
// @formatter:off
4532
http
46-
.authorizeHttpRequests((authz) -> authz
47-
.requestMatchers("/profile").hasAuthority("profile:read")
48-
.anyRequest().authenticated()
49-
)
50-
.formLogin((form) -> form
51-
.loginPage("/login/form").permitAll()
52-
.factor((f) -> f.grants(Duration.ofMinutes(1), "profile:read"))
33+
.authorizeHttpRequests((authorize) -> authorize
34+
.anyRequest().access(authz.authenticated())
5335
)
54-
.oneTimeTokenLogin((ott) -> ott
55-
.loginPage("/login/ott").permitAll()
56-
.factor(Customizer.withDefaults())
57-
);
36+
.formLogin((form) -> form.loginPage("/login/form").permitAll())
37+
.oneTimeTokenLogin((ott) -> ott.loginPage("/login/ott").permitAll());
5838

5939
// @formatter:on
6040
return http.build();
6141
}
6242

43+
@Bean
44+
AuthorizationManagerFactory authz() {
45+
return new AuthorizationManagerFactory("FACTOR_PASSWORD", "FACTOR_OTT");
46+
}
6347
}

servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/org/example/magiclink/MagicLinkApplication.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
import org.springframework.boot.SpringApplication;
2020
import org.springframework.boot.autoconfigure.SpringBootApplication;
2121
import org.springframework.context.annotation.Bean;
22+
import org.springframework.security.access.prepost.PreAuthorize;
23+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
2224
import org.springframework.security.core.userdetails.User;
2325
import org.springframework.security.core.userdetails.UserDetails;
2426
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
2527
import org.springframework.stereotype.Controller;
2628
import org.springframework.web.bind.annotation.GetMapping;
2729

2830
@SpringBootApplication
31+
@EnableMethodSecurity
2932
public class MagicLinkApplication {
3033

3134
public static void main(String[] args) {
@@ -35,6 +38,8 @@ public static void main(String[] args) {
3538
@Controller
3639
static class AppController {
3740
@GetMapping("/profile")
41+
@PreAuthorize("@authz.hasAuthority('profile:read')") // FIXME add hasAuthorityWithin once
42+
// GrantedAuthority is timestamped
3843
String profile() {
3944
return "profile";
4045
}
@@ -45,7 +50,7 @@ InMemoryUserDetailsManager userDetailsService() {
4550
UserDetails user = User.withDefaultPasswordEncoder()
4651
.username("user")
4752
.password("password")
48-
.roles("USER")
53+
.authorities("ROLE_USER", "profile:read")
4954
.build();
5055
return new InMemoryUserDetailsManager(user);
5156
}

servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/main/java/org/example/magiclink/MagicLinkOneTimeTokenGenerationSuccessHandler.java

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,22 @@
2020

2121
import jakarta.servlet.http.HttpServletRequest;
2222
import jakarta.servlet.http.HttpServletResponse;
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
2325

24-
import org.springframework.mail.SimpleMailMessage;
25-
import org.springframework.mail.javamail.JavaMailSender;
2626
import org.springframework.security.authentication.ott.OneTimeToken;
2727
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
2828
import org.springframework.stereotype.Component;
2929

3030
@Component
3131
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
3232

33-
private final JavaMailSender mailSender;
34-
35-
public MagicLinkOneTimeTokenGenerationSuccessHandler(JavaMailSender mailSender) {
36-
this.mailSender = mailSender;
37-
}
33+
private final Log logger = LogFactory.getLog(this.getClass());
3834

3935
@Override
4036
public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken)
4137
throws IOException {
42-
SimpleMailMessage message = new SimpleMailMessage();
43-
message.setFrom("noreply@example.com");
44-
message.setTo("johndoe@example.com");
45-
message.setSubject("Your token");
46-
message.setText("Please enter this token " + oneTimeToken.getTokenValue());
47-
this.mailSender.send(message);
38+
this.logger.info("Use this one-time token: " + oneTimeToken.getTokenValue());
4839
response.sendRedirect("/login/ott");
4940
}
5041

servlet/spring-boot/java/authentication/mfa/formLogin+ott/src/test/java/org/example/magiclink/MagicLinkApplicationTests.java

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)