7 changed files with 334 additions and 1 deletions
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
/* |
||||
* 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 jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.ServletRequest; |
||||
import jakarta.servlet.ServletResponse; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
|
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.filter.GenericFilterBean; |
||||
|
||||
/** |
||||
* Serve common static assets used in default UIs, such as CSS or Javascript files. For |
||||
* internal use only. |
||||
* |
||||
* @author Daniel Garnier-Moiroux |
||||
* @since 6.4 |
||||
*/ |
||||
public final class DefaultResourcesFilter extends GenericFilterBean { |
||||
|
||||
private final RequestMatcher matcher; |
||||
|
||||
private final ClassPathResource resource; |
||||
|
||||
private final MediaType mediaType; |
||||
|
||||
private DefaultResourcesFilter(RequestMatcher matcher, ClassPathResource resource, MediaType mediaType) { |
||||
Assert.isTrue(resource.exists(), "classpath resource must exist"); |
||||
this.matcher = matcher; |
||||
this.resource = resource; |
||||
this.mediaType = mediaType; |
||||
} |
||||
|
||||
@Override |
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
if (!(request instanceof HttpServletRequest servletRequest)) { |
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
|
||||
if (this.matcher.matches(servletRequest)) { |
||||
response.setContentType(this.mediaType.toString()); |
||||
response.getWriter().write(this.resource.getContentAsString(StandardCharsets.UTF_8)); |
||||
return; |
||||
} |
||||
|
||||
filterChain.doFilter(request, response); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "%s [matcher=%s, resource=%s]".formatted(getClass().getSimpleName(), this.matcher.toString(), |
||||
this.resource.getPath()); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of {@link DefaultResourcesFilter} serving Spring Security's |
||||
* default CSS stylesheet. |
||||
* <p> |
||||
* The created {@link DefaultResourcesFilter} matches requests |
||||
* {@code HTTP GET /default-ui.css}, and returns the default |
||||
* stylesheet at {@code org/springframework/security/default-ui.css} with |
||||
* content-type {@code text/css;charset=UTF-8}. |
||||
* @return - |
||||
*/ |
||||
public static DefaultResourcesFilter defaultCss() { |
||||
return new DefaultResourcesFilter( |
||||
AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/default-ui.css"), |
||||
new ClassPathResource("org/springframework/security/default-ui.css"), |
||||
new MediaType("text", "css", StandardCharsets.UTF_8)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,139 @@
@@ -0,0 +1,139 @@
|
||||
/* |
||||
* 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. |
||||
*/ |
||||
|
||||
/* 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; |
||||
} |
||||
|
||||
h2 { |
||||
margin-top: 0; |
||||
margin-bottom: 0.5rem; |
||||
font-size: 2rem; |
||||
font-weight: 500; |
||||
line-height: 2rem; |
||||
} |
||||
|
||||
.content { |
||||
margin-right: auto; |
||||
margin-left: auto; |
||||
padding-right: 15px; |
||||
padding-left: 15px; |
||||
width: 100%; |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
@media (min-width: 800px) { |
||||
.content { |
||||
max-width: 760px; |
||||
} |
||||
} |
||||
|
||||
/* Components */ |
||||
a, |
||||
a:visited { |
||||
text-decoration: none; |
||||
color: #06f; |
||||
} |
||||
|
||||
a:hover { |
||||
text-decoration: underline; |
||||
color: #003c97; |
||||
} |
||||
|
||||
input[type="text"], |
||||
input[type="password"] { |
||||
height: auto; |
||||
width: 100%; |
||||
font-size: 1rem; |
||||
padding: 0.5rem; |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
button { |
||||
padding: 0.5rem 1rem; |
||||
font-size: 1.25rem; |
||||
line-height: 1.5; |
||||
border: none; |
||||
border-radius: 0.1rem; |
||||
width: 100%; |
||||
} |
||||
|
||||
button.primary { |
||||
color: #fff; |
||||
background-color: #06f; |
||||
} |
||||
|
||||
.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; |
||||
} |
||||
|
||||
.alert.alert-danger { |
||||
color: #6b1922; |
||||
background-color: #f7d5d7; |
||||
border-color: #eab6bb; |
||||
} |
||||
|
||||
.alert.alert-success { |
||||
color: #145222; |
||||
background-color: #d1f0d9; |
||||
border-color: #c2ebcb; |
||||
} |
||||
|
||||
.screenreader { |
||||
position: absolute; |
||||
clip: rect(0 0 0 0); |
||||
height: 1px; |
||||
width: 1px; |
||||
padding: 0; |
||||
border: 0; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
table { |
||||
width: 100%; |
||||
max-width: 100%; |
||||
margin-bottom: 2rem; |
||||
} |
||||
|
||||
.table-striped tr:nth-of-type(2n + 1) { |
||||
background-color: #e1e1e1; |
||||
} |
||||
|
||||
td { |
||||
padding: 0.75rem; |
||||
vertical-align: top; |
||||
} |
||||
|
||||
/* Login / logout layouts */ |
||||
.login-form, |
||||
.logout-form { |
||||
max-width: 340px; |
||||
padding: 0 15px 15px 15px; |
||||
margin: 0 auto 2rem auto; |
||||
box-sizing: border-box; |
||||
} |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* |
||||
* 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.Test; |
||||
|
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hamcrest.Matchers.containsString; |
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
||||
/** |
||||
* @author Daniel Garnier-Moiroux |
||||
* @since 6.4 |
||||
*/ |
||||
public class DefaultResourcesFilterTests { |
||||
|
||||
private final DefaultResourcesFilter filter = DefaultResourcesFilter.css(); |
||||
|
||||
private final MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new Object()).addFilters(this.filter).build(); |
||||
|
||||
@Test |
||||
public void doFilterThenRender() throws Exception { |
||||
this.mockMvc.perform(get("/default-ui.css")) |
||||
.andExpect(status().isOk()) |
||||
.andExpect(content().contentType("text/css;charset=UTF-8")) |
||||
.andExpect(content().string(containsString("body {"))); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenPathDoesNotMatchThenCallsThrough() throws Exception { |
||||
this.mockMvc.perform(get("/does-not-match")).andExpect(status().isNotFound()); |
||||
} |
||||
|
||||
@Test |
||||
void toStringPrintsPathAndResource() { |
||||
assertThat(this.filter.toString()).isEqualTo( |
||||
"DefaultResourcesFilter [matcher=Ant [pattern='/default-ui.css', GET], resource=org/springframework/security/default-ui.css]"); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue