Add support for One-Time Token Login
Closes gh-15114
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ott;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenService;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
|
||||
|
||||
/**
|
||||
* Filter that process a One-Time Token generation request.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.4
|
||||
* @see OneTimeTokenService
|
||||
*/
|
||||
public final class GenerateOneTimeTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
private final OneTimeTokenService oneTimeTokenService;
|
||||
|
||||
private RequestMatcher requestMatcher = antMatcher(HttpMethod.POST, "/ott/generate");
|
||||
|
||||
private GeneratedOneTimeTokenHandler generatedOneTimeTokenHandler = new RedirectGeneratedOneTimeTokenHandler(
|
||||
"/login/ott");
|
||||
|
||||
public GenerateOneTimeTokenFilter(OneTimeTokenService oneTimeTokenService) {
|
||||
Assert.notNull(oneTimeTokenService, "oneTimeTokenService cannot be null");
|
||||
this.oneTimeTokenService = oneTimeTokenService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
if (!this.requestMatcher.matches(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String username = request.getParameter("username");
|
||||
if (!StringUtils.hasText(username)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
GenerateOneTimeTokenRequest generateRequest = new GenerateOneTimeTokenRequest(username);
|
||||
OneTimeToken ott = this.oneTimeTokenService.generate(generateRequest);
|
||||
this.generatedOneTimeTokenHandler.handle(request, response, ott);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link RequestMatcher} to match the request.
|
||||
* @param requestMatcher
|
||||
*/
|
||||
public void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies {@link GeneratedOneTimeTokenHandler} to be used to handle generated
|
||||
* one-time tokens
|
||||
* @param generatedOneTimeTokenHandler
|
||||
*/
|
||||
public void setGeneratedOneTimeTokenHandler(GeneratedOneTimeTokenHandler generatedOneTimeTokenHandler) {
|
||||
Assert.notNull(generatedOneTimeTokenHandler, "generatedOneTimeTokenHandler cannot be null");
|
||||
this.generatedOneTimeTokenHandler = generatedOneTimeTokenHandler;
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ott;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
|
||||
/**
|
||||
* Defines a strategy to handle generated one-time tokens.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.4
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GeneratedOneTimeTokenHandler {
|
||||
|
||||
/**
|
||||
* Handles generated one-time tokens
|
||||
*/
|
||||
void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken)
|
||||
throws IOException, ServletException;
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ott;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of {@link AuthenticationConverter} that detects if the request
|
||||
* contains a {@code token} parameter and constructs a
|
||||
* {@link OneTimeTokenAuthenticationToken} with it.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.4
|
||||
* @see GenerateOneTimeTokenFilter
|
||||
*/
|
||||
public class OneTimeTokenAuthenticationConverter implements AuthenticationConverter {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
String token = request.getParameter("token");
|
||||
if (!StringUtils.hasText(token)) {
|
||||
this.logger.debug("No token found in request");
|
||||
return null;
|
||||
}
|
||||
return OneTimeTokenAuthenticationToken.unauthenticated(token);
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ott;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link GeneratedOneTimeTokenHandler} that performs a redirect to a specific location
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.4
|
||||
*/
|
||||
public final class RedirectGeneratedOneTimeTokenHandler implements GeneratedOneTimeTokenHandler {
|
||||
|
||||
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
private final String redirectUrl;
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class that redirects to the specified URL.
|
||||
* @param redirectUrl
|
||||
*/
|
||||
public RedirectGeneratedOneTimeTokenHandler(String redirectUrl) {
|
||||
Assert.hasText(redirectUrl, "redirectUrl cannot be empty or null");
|
||||
this.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken)
|
||||
throws IOException {
|
||||
this.redirectStrategy.sendRedirect(request, response, this.redirectUrl);
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -68,8 +68,12 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
|
||||
private boolean saml2LoginEnabled;
|
||||
|
||||
private boolean oneTimeTokenEnabled;
|
||||
|
||||
private String authenticationUrl;
|
||||
|
||||
private String generateOneTimeTokenUrl;
|
||||
|
||||
private String usernameParameter;
|
||||
|
||||
private String passwordParameter;
|
||||
@@ -142,6 +146,10 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
this.oauth2LoginEnabled = oauth2LoginEnabled;
|
||||
}
|
||||
|
||||
public void setOneTimeTokenEnabled(boolean oneTimeTokenEnabled) {
|
||||
this.oneTimeTokenEnabled = oneTimeTokenEnabled;
|
||||
}
|
||||
|
||||
public void setSaml2LoginEnabled(boolean saml2LoginEnabled) {
|
||||
this.saml2LoginEnabled = saml2LoginEnabled;
|
||||
}
|
||||
@@ -150,6 +158,10 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
this.authenticationUrl = authenticationUrl;
|
||||
}
|
||||
|
||||
public void setGenerateOneTimeTokenUrl(String generateOneTimeTokenUrl) {
|
||||
this.generateOneTimeTokenUrl = generateOneTimeTokenUrl;
|
||||
}
|
||||
|
||||
public void setUsernameParameter(String usernameParameter) {
|
||||
this.usernameParameter = usernameParameter;
|
||||
}
|
||||
@@ -224,6 +236,19 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
sb.append(" <button type=\"submit\" class=\"primary\">Sign in</button>\n");
|
||||
sb.append(" </form>\n");
|
||||
}
|
||||
if (this.oneTimeTokenEnabled) {
|
||||
sb.append(" <form id=\"ott-form\" class=\"login-form\" method=\"post\" action=\"" + contextPath
|
||||
+ this.generateOneTimeTokenUrl + "\">\n");
|
||||
sb.append(" <h2>Request a One-Time Token</h2>\n");
|
||||
sb.append(createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "<p>\n");
|
||||
sb.append(" <label for=\"ott-username\" class=\"screenreader\">Username</label>\n");
|
||||
sb.append(" <input type=\"text\" id=\"ott-username\" name=\"" + this.usernameParameter
|
||||
+ "\" placeholder=\"Username\" required>\n");
|
||||
sb.append(" </p>\n");
|
||||
sb.append(renderHiddenInputs(request));
|
||||
sb.append(" <button class=\"primary\" type=\"submit\" form=\"ott-form\">Send Token</button>\n");
|
||||
sb.append(" </form>\n");
|
||||
}
|
||||
if (this.oauth2LoginEnabled) {
|
||||
sb.append("<h2>Login with OAuth 2.0</h2>");
|
||||
sb.append(createError(loginError, errorMsg));
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.web.util.CssUtils;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
/**
|
||||
* Creates a default one-time token submit page. If the request contains a {@code token}
|
||||
* query param the page will automatically fill the form with the token value.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.4
|
||||
*/
|
||||
public final class DefaultOneTimeTokenSubmitPageGeneratingFilter extends OncePerRequestFilter {
|
||||
|
||||
private RequestMatcher requestMatcher = new AntPathRequestMatcher("/login/ott", "GET");
|
||||
|
||||
private Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs = (request) -> Collections.emptyMap();
|
||||
|
||||
private String loginProcessingUrl = "/login/ott";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
if (!this.requestMatcher.matches(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String html = generateHtml(request);
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
response.setContentLength(html.getBytes(StandardCharsets.UTF_8).length);
|
||||
response.getWriter().write(html);
|
||||
}
|
||||
|
||||
private String generateHtml(HttpServletRequest request) {
|
||||
String token = request.getParameter("token");
|
||||
String inputValue = StringUtils.hasText(token) ? HtmlUtils.htmlEscape(token) : "";
|
||||
String input = "<input type=\"text\" id=\"token\" name=\"token\" value=\"" + inputValue + "\""
|
||||
+ " placeholder=\"Token\" required=\"true\" autofocus=\"autofocus\"/>";
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>One-Time Token Login</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='"/>
|
||||
"""
|
||||
+ CssUtils.getCssStyleBlock().indent(4)
|
||||
+ """
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<p>
|
||||
<strong>Note:</strong> Since your browser does not support JavaScript, you must press the Sign In button once to proceed.
|
||||
</p>
|
||||
</noscript>
|
||||
<div class="container">
|
||||
"""
|
||||
+ "<form class=\"login-form\" action=\"" + this.loginProcessingUrl + "\" method=\"post\">" + """
|
||||
<h2>Please input the token</h2>
|
||||
<p>
|
||||
<label for="token" class="screenreader">Token</label>
|
||||
""" + input + """
|
||||
</p>
|
||||
<button class="primary" type="submit">Sign in</button>
|
||||
""" + renderHiddenInputs(request) + """
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
}
|
||||
|
||||
private String renderHiddenInputs(HttpServletRequest request) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, String> input : this.resolveHiddenInputs.apply(request).entrySet()) {
|
||||
sb.append("<input name=\"");
|
||||
sb.append(input.getKey());
|
||||
sb.append("\" type=\"hidden\" value=\"");
|
||||
sb.append(input.getValue());
|
||||
sb.append("\" />\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void setResolveHiddenInputs(Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs) {
|
||||
Assert.notNull(resolveHiddenInputs, "resolveHiddenInputs cannot be null");
|
||||
this.resolveHiddenInputs = resolveHiddenInputs;
|
||||
}
|
||||
|
||||
public void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the URL that the submit form should POST to. Defaults to
|
||||
* {@code /login/ott}.
|
||||
* @param loginProcessingUrl
|
||||
*/
|
||||
public void setLoginProcessingUrl(String loginProcessingUrl) {
|
||||
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be null or empty");
|
||||
this.loginProcessingUrl = loginProcessingUrl;
|
||||
}
|
||||
|
||||
}
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -185,4 +185,25 @@ public class DefaultLoginPageGeneratingFilterTests {
|
||||
assertThat(response.getContentAsString()).contains("Invalid credentials");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateWhenOneTimeTokenLoginThenOttForm() throws Exception {
|
||||
DefaultLoginPageGeneratingFilter filter = new DefaultLoginPageGeneratingFilter();
|
||||
filter.setLoginPageUrl(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL);
|
||||
filter.setOneTimeTokenEnabled(true);
|
||||
filter.setGenerateOneTimeTokenUrl("/ott/authenticate");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(new MockHttpServletRequest("GET", "/login"), response, this.chain);
|
||||
assertThat(response.getContentAsString()).contains("Request a One-Time Token");
|
||||
assertThat(response.getContentAsString()).contains("""
|
||||
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
|
||||
<h2>Request a One-Time Token</h2>
|
||||
<p>
|
||||
<label for="ott-username" class="screenreader">Username</label>
|
||||
<input type="text" id="ott-username" name="null" placeholder="Username" required>
|
||||
</p>
|
||||
<button class="primary" type="submit" form="ott-form">Send Token</button>
|
||||
</form>
|
||||
""");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ott;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OneTimeTokenAuthenticationConverter}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
*/
|
||||
class OneTimeTokenAuthenticationConverterTests {
|
||||
|
||||
private final OneTimeTokenAuthenticationConverter converter = new OneTimeTokenAuthenticationConverter();
|
||||
|
||||
@Test
|
||||
void convertWhenTokenParameterThenReturnOneTimeTokenAuthenticationToken() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameter("token", "1234");
|
||||
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.converter
|
||||
.convert(request);
|
||||
assertThat(authentication).isNotNull();
|
||||
assertThat(authentication.getTokenValue()).isEqualTo("1234");
|
||||
assertThat(authentication.getPrincipal()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertWhenTokenAndUsernameParameterThenReturnOneTimeTokenAuthenticationTokenWithUsername() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameter("token", "1234");
|
||||
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.converter
|
||||
.convert(request);
|
||||
assertThat(authentication).isNotNull();
|
||||
assertThat(authentication.getTokenValue()).isEqualTo("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertWhenOnlyUsernameParameterThenReturnNull() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameter("username", "josh");
|
||||
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.converter
|
||||
.convert(request);
|
||||
assertThat(authentication).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertWhenNoTokenParameterThenNull() {
|
||||
Authentication authentication = this.converter.convert(new MockHttpServletRequest());
|
||||
assertThat(authentication).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ott;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.ott.DefaultOneTimeToken;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link RedirectGeneratedOneTimeTokenHandler}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
*/
|
||||
class RedirectGeneratedOneTimeTokenHandlerTests {
|
||||
|
||||
@Test
|
||||
void handleThenRedirectToDefaultLocation() throws IOException {
|
||||
RedirectGeneratedOneTimeTokenHandler handler = new RedirectGeneratedOneTimeTokenHandler("/login/ott");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
handler.handle(new MockHttpServletRequest(), response, new DefaultOneTimeToken("token", "user", Instant.now()));
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/login/ott");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleWhenUrlChangedThenRedirectToUrl() throws IOException {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RedirectGeneratedOneTimeTokenHandler handler = new RedirectGeneratedOneTimeTokenHandler("/redirected");
|
||||
handler.handle(new MockHttpServletRequest(), response, new DefaultOneTimeToken("token", "user", Instant.now()));
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/redirected");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setRedirectUrlWhenNullOrEmptyThenException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RedirectGeneratedOneTimeTokenHandler(null))
|
||||
.withMessage("redirectUrl cannot be empty or null");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RedirectGeneratedOneTimeTokenHandler(""))
|
||||
.withMessage("redirectUrl cannot be empty or null");
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2002-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 org.springframework.security.web.authentication.ui;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultOneTimeTokenSubmitPageGeneratingFilter}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
*/
|
||||
class DefaultOneTimeTokenSubmitPageGeneratingFilterTests {
|
||||
|
||||
DefaultOneTimeTokenSubmitPageGeneratingFilter filter = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
MockFilterChain filterChain = new MockFilterChain();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.request.setMethod("GET");
|
||||
this.request.setServletPath("/login/ott");
|
||||
}
|
||||
|
||||
@Test
|
||||
void filterWhenTokenQueryParamThenShouldIncludeJavascriptToAutoSubmitFormAndInputHasTokenValue() throws Exception {
|
||||
this.request.setParameter("token", "1234");
|
||||
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
|
||||
String response = this.response.getContentAsString();
|
||||
assertThat(response).contains(
|
||||
"<input type=\"text\" id=\"token\" name=\"token\" value=\"1234\" placeholder=\"Token\" required=\"true\" autofocus=\"autofocus\"/>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setRequestMatcherWhenNullThenException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestMatcher(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setLoginProcessingUrlWhenNullOrEmptyThenException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setLoginProcessingUrl(null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setLoginProcessingUrl(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setLoginProcessingUrlThenUseItForFormAction() throws Exception {
|
||||
this.filter.setLoginProcessingUrl("/login/another");
|
||||
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
|
||||
String response = this.response.getContentAsString();
|
||||
assertThat(response).contains(
|
||||
"<form class=\"login-form\" action=\"/login/another\" method=\"post\">\t<h2>Please input the token</h2>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void filterWhenTokenQueryParamUsesSpecialCharactersThenValueIsEscaped() throws Exception {
|
||||
this.request.setParameter("token", "this<>!@#\"");
|
||||
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
|
||||
String response = this.response.getContentAsString();
|
||||
assertThat(response).contains(
|
||||
"<input type=\"text\" id=\"token\" name=\"token\" value=\"this<>!@#"\" placeholder=\"Token\" required=\"true\" autofocus=\"autofocus\"/>");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user