Skip to content

Commit 3541139

Browse files
committed
One Time Token login registers the default login page
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
1 parent 5d9011b commit 3541139

File tree

9 files changed

+214
-103
lines changed

9 files changed

+214
-103
lines changed

Diff for: config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
3232
import org.springframework.security.web.authentication.logout.LogoutFilter;
3333
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenFilter;
34+
import org.springframework.security.web.authentication.ott.OneTimeTokenAuthenticationFilter;
3435
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
3536
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
3637
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
@@ -101,6 +102,7 @@ final class FilterOrderRegistration {
101102
"org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter",
102103
order.next());
103104
put(UsernamePasswordAuthenticationFilter.class, order.next());
105+
put(OneTimeTokenAuthenticationFilter.class, order.next());
104106
order.next(); // gh-8105
105107
put(DefaultResourcesFilter.class, order.next());
106108
put(DefaultLoginPageGeneratingFilter.class, order.next());

Diff for: config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java

+101-67
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import org.springframework.context.ApplicationContext;
2626
import org.springframework.http.HttpMethod;
27-
import org.springframework.security.authentication.AuthenticationManager;
2827
import org.springframework.security.authentication.AuthenticationProvider;
2928
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
3029
import org.springframework.security.authentication.ott.InMemoryOneTimeTokenService;
@@ -33,51 +32,88 @@
3332
import org.springframework.security.authentication.ott.OneTimeTokenService;
3433
import org.springframework.security.config.Customizer;
3534
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
35+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
36+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
37+
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
3638
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
3739
import org.springframework.security.core.Authentication;
3840
import org.springframework.security.core.userdetails.UserDetailsService;
3941
import org.springframework.security.web.authentication.AuthenticationConverter;
4042
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
41-
import org.springframework.security.web.authentication.AuthenticationFilter;
4243
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
4344
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
4445
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
4546
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver;
4647
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenFilter;
4748
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver;
4849
import org.springframework.security.web.authentication.ott.OneTimeTokenAuthenticationConverter;
50+
import org.springframework.security.web.authentication.ott.OneTimeTokenAuthenticationFilter;
4951
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
5052
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
5153
import org.springframework.security.web.authentication.ui.DefaultOneTimeTokenSubmitPageGeneratingFilter;
5254
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
53-
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
54-
import org.springframework.security.web.context.SecurityContextRepository;
5555
import org.springframework.security.web.csrf.CsrfToken;
56+
import org.springframework.security.web.util.matcher.RequestMatcher;
5657
import org.springframework.util.Assert;
5758
import org.springframework.util.StringUtils;
5859

5960
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
6061

61-
public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
62-
extends AbstractHttpConfigurer<OneTimeTokenLoginConfigurer<H>, H> {
62+
/**
63+
* An {@link AbstractHttpConfigurer} for One-Time Token Login.
64+
*
65+
* <p>
66+
* One-Time Token Login provides an application with the capability to have users log in
67+
* by obtaining a single-use token out of band, for example through email.
68+
*
69+
* <p>
70+
* Defaults are provided for all configuration options, with the only required
71+
* configuration being
72+
* {@link #tokenGenerationSuccessHandler(OneTimeTokenGenerationSuccessHandler)}.
73+
* Alternatively, a {@link OneTimeTokenGenerationSuccessHandler} {@code @Bean} may be
74+
* registered instead.
75+
*
76+
* <h2>Security Filters</h2>
77+
*
78+
* The following {@code Filter}s are populated:
79+
*
80+
* <ul>
81+
* <li>{@link DefaultOneTimeTokenSubmitPageGeneratingFilter}</li>
82+
* <li>{@link GenerateOneTimeTokenFilter}</li>
83+
* <li>{@link OneTimeTokenAuthenticationFilter}</li>
84+
* </ul>
85+
*
86+
* <h2>Shared Objects Used</h2>
87+
*
88+
* The following shared objects are used:
89+
*
90+
* <ul>
91+
* <li>{@link DefaultLoginPageGeneratingFilter} - if {@link #loginPage(String)} is not
92+
* configured and {@code DefaultLoginPageGeneratingFilter} is available, then a default
93+
* login page will be made available</li>
94+
* </ul>
95+
*
96+
* @author Marcus Da Coregio
97+
* @author Daniel Garnier-Moiroux
98+
* @since 6.4
99+
* @see HttpSecurity#oneTimeTokenLogin(Customizer)
100+
* @see DefaultOneTimeTokenSubmitPageGeneratingFilter
101+
* @see GenerateOneTimeTokenFilter
102+
* @see OneTimeTokenAuthenticationFilter
103+
* @see AbstractAuthenticationFilterConfigurer
104+
*/
105+
public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
106+
AbstractAuthenticationFilterConfigurer<H, OneTimeTokenLoginConfigurer<H>, OneTimeTokenAuthenticationFilter> {
63107

64108
private final ApplicationContext context;
65109

66110
private OneTimeTokenService oneTimeTokenService;
67111

68-
private AuthenticationConverter authenticationConverter = new OneTimeTokenAuthenticationConverter();
69-
70-
private AuthenticationFailureHandler authenticationFailureHandler;
71-
72-
private AuthenticationSuccessHandler authenticationSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
73-
74-
private String defaultSubmitPageUrl = "/login/ott";
112+
private String defaultSubmitPageUrl = DefaultOneTimeTokenSubmitPageGeneratingFilter.DEFAULT_SUBMIT_PAGE_URL;
75113

76114
private boolean submitPageEnabled = true;
77115

78-
private String loginProcessingUrl = "/login/ott";
79-
80-
private String tokenGeneratingUrl = "/ott/generate";
116+
private String tokenGeneratingUrl = GenerateOneTimeTokenFilter.DEFAULT_GENERATE_URL;
81117

82118
private OneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler;
83119

@@ -86,55 +122,51 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
86122
private GenerateOneTimeTokenRequestResolver requestResolver;
87123

88124
public OneTimeTokenLoginConfigurer(ApplicationContext context) {
125+
super(new OneTimeTokenAuthenticationFilter(), OneTimeTokenAuthenticationFilter.DEFAULT_LOGIN_PROCESSING_URL);
89126
this.context = context;
90127
}
91128

92129
@Override
93-
public void init(H http) {
130+
public void init(H http) throws Exception {
131+
super.init(http);
94132
AuthenticationProvider authenticationProvider = getAuthenticationProvider(http);
95133
http.authenticationProvider(postProcess(authenticationProvider));
96-
configureDefaultLoginPage(http);
134+
intiDefaultLoginFilter(http);
135+
}
136+
137+
private AuthenticationProvider getAuthenticationProvider(H http) {
138+
if (this.authenticationProvider != null) {
139+
return this.authenticationProvider;
140+
}
141+
UserDetailsService userDetailsService = getContext().getBean(UserDetailsService.class);
142+
this.authenticationProvider = new OneTimeTokenAuthenticationProvider(getOneTimeTokenService(http),
143+
userDetailsService);
144+
return this.authenticationProvider;
97145
}
98146

99-
private void configureDefaultLoginPage(H http) {
147+
private void intiDefaultLoginFilter(H http) {
100148
DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
101149
.getSharedObject(DefaultLoginPageGeneratingFilter.class);
102-
if (loginPageGeneratingFilter == null) {
150+
if (loginPageGeneratingFilter == null || isCustomLoginPage()) {
103151
return;
104152
}
105153
loginPageGeneratingFilter.setOneTimeTokenEnabled(true);
106154
loginPageGeneratingFilter.setOneTimeTokenGenerationUrl(this.tokenGeneratingUrl);
107-
if (this.authenticationFailureHandler == null
108-
&& StringUtils.hasText(loginPageGeneratingFilter.getLoginPageUrl())) {
109-
this.authenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler(
110-
loginPageGeneratingFilter.getLoginPageUrl() + "?error");
155+
156+
if (!StringUtils.hasText(loginPageGeneratingFilter.getLoginPageUrl())) {
157+
loginPageGeneratingFilter.setLoginPageUrl(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL);
158+
loginPageGeneratingFilter.setFailureUrl(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL + "?"
159+
+ DefaultLoginPageGeneratingFilter.ERROR_PARAMETER_NAME);
160+
loginPageGeneratingFilter
161+
.setLogoutSuccessUrl(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL + "?logout");
111162
}
112163
}
113164

114165
@Override
115-
public void configure(H http) {
166+
public void configure(H http) throws Exception {
167+
super.configure(http);
116168
configureSubmitPage(http);
117169
configureOttGenerateFilter(http);
118-
configureOttAuthenticationFilter(http);
119-
}
120-
121-
private void configureOttAuthenticationFilter(H http) {
122-
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
123-
AuthenticationFilter oneTimeTokenAuthenticationFilter = new AuthenticationFilter(authenticationManager,
124-
this.authenticationConverter);
125-
oneTimeTokenAuthenticationFilter.setSecurityContextRepository(getSecurityContextRepository(http));
126-
oneTimeTokenAuthenticationFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.loginProcessingUrl));
127-
oneTimeTokenAuthenticationFilter.setFailureHandler(getAuthenticationFailureHandler());
128-
oneTimeTokenAuthenticationFilter.setSuccessHandler(this.authenticationSuccessHandler);
129-
http.addFilter(postProcess(oneTimeTokenAuthenticationFilter));
130-
}
131-
132-
private SecurityContextRepository getSecurityContextRepository(H http) {
133-
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
134-
if (securityContextRepository != null) {
135-
return securityContextRepository;
136-
}
137-
return new HttpSessionSecurityContextRepository();
138170
}
139171

140172
private void configureOttGenerateFilter(H http) {
@@ -166,18 +198,13 @@ private void configureSubmitPage(H http) {
166198
DefaultOneTimeTokenSubmitPageGeneratingFilter submitPage = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
167199
submitPage.setResolveHiddenInputs(this::hiddenInputs);
168200
submitPage.setRequestMatcher(antMatcher(HttpMethod.GET, this.defaultSubmitPageUrl));
169-
submitPage.setLoginProcessingUrl(this.loginProcessingUrl);
201+
submitPage.setLoginProcessingUrl(this.getLoginProcessingUrl());
170202
http.addFilter(postProcess(submitPage));
171203
}
172204

173-
private AuthenticationProvider getAuthenticationProvider(H http) {
174-
if (this.authenticationProvider != null) {
175-
return this.authenticationProvider;
176-
}
177-
UserDetailsService userDetailsService = getContext().getBean(UserDetailsService.class);
178-
this.authenticationProvider = new OneTimeTokenAuthenticationProvider(getOneTimeTokenService(http),
179-
userDetailsService);
180-
return this.authenticationProvider;
205+
@Override
206+
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
207+
return antMatcher(HttpMethod.POST, loginProcessingUrl);
181208
}
182209

183210
/**
@@ -217,14 +244,25 @@ public OneTimeTokenLoginConfigurer<H> tokenGenerationSuccessHandler(
217244
* Only POST requests are processed, for that reason make sure that you pass a valid
218245
* CSRF token if CSRF protection is enabled.
219246
* @param loginProcessingUrl
220-
* @see org.springframework.security.config.annotation.web.builders.HttpSecurity#csrf(Customizer)
247+
* @see HttpSecurity#csrf(Customizer)
221248
*/
222249
public OneTimeTokenLoginConfigurer<H> loginProcessingUrl(String loginProcessingUrl) {
223250
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be null or empty");
224-
this.loginProcessingUrl = loginProcessingUrl;
251+
super.loginProcessingUrl(loginProcessingUrl);
225252
return this;
226253
}
227254

255+
/**
256+
* Specifies the URL to send users to if login is required. If used with
257+
* {@link EnableWebSecurity} a default login page will be generated when this
258+
* attribute is not specified.
259+
* @param loginPage
260+
*/
261+
@Override
262+
public OneTimeTokenLoginConfigurer<H> loginPage(String loginPage) {
263+
return super.loginPage(loginPage);
264+
}
265+
228266
/**
229267
* Configures whether the default one-time token submit page should be shown. This
230268
* will prevent the {@link DefaultOneTimeTokenSubmitPageGeneratingFilter} to be
@@ -269,7 +307,7 @@ public OneTimeTokenLoginConfigurer<H> tokenService(OneTimeTokenService oneTimeTo
269307
*/
270308
public OneTimeTokenLoginConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) {
271309
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
272-
this.authenticationConverter = authenticationConverter;
310+
this.getAuthenticationFilter().setAuthenticationConverter(authenticationConverter);
273311
return this;
274312
}
275313

@@ -279,11 +317,13 @@ public OneTimeTokenLoginConfigurer<H> authenticationConverter(AuthenticationConv
279317
* {@link SimpleUrlAuthenticationFailureHandler}
280318
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} to use
281319
* when authentication fails.
320+
* @deprecated Use {@link #failureHandler(AuthenticationFailureHandler)} instead
282321
*/
322+
@Deprecated(since = "6.5")
283323
public OneTimeTokenLoginConfigurer<H> authenticationFailureHandler(
284324
AuthenticationFailureHandler authenticationFailureHandler) {
285325
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
286-
this.authenticationFailureHandler = authenticationFailureHandler;
326+
super.failureHandler(authenticationFailureHandler);
287327
return this;
288328
}
289329

@@ -292,22 +332,16 @@ public OneTimeTokenLoginConfigurer<H> authenticationFailureHandler(
292332
* {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properties
293333
* set.
294334
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler}.
335+
* @deprecated Use {@link #successHandler(AuthenticationSuccessHandler)} instead
295336
*/
337+
@Deprecated(since = "6.5")
296338
public OneTimeTokenLoginConfigurer<H> authenticationSuccessHandler(
297339
AuthenticationSuccessHandler authenticationSuccessHandler) {
298340
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
299-
this.authenticationSuccessHandler = authenticationSuccessHandler;
341+
super.successHandler(authenticationSuccessHandler);
300342
return this;
301343
}
302344

303-
private AuthenticationFailureHandler getAuthenticationFailureHandler() {
304-
if (this.authenticationFailureHandler != null) {
305-
return this.authenticationFailureHandler;
306-
}
307-
this.authenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");
308-
return this.authenticationFailureHandler;
309-
}
310-
311345
/**
312346
* Use this {@link GenerateOneTimeTokenRequestResolver} when resolving
313347
* {@link GenerateOneTimeTokenRequest} from {@link HttpServletRequest}. By default,

Diff for: config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -62,12 +62,12 @@ class OneTimeTokenLoginDsl {
6262
tokenService?.also { oneTimeTokenLoginConfigurer.tokenService(tokenService) }
6363
authenticationConverter?.also { oneTimeTokenLoginConfigurer.authenticationConverter(authenticationConverter) }
6464
authenticationFailureHandler?.also {
65-
oneTimeTokenLoginConfigurer.authenticationFailureHandler(
65+
oneTimeTokenLoginConfigurer.failureHandler(
6666
authenticationFailureHandler
6767
)
6868
}
6969
authenticationSuccessHandler?.also {
70-
oneTimeTokenLoginConfigurer.authenticationSuccessHandler(
70+
oneTimeTokenLoginConfigurer.successHandler(
7171
authenticationSuccessHandler
7272
)
7373
}

0 commit comments

Comments
 (0)