Ver Fonte

Use static CSS in OneTimeToken default UI

Daniel Garnier-Moiroux há 1 ano atrás
pai
commit
c1b9035544

+ 2 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java

@@ -46,6 +46,7 @@ import org.springframework.security.web.authentication.ott.GeneratedOneTimeToken
 import org.springframework.security.web.authentication.ott.OneTimeTokenAuthenticationConverter;
 import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
 import org.springframework.security.web.authentication.ui.DefaultOneTimeTokenSubmitPageGeneratingFilter;
+import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 import org.springframework.security.web.context.SecurityContextRepository;
 import org.springframework.security.web.csrf.CsrfToken;
@@ -136,6 +137,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
 		generateFilter.setGeneratedOneTimeTokenHandler(getGeneratedOneTimeTokenHandler(http));
 		generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.generateTokenUrl));
 		http.addFilter(postProcess(generateFilter));
+		http.addFilter(DefaultResourcesFilter.css());
 	}
 
 	private GeneratedOneTimeTokenHandler getGeneratedOneTimeTokenHandler(H http) {

+ 20 - 138
config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java

@@ -21,6 +21,7 @@ import java.io.IOException;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -53,6 +54,7 @@ import static org.springframework.security.test.web.servlet.response.SecurityMoc
 import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
@@ -64,143 +66,6 @@ public class OneTimeTokenLoginConfigurerTests {
 	@Autowired(required = false)
 	MockMvc mvc;
 
-	public static final String EXPECTED_HTML_HEAD = """
-			<!DOCTYPE html>
-			<html lang="en">
-			  <head>
-			    <meta charset="utf-8">
-			    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-			    <meta name="description" content="">
-			    <meta name="author" content="">
-			    <title>Please sign in</title>
-			    <style>
-			    /* General layout */
-			    body {
-			      font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-			      background-color: #eee;
-			      padding: 40px 0;
-			      margin: 0;
-			      line-height: 1.5;
-			    }
-			\s\s\s\s
-			    h2 {
-			      margin-top: 0;
-			      margin-bottom: 0.5rem;
-			      font-size: 2rem;
-			      font-weight: 500;
-			      line-height: 2rem;
-			    }
-			\s\s\s\s
-			    .content {
-			      margin-right: auto;
-			      margin-left: auto;
-			      padding-right: 15px;
-			      padding-left: 15px;
-			      width: 100%;
-			      box-sizing: border-box;
-			    }
-			\s\s\s\s
-			    @media (min-width: 800px) {
-			      .content {
-			        max-width: 760px;
-			      }
-			    }
-			\s\s\s\s
-			    /* Components */
-			    a,
-			    a:visited {
-			      text-decoration: none;
-			      color: #06f;
-			    }
-			\s\s\s\s
-			    a:hover {
-			      text-decoration: underline;
-			      color: #003c97;
-			    }
-			\s\s\s\s
-			    input[type="text"],
-			    input[type="password"] {
-			      height: auto;
-			      width: 100%;
-			      font-size: 1rem;
-			      padding: 0.5rem;
-			      box-sizing: border-box;
-			    }
-			\s\s\s\s
-			    button {
-			      padding: 0.5rem 1rem;
-			      font-size: 1.25rem;
-			      line-height: 1.5;
-			      border: none;
-			      border-radius: 0.1rem;
-			      width: 100%;
-			    }
-			\s\s\s\s
-			    button.primary {
-			      color: #fff;
-			      background-color: #06f;
-			    }
-			\s\s\s\s
-			    .alert {
-			      padding: 0.75rem 1rem;
-			      margin-bottom: 1rem;
-			      line-height: 1.5;
-			      border-radius: 0.1rem;
-			      width: 100%;
-			      box-sizing: border-box;
-			      border-width: 1px;
-			      border-style: solid;
-			    }
-			\s\s\s\s
-			    .alert.alert-danger {
-			      color: #6b1922;
-			      background-color: #f7d5d7;
-			      border-color: #eab6bb;
-			    }
-			\s\s\s\s
-			    .alert.alert-success {
-			      color: #145222;
-			      background-color: #d1f0d9;
-			      border-color: #c2ebcb;
-			    }
-			\s\s\s\s
-			    .screenreader {
-			      position: absolute;
-			      clip: rect(0 0 0 0);
-			      height: 1px;
-			      width: 1px;
-			      padding: 0;
-			      border: 0;
-			      overflow: hidden;
-			    }
-			\s\s\s\s
-			    table {
-			      width: 100%;
-			      max-width: 100%;
-			      margin-bottom: 2rem;
-			    }
-			\s\s\s\s
-			    .table-striped tr:nth-of-type(2n + 1) {
-			      background-color: #e1e1e1;
-			    }
-			\s\s\s\s
-			    td {
-			      padding: 0.75rem;
-			      vertical-align: top;
-			    }
-			\s\s\s\s
-			    /* Login / logout layouts */
-			    .login-form,
-			    .logout-form {
-			      max-width: 340px;
-			      padding: 0 15px 15px 15px;
-			      margin: 0 auto 2rem auto;
-			      box-sizing: border-box;
-			    }
-			    </style>
-			  </head>
-			""";
-
 	@Test
 	void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception {
 		this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
@@ -252,6 +117,14 @@ public class OneTimeTokenLoginConfigurerTests {
 			.andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated());
 	}
 
+	@Test
+	void oneTimeTokenWhenConfiguredThenServesCss() throws Exception {
+		this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
+		this.mvc.perform(get("/default-ui.css"))
+			.andExpect(status().isOk())
+			.andExpect(content().string(Matchers.containsString("body {")));
+	}
+
 	@Test
 	void oneTimeTokenWhenFormLoginConfiguredThenRendersRequestTokenForm() throws Exception {
 		this.spring.register(OneTimeTokenFormLoginConfig.class).autowire();
@@ -262,8 +135,17 @@ public class OneTimeTokenLoginConfigurerTests {
 				.andExpect((result) -> {
 					CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName());
 					assertThat(result.getResponse().getContentAsString()).isEqualTo(
-						EXPECTED_HTML_HEAD +
 						"""
+						<!DOCTYPE html>
+						<html lang="en">
+						  <head>
+						    <meta charset="utf-8">
+						    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+						    <meta name="description" content="">
+						    <meta name="author" content="">
+						    <title>Please sign in</title>
+						    <link href="/default-ui.css" rel="stylesheet" />
+						  </head>
 						  <body>
 						    <div class="content">
 						      <form class="login-form" method="post" action="/login">

+ 4 - 4
web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java

@@ -28,7 +28,6 @@ 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;
@@ -64,9 +63,10 @@ public final class DefaultOneTimeTokenSubmitPageGeneratingFilter extends OncePer
 	}
 
 	private String generateHtml(HttpServletRequest request) {
+		String contextPath = request.getContextPath();
+
 		String token = request.getParameter("token");
 		String tokenValue = StringUtils.hasText(token) ? token : "";
-		String contextPath = request.getContextPath();
 
 		String hiddenInputs = this.resolveHiddenInputs.apply(request)
 			.entrySet()
@@ -75,7 +75,7 @@ public final class DefaultOneTimeTokenSubmitPageGeneratingFilter extends OncePer
 			.collect(Collectors.joining("\n"));
 
 		return HtmlTemplates.fromTemplate(ONE_TIME_TOKEN_SUBMIT_PAGE_TEMPLATE)
-			.withRawHtml("cssStyle", CssUtils.getCssStyleBlock().indent(4))
+			.withValue("contextPath", contextPath)
 			.withValue("tokenValue", tokenValue)
 			.withValue("loginProcessingUrl", contextPath + this.loginProcessingUrl)
 			.withRawHtml("hiddenInputs", hiddenInputs)
@@ -116,7 +116,7 @@ public final class DefaultOneTimeTokenSubmitPageGeneratingFilter extends OncePer
 			    <title>One-Time Token Login</title>
 			    <meta charset="utf-8"/>
 			    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
-			{{cssStyle}}
+			    <link href="{{contextPath}}/default-ui.css" rel="stylesheet" />
 			  </head>
 			  <body>
 			    <div class="container">

+ 1 - 125
web/src/test/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilterTests.java

@@ -110,131 +110,7 @@ class DefaultOneTimeTokenSubmitPageGeneratingFilterTests {
 						    <title>One-Time Token Login</title>
 						    <meta charset="utf-8"/>
 						    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
-						    <style>
-						    /* General layout */
-						    body {
-						      font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-						      background-color: #eee;
-						      padding: 40px 0;
-						      margin: 0;
-						      line-height: 1.5;
-						    }
-						   \s
-						    h2 {
-						      margin-top: 0;
-						      margin-bottom: 0.5rem;
-						      font-size: 2rem;
-						      font-weight: 500;
-						      line-height: 2rem;
-						    }
-						   \s
-						    .content {
-						      margin-right: auto;
-						      margin-left: auto;
-						      padding-right: 15px;
-						      padding-left: 15px;
-						      width: 100%;
-						      box-sizing: border-box;
-						    }
-						   \s
-						    @media (min-width: 800px) {
-						      .content {
-						        max-width: 760px;
-						      }
-						    }
-						   \s
-						    /* Components */
-						    a,
-						    a:visited {
-						      text-decoration: none;
-						      color: #06f;
-						    }
-						   \s
-						    a:hover {
-						      text-decoration: underline;
-						      color: #003c97;
-						    }
-						   \s
-						    input[type="text"],
-						    input[type="password"] {
-						      height: auto;
-						      width: 100%;
-						      font-size: 1rem;
-						      padding: 0.5rem;
-						      box-sizing: border-box;
-						    }
-						   \s
-						    button {
-						      padding: 0.5rem 1rem;
-						      font-size: 1.25rem;
-						      line-height: 1.5;
-						      border: none;
-						      border-radius: 0.1rem;
-						      width: 100%;
-						    }
-						   \s
-						    button.primary {
-						      color: #fff;
-						      background-color: #06f;
-						    }
-						   \s
-						    .alert {
-						      padding: 0.75rem 1rem;
-						      margin-bottom: 1rem;
-						      line-height: 1.5;
-						      border-radius: 0.1rem;
-						      width: 100%;
-						      box-sizing: border-box;
-						      border-width: 1px;
-						      border-style: solid;
-						    }
-						   \s
-						    .alert.alert-danger {
-						      color: #6b1922;
-						      background-color: #f7d5d7;
-						      border-color: #eab6bb;
-						    }
-						   \s
-						    .alert.alert-success {
-						      color: #145222;
-						      background-color: #d1f0d9;
-						      border-color: #c2ebcb;
-						    }
-						   \s
-						    .screenreader {
-						      position: absolute;
-						      clip: rect(0 0 0 0);
-						      height: 1px;
-						      width: 1px;
-						      padding: 0;
-						      border: 0;
-						      overflow: hidden;
-						    }
-						   \s
-						    table {
-						      width: 100%;
-						      max-width: 100%;
-						      margin-bottom: 2rem;
-						    }
-						   \s
-						    .table-striped tr:nth-of-type(2n + 1) {
-						      background-color: #e1e1e1;
-						    }
-						   \s
-						    td {
-						      padding: 0.75rem;
-						      vertical-align: top;
-						    }
-						   \s
-						    /* Login / logout layouts */
-						    .login-form,
-						    .logout-form {
-						      max-width: 340px;
-						      padding: 0 15px 15px 15px;
-						      margin: 0 auto 2rem auto;
-						      box-sizing: border-box;
-						    }
-						    </style>
+						    <link href="/default-ui.css" rel="stylesheet" />
 						  </head>
 						  <body>
 						    <div class="container">