1
0
mirror of synced 2026-07-10 21:30:04 +00:00

Compare commits

...

29 Commits

Author SHA1 Message Date
github-actions[bot] 20332fedb0 Release 5.8.13 2024-06-17 15:22:54 +00:00
dependabot[bot] 2537a033f5 Bump org.springframework:spring-framework-bom from 5.3.36 to 5.3.37
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.36 to 5.3.37.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.36...v5.3.37)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 21:19:30 -07:00
Steve Riesenberg f622d8e2e2 Polish gh-15235 2024-06-13 16:00:03 -05:00
Harsh4902 92cab2b678 Add 'Required Dependencies' section in ldap.adoc file
Closes gh-14699
2024-06-13 16:00:02 -05:00
dependabot[bot] 254709ce0b Bump com.gradle.develocity from 3.17.4 to 3.17.5
Bumps com.gradle.develocity from 3.17.4 to 3.17.5.

---
updated-dependencies:
- dependency-name: com.gradle.develocity
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-12 20:46:14 -07:00
dependabot[bot] e9c1b7b0ae Bump io.projectreactor:reactor-bom from 2020.0.44 to 2020.0.45
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.44 to 2020.0.45.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.44...2020.0.45)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 21:30:14 -07:00
dependabot[bot] 2b322da9ac Bump io.projectreactor.netty:reactor-netty from 1.0.45 to 1.0.46
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.45 to 1.0.46.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.45...v1.0.46)

---
updated-dependencies:
- dependency-name: io.projectreactor.netty:reactor-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 21:13:46 -07:00
Josh Cummings 6aabd768a8 Pick MvcRequestMatcher for MockMvc requests
Closes gh-13849
2024-06-06 13:17:43 -06:00
Josh Cummings cdd626644e Use Request-Level Servlet Context
Spring Security cannot use the ServletContext attached
to the ApplicationContext since there may be child
ApplicationContext's with their own ServletContext.

Because of that, it is necessary to always use the
ServletContext attached to the request.

Closes gh-14418
2024-06-03 17:41:51 -06:00
Josh Cummings 5a798e93f1 Polish MVC Tests
Issue gh-14418
2024-06-03 17:41:51 -06:00
dependabot[bot] aa88404d69 Bump org.hsqldb:hsqldb from 2.7.2 to 2.7.3
Bumps org.hsqldb:hsqldb from 2.7.2 to 2.7.3.

---
updated-dependencies:
- dependency-name: org.hsqldb:hsqldb
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-02 20:34:36 -07:00
Steve Riesenberg dcb8c563e8 Fix ArrayIndexOutOfBoundsException
Issue gh-13310
Closes gh-15184
2024-05-31 18:12:21 -05:00
Steve Riesenberg e34621ec2c Polish gh-14977 2024-05-29 16:23:00 -05:00
JANG 1695d03b72 Assert WebSession is not null
Issue gh-14975
2024-05-29 14:55:37 -05:00
Josh Cummings e7ea4091a0 Migrate SampleLDIF to UnboundID
Closes gh-15089
2024-05-24 15:03:53 -06:00
dependabot[bot] 235dfc4dfd Bump @antora/collector-extension in /docs
---
updated-dependencies:
- dependency-name: "@antora/collector-extension"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-24 07:57:40 -07:00
Josh Cummings 092a63d1e0 Conditionally Add Conventions Plugin 2024-05-23 16:56:35 -06:00
Caio Henrique 896dd93313 fix: add correction to java example in multitenancy.adoc
Closes gh-15146
2024-05-23 14:16:59 -03:00
Marcus Hert Da Coregio c7b739eb3f Fix broken link to jaspan article
Closes gh-14358
2024-05-23 14:04:10 -03:00
dependabot[bot] 32e860523c Bump @springio/antora-extensions from 1.10.0 to 1.11.1 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.10.0 to 1.11.1.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.10.0...v1.11.1)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-23 04:10:21 -07:00
dependabot[bot] 58232b1172 Bump org.springframework:spring-framework-bom from 5.3.35 to 5.3.36
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.35 to 5.3.36.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.35...v5.3.36)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-22 21:09:25 -07:00
dependabot[bot] 0e8fd1cd6c Bump com.github.spullara.mustache.java:compiler from 0.9.11 to 0.9.13
Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.11 to 0.9.13.
- [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.11...mustache.java-0.9.13)

---
updated-dependencies:
- dependency-name: com.github.spullara.mustache.java:compiler
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 10:38:07 -07:00
dependabot[bot] 1f001f8a6d Bump com.gradle.develocity from 3.17.2 to 3.17.4
Bumps com.gradle.develocity from 3.17.2 to 3.17.4.

---
updated-dependencies:
- dependency-name: com.gradle.develocity
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 08:36:59 -07:00
dependabot[bot] 3fdfa98172 Bump org.springframework:spring-framework-bom from 5.3.34 to 5.3.35
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.34 to 5.3.35.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.34...v5.3.35)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 18:05:26 -07:00
dependabot[bot] 90f5141880 Bump io.projectreactor.netty:reactor-netty from 1.0.44 to 1.0.45
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.44 to 1.0.45.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.44...v1.0.45)

---
updated-dependencies:
- dependency-name: io.projectreactor.netty:reactor-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 15:48:30 -07:00
dependabot[bot] ab6667eb5c Bump io.projectreactor:reactor-bom from 2020.0.43 to 2020.0.44
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.43 to 2020.0.44.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.43...2020.0.44)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 15:35:59 -07:00
Josh Cummings 4b10c2daac Turn Off Build Scans
Currently, --scan is preventing PR builds from completing.

Issue gh-15120
2024-05-20 16:20:43 -06:00
Josh Cummings 63e24a3026 Adjust JavaDoc Classpath
Issue gh-14931
2024-05-20 12:42:01 -06:00
github-actions[bot] 9027dceb71 Update Antora Spring UI to v0.4.15 2024-05-20 13:51:18 -03:00
25 changed files with 464 additions and 132 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
java-version: '11'
distribution: 'adopt'
- name: Build with Gradle
run: ./gradlew clean build --continue --scan
run: ./gradlew clean build --continue
generate-docs:
name: Generate Docs
runs-on: ubuntu-latest
@@ -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.
@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
@@ -43,11 +44,13 @@ import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/**
@@ -322,43 +325,22 @@ public abstract class AbstractRequestMatcherRegistry<C> {
if (servletContext == null) {
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
}
boolean isProgrammaticApiAvailable = isProgrammaticApiAvailable(servletContext);
List<RequestMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
if (isProgrammaticApiAvailable) {
matchers.add(resolve(ant, mvc, servletContext));
}
else {
this.logger
.warn("The ServletRegistration API was not available at startup time. This may be due to a misconfiguration; "
+ "if you are using AbstractSecurityWebApplicationInitializer, please double-check the recommendations outlined in "
+ "https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#abstractsecuritywebapplicationinitializer-with-spring-mvc");
matchers.add(new DeferredRequestMatcher((request) -> resolve(ant, mvc, request.getServletContext()),
mvc, ant));
}
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
}
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
}
private static boolean isProgrammaticApiAvailable(ServletContext servletContext) {
try {
servletContext.getServletRegistrations();
return true;
}
catch (UnsupportedOperationException ex) {
return false;
}
}
private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) {
Map<String, ? extends ServletRegistration> registrations = mappableServletRegistrations(servletContext);
if (registrations.isEmpty()) {
return ant;
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
if (!hasDispatcherServlet(registrations)) {
return ant;
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
}
ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
if (dispatcherServlet != null) {
@@ -593,34 +575,29 @@ public abstract class AbstractRequestMatcherRegistry<C> {
static class DeferredRequestMatcher implements RequestMatcher {
final Function<HttpServletRequest, RequestMatcher> requestMatcherFactory;
final Function<ServletContext, RequestMatcher> requestMatcherFactory;
final AtomicReference<String> description = new AtomicReference<>();
volatile RequestMatcher requestMatcher;
final Map<ServletContext, RequestMatcher> requestMatchers = new ConcurrentHashMap<>();
DeferredRequestMatcher(Function<HttpServletRequest, RequestMatcher> resolver, RequestMatcher... candidates) {
this.requestMatcherFactory = (request) -> {
if (this.requestMatcher == null) {
synchronized (this) {
if (this.requestMatcher == null) {
this.requestMatcher = resolver.apply(request);
}
}
}
return this.requestMatcher;
};
DeferredRequestMatcher(Function<ServletContext, RequestMatcher> resolver, RequestMatcher... candidates) {
this.requestMatcherFactory = (sc) -> this.requestMatchers.computeIfAbsent(sc, resolver);
this.description.set("Deferred " + Arrays.toString(candidates));
}
RequestMatcher requestMatcher(ServletContext servletContext) {
return this.requestMatcherFactory.apply(servletContext);
}
@Override
public boolean matches(HttpServletRequest request) {
return this.requestMatcherFactory.apply(request).matches(request);
return this.requestMatcherFactory.apply(request.getServletContext()).matches(request);
}
@Override
public MatchResult matcher(HttpServletRequest request) {
return this.requestMatcherFactory.apply(request).matcher(request);
return this.requestMatcherFactory.apply(request.getServletContext()).matcher(request);
}
@Override
@@ -630,27 +607,70 @@ public abstract class AbstractRequestMatcherRegistry<C> {
}
static class MockMvcRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
return request.getAttribute("org.springframework.test.web.servlet.MockMvc.MVC_RESULT_ATTRIBUTE") != null;
}
}
static class DispatcherServletRequestMatcher implements RequestMatcher {
private final ServletContext servletContext;
DispatcherServletRequestMatcher(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public boolean matches(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(name, "Failed to find servlet [" + name + "] in the servlet context");
try {
Class<?> clazz = Class.forName(registration.getClassName());
return DispatcherServlet.class.isAssignableFrom(clazz);
}
catch (ClassNotFoundException ex) {
return false;
}
}
}
static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
private final AntPathRequestMatcher ant;
private final MvcRequestMatcher mvc;
private final ServletContext servletContext;
private final RequestMatcher dispatcherServlet;
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
ServletContext servletContext) {
this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(),
new DispatcherServletRequestMatcher(servletContext)));
}
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
RequestMatcher dispatcherServlet) {
this.ant = ant;
this.mvc = mvc;
this.servletContext = servletContext;
this.dispatcherServlet = dispatcherServlet;
}
RequestMatcher requestMatcher(HttpServletRequest request) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc;
}
return this.ant;
}
@Override
public boolean matches(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
if (isDispatcherServlet(registration)) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc.matches(request);
}
return this.ant.matches(request);
@@ -658,27 +678,12 @@ public abstract class AbstractRequestMatcherRegistry<C> {
@Override
public MatchResult matcher(HttpServletRequest request) {
String name = request.getHttpServletMapping().getServletName();
ServletRegistration registration = this.servletContext.getServletRegistration(name);
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
if (isDispatcherServlet(registration)) {
if (this.dispatcherServlet.matches(request)) {
return this.mvc.matcher(request);
}
return this.ant.matcher(request);
}
private boolean isDispatcherServlet(ServletRegistration registration) {
Class<?> dispatcherServlet = ClassUtils
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
try {
Class<?> clazz = Class.forName(registration.getClassName());
return dispatcherServlet.isAssignableFrom(clazz);
}
catch (ClassNotFoundException ex) {
return false;
}
}
@Override
public String toString() {
return "DispatcherServletDelegating [" + "ant = " + this.ant + ", mvc = " + this.mvc + "]";
@@ -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.
@@ -30,27 +30,35 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.TestMockHttpServletMappings;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.DispatcherServletDelegatingRequestMatcher;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
* Tests for {@link AbstractRequestMatcherRegistry}.
@@ -206,18 +214,65 @@ public class AbstractRequestMatcherRegistryTests {
mockMvcIntrospector(true);
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
MockHttpServletRequest request = new MockHttpServletRequest();
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(AntPathRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(AntPathRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class);
servletContext.addServlet("servletTwo", Servlet.class);
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(AntPathRequestMatcher.class);
}
// gh-14418
@Test
public void requestMatchersWhenNoDispatcherServletMockMvcThenMvcRequestMatcherType() throws Exception {
MockServletContext servletContext = new MockServletContext();
try (SpringTestContext spring = new SpringTestContext(this)) {
spring.register(MockMvcConfiguration.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.matcherRegistry.setApplicationContext(spring.getContext());
MockMvc mvc = MockMvcBuilders.webAppContextSetup(spring.getContext()).build();
MockHttpServletRequest request = mvc.perform(get("/")).andReturn().getRequest();
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(MvcRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(MvcRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class);
servletContext.addServlet("servletTwo", Servlet.class);
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).asInstanceOf(type(DispatcherServletDelegatingRequestMatcher.class))
.extracting((matcher) -> matcher.requestMatcher(request))
.isInstanceOf(MvcRequestMatcher.class);
}
}
@Test
@@ -381,11 +436,13 @@ public class AbstractRequestMatcherRegistryTests {
return requestMatchers;
}
private static List<RequestMatcher> unwrap(List<RequestMatcher> wrappedMatchers) {
private List<RequestMatcher> unwrap(List<RequestMatcher> wrappedMatchers) {
List<RequestMatcher> requestMatchers = new ArrayList<>();
for (RequestMatcher requestMatcher : wrappedMatchers) {
if (requestMatcher instanceof AbstractRequestMatcherRegistry.DeferredRequestMatcher) {
requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher);
if (requestMatcher instanceof DeferredRequestMatcher) {
DeferredRequestMatcher deferred = (DeferredRequestMatcher) requestMatcher;
WebApplicationContext web = (WebApplicationContext) getApplicationContext();
requestMatchers.add(deferred.requestMatcher(web.getServletContext()));
}
else {
requestMatchers.add(requestMatcher);
@@ -396,4 +453,11 @@ public class AbstractRequestMatcherRegistryTests {
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class MockMvcConfiguration {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -76,7 +76,7 @@ public class AuthorizeRequestsTests {
@BeforeEach
public void setup() {
this.servletContext = spy(MockServletContext.mvc());
this.request = new MockHttpServletRequest("GET", "");
this.request = new MockHttpServletRequest(this.servletContext, "GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -75,7 +75,7 @@ public class HttpSecuritySecurityMatchersTests {
@BeforeEach
public void setup() throws Exception {
this.request = new MockHttpServletRequest("GET", "");
this.request = new MockHttpServletRequest(MockServletContext.mvc(), "GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -72,7 +72,7 @@ public class UrlAuthorizationConfigurerTests {
@BeforeEach
public void setup() {
this.request = new MockHttpServletRequest("GET", "");
this.request = new MockHttpServletRequest(MockServletContext.mvc(), "GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
+1 -1
View File
@@ -30,7 +30,7 @@ urls:
redirect_facility: httpd
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.11/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
snapshot: true
runtime:
log:
@@ -10,6 +10,41 @@ However, despite leveraging a username/password for authentication it does not i
There are many different scenarios for how an LDAP server may be configured so Spring Security's LDAP provider is fully configurable.
It uses separate strategy interfaces for authentication and role retrieval and provides default implementations which can be configured to handle a wide range of situations.
[[servlet-authentication-ldap-required-dependencies]]
== Required Dependencies
To get started, add the `spring-security-ldap` dependency to your project.
When using Spring Boot, add the following dependencies:
.Spring Security LDAP Dependencies
[tabs]
======
Maven::
+
[source,xml,role="primary"]
----
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
----
Gradle::
+
[source,groovy,role="secondary"]
----
depenendencies {
implementation "org.springframework.boot:spring-boot-starter-data-ldap"
implementation "org.springframework.security:spring-security-ldap"
}
----
======
[[servlet-authentication-ldap-prerequisites]]
== Prerequisites
@@ -81,14 +116,14 @@ dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org
member: uid=admin,ou=people,dc=springframework,dc=org
member: uid=user,ou=people,dc=springframework,dc=org
dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
member: uid=admin,ou=people,dc=springframework,dc=org
----
[[servlet-authentication-ldap-unboundid]]
@@ -52,9 +52,9 @@ If you have more than one in your application context, you need to specify which
[[remember-me-persistent-token]]
== Persistent Token Approach
This approach is based on the article https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice] with some minor modifications footnote:[Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily.
This approach is based on the article https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice[Improved Persistent Login Cookie Best Practice] with some minor modifications footnote:[Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily.
There is a discussion on this in the comments section of this article.].
To use the this approach with namespace configuration, you would supply a datasource reference:
To use this approach with namespace configuration, you would supply a datasource reference:
[source,xml]
----
@@ -339,7 +339,7 @@ Java::
[source,java,role="primary"]
----
@Bean
JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) {
JWTProcessor jwtProcessor(JWTClaimsSetAwareJWSKeySelector keySelector) {
ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor();
jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector);
+2 -2
View File
@@ -2,9 +2,9 @@
"dependencies": {
"antora": "3.2.0-alpha.4",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.3",
"@antora/collector-extension": "1.0.0-alpha.4",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.10.0",
"@springio/antora-extensions": "1.11.1",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
+1
View File
@@ -20,6 +20,7 @@
<property name="avoidStaticImportExcludes" value="org.springframework.security.web.csrf.CsrfTokenAssert.*" />
<property name="avoidStaticImportExcludes" value="org.springframework.security.web.util.matcher.AntPathRequestMatcher.*" />
<property name="avoidStaticImportExcludes" value="org.springframework.security.web.util.matcher.RegexRequestMatcher.*" />
<property name="avoidStaticImportExcludes" value="org.assertj.core.api.InstanceOfAssertFactories.*"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
+1 -1
View File
@@ -1,5 +1,5 @@
springBootVersion=2.7.12
version=5.8.13-SNAPSHOT
version=5.8.13
samplesBranch=5.8.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+5 -5
View File
@@ -15,7 +15,7 @@ org-mockito = "4.8.1"
org-opensaml4 = "4.1.0"
org-opensaml3 = "3.4.6"
org-slf4j = "1.7.36"
org-springframework = "5.3.34"
org-springframework = "5.3.37"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.2.13"
@@ -30,8 +30,8 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:4.0.14"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:6.5.1"
io-mockk = "io.mockk:mockk:1.13.3"
io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.44"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.43"
io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.46"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.45"
io-projectreactor-tools-blockhound = "io.projectreactor.tools:blockhound:1.0.9.RELEASE"
io-r2dbc-r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "io-r2dbc" }
io-r2dbc-r2dbc-spi-test = { module = "io.r2dbc:r2dbc-spi-test", version.ref = "io-r2dbc" }
@@ -69,7 +69,7 @@ org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet",
org-eclipse-persistence-javax-persistence = "org.eclipse.persistence:javax.persistence:2.2.1"
org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-hibernate-entitymanager = "org.hibernate:hibernate-entitymanager:5.6.15.Final"
org-hsqldb = "org.hsqldb:hsqldb:2.7.2"
org-hsqldb = "org.hsqldb:hsqldb:2.7.3"
org-jasig-cas-client-cas-client-core = "org.jasig.cas.client:cas-client-core:3.6.4"
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "org-jetbrains-kotlin" }
@@ -103,7 +103,7 @@ org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2"
io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0"
org-gretty-gretty = "org.gretty:gretty:3.0.9"
com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0"
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.11"
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13"
org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1"
org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.29.4"
org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.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.
@@ -19,6 +19,7 @@ package org.springframework.security.messaging.web.csrf;
import java.util.Base64;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.util.Assert;
/**
* Copied from
@@ -43,26 +44,26 @@ final class XorCsrfTokenUtils {
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
if (actualBytes.length != tokenSize * 2) {
return null;
}
// extract token and random bytes
int randomBytesSize = actualBytes.length - tokenSize;
byte[] xoredCsrf = new byte[tokenSize];
byte[] randomBytes = new byte[randomBytesSize];
byte[] randomBytes = new byte[tokenSize];
System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);
System.arraycopy(actualBytes, 0, randomBytes, 0, tokenSize);
System.arraycopy(actualBytes, tokenSize, xoredCsrf, 0, tokenSize);
byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
return Utf8.decode(csrfBytes);
}
private static byte[] xorCsrf(byte[] randomBytes, byte[] csrfBytes) {
int len = Math.min(randomBytes.length, csrfBytes.length);
Assert.isTrue(randomBytes.length == csrfBytes.length, "arrays must be equal length");
int len = csrfBytes.length;
byte[] xoredCsrf = new byte[len];
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, csrfBytes.length);
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, len);
for (int i = 0; i < len; i++) {
xoredCsrf[i] ^= randomBytes[i];
}
@@ -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.
@@ -16,6 +16,7 @@
package org.springframework.security.messaging.web.csrf;
import java.util.Base64;
import java.util.HashMap;
import org.junit.jupiter.api.BeforeEach;
@@ -141,6 +142,73 @@ public class XorCsrfChannelInterceptorTests {
this.interceptor.preSend(message(), this.channel);
}
// gh-13310, gh-15184
@Test
public void preSendWhenCsrfBytesIsShorterThanRandomBytesThenThrowsInvalidCsrfTokenException() {
/*
* Token format: 3 random pad bytes + 2 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), actualToken);
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenCsrfBytesIsLongerThanRandomBytesThenThrowsInvalidCsrfTokenException() {
/*
* Token format: 3 random pad bytes + 4 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99, 98, 97 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), actualToken);
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenTokenBytesIsShorterThanActualBytesThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("header", "param", "a");
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), csrfToken);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenTokenBytesIsLongerThanActualBytesThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("header", "param", "abcde");
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), csrfToken);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
// gh-13310, gh-15184
@Test
public void preSendWhenActualBytesIsEmptyThenThrowsInvalidCsrfTokenException() {
this.messageHeaders.setNativeHeader(this.token.getHeaderName(), "");
this.messageHeaders.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
// @formatter:off
assertThatExceptionOfType(InvalidCsrfTokenException.class)
.isThrownBy(() -> this.interceptor.preSend(message(), mock(MessageChannel.class)));
// @formatter:on
}
private Message<String> message() {
return MessageBuilder.withPayload("message").copyHeaders(this.messageHeaders.toMap()).build();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -94,14 +94,10 @@ public final class WebSessionServerOAuth2AuthorizedClientRepository implements S
// @formatter:on
}
@SuppressWarnings("unchecked")
private Map<String, OAuth2AuthorizedClient> getAuthorizedClients(WebSession session) {
Map<String, OAuth2AuthorizedClient> authorizedClients = (session != null)
? (Map<String, OAuth2AuthorizedClient>) session.getAttribute(this.sessionAttributeName) : null;
if (authorizedClients == null) {
authorizedClients = new HashMap<>();
}
return authorizedClients;
Assert.notNull(session, "session cannot be null");
Map<String, OAuth2AuthorizedClient> authorizedClients = session.getAttribute(this.sessionAttributeName);
return (authorizedClients != null) ? authorizedClients : new HashMap<>();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -17,6 +17,7 @@
package org.springframework.security.oauth2.client.web.server;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
@@ -24,10 +25,12 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
@@ -202,4 +205,28 @@ public class WebSessionServerOAuth2AuthorizedClientRepositoryTests {
assertThat(loadedAuthorizedClient2).isSameAs(authorizedClient2);
}
@Test
public void saveAuthorizedClientWhenSessionIsNullThenThrowIllegalArgumentException() {
ServerWebExchange exchange = mock(ServerWebExchange.class);
given(exchange.getSession()).willReturn(Mono.empty());
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.registration1, this.principalName1,
mock(OAuth2AccessToken.class));
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, exchange).block())
.withMessage("session cannot be null");
// @formatter:on
}
@Test
public void removeAuthorizedClientWhenSessionIsNullThenThrowIllegalArgumentException() {
ServerWebExchange exchange = mock(ServerWebExchange.class);
given(exchange.getSession()).willReturn(Mono.empty());
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, exchange).block())
.withMessage("session cannot be null");
// @formatter:on
}
}
@@ -96,6 +96,7 @@ project.tasks.matching { t -> t.name == "sourcesJar"}.configureEach {
javadoc {
classpath += sourceSets.opensaml3Main.runtimeClasspath + sourceSets.opensaml4Main.runtimeClasspath
source += sourceSets.opensaml3Main.allJava + sourceSets.opensaml4Main.allJava
}
+6 -2
View File
@@ -5,8 +5,12 @@ pluginManagement {
}
plugins {
id "com.gradle.develocity" version "3.17.2"
id "io.spring.ge.conventions" version "0.0.17"
id "com.gradle.develocity" version "3.17.5"
id "io.spring.ge.conventions" version "0.0.17" apply false
}
if (System.getenv("DEVELOCITY_ACCESS_KEY")) {
apply plugin: "io.spring.ge.conventions"
}
dependencyResolutionManagement {
@@ -32,8 +32,8 @@ import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.util.Assert;
/**
* {@link RememberMeServices} implementation based on Barry Jaspan's
* <a href="http://jaspan.com/improved_persistent_login_cookie_best_practice">Improved
* {@link RememberMeServices} implementation based on Barry Jaspan's <a href=
* "https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice">Improved
* Persistent Login Cookie Best Practice</a>.
*
* There is a slight modification to the described approach, in that the username is not
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -84,17 +84,16 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
if (actualBytes.length != tokenSize * 2) {
return null;
}
// extract token and random bytes
int randomBytesSize = actualBytes.length - tokenSize;
byte[] xoredCsrf = new byte[tokenSize];
byte[] randomBytes = new byte[randomBytesSize];
byte[] randomBytes = new byte[tokenSize];
System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);
System.arraycopy(actualBytes, 0, randomBytes, 0, tokenSize);
System.arraycopy(actualBytes, tokenSize, xoredCsrf, 0, tokenSize);
byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
return Utf8.decode(csrfBytes);
@@ -114,9 +113,10 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
}
private static byte[] xorCsrf(byte[] randomBytes, byte[] csrfBytes) {
int len = Math.min(randomBytes.length, csrfBytes.length);
Assert.isTrue(randomBytes.length == csrfBytes.length, "arrays must be equal length");
int len = csrfBytes.length;
byte[] xoredCsrf = new byte[len];
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, csrfBytes.length);
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, len);
for (int i = 0; i < len; i++) {
xoredCsrf[i] ^= randomBytes[i];
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -77,17 +77,16 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
if (actualBytes.length != tokenSize * 2) {
return null;
}
// extract token and random bytes
int randomBytesSize = actualBytes.length - tokenSize;
byte[] xoredCsrf = new byte[tokenSize];
byte[] randomBytes = new byte[randomBytesSize];
byte[] randomBytes = new byte[tokenSize];
System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);
System.arraycopy(actualBytes, 0, randomBytes, 0, tokenSize);
System.arraycopy(actualBytes, tokenSize, xoredCsrf, 0, tokenSize);
byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
return Utf8.decode(csrfBytes);
@@ -107,9 +106,10 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT
}
private static byte[] xorCsrf(byte[] randomBytes, byte[] csrfBytes) {
int len = Math.min(randomBytes.length, csrfBytes.length);
Assert.isTrue(randomBytes.length == csrfBytes.length, "arrays must be equal length");
int len = csrfBytes.length;
byte[] xoredCsrf = new byte[len];
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, csrfBytes.length);
System.arraycopy(csrfBytes, 0, xoredCsrf, 0, len);
for (int i = 0; i < len; i++) {
xoredCsrf[i] ^= randomBytes[i];
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -44,6 +44,9 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
*/
public class XorCsrfTokenRequestAttributeHandlerTests {
/*
* Token format: 3 random pad bytes + 3 padded bytes.
*/
private static final byte[] XOR_CSRF_TOKEN_BYTES = new byte[] { 1, 1, 1, 96, 99, 98 };
private static final String XOR_CSRF_TOKEN_VALUE = Base64.getEncoder().encodeToString(XOR_CSRF_TOKEN_BYTES);
@@ -203,6 +206,58 @@ public class XorCsrfTokenRequestAttributeHandlerTests {
assertThat(tokenValue).isEqualTo(this.token.getToken());
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsShorterThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 2 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.request.setParameter(this.token.getParameterName(), actualToken);
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, this.token);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsLongerThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 4 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99, 98, 97 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.request.setParameter(this.token.getParameterName(), actualToken);
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, this.token);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsShorterThanActualBytesThenReturnsNull() {
this.request.setParameter(this.token.getParameterName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "a");
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, csrfToken);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsLongerThanActualBytesThenReturnsNull() {
this.request.setParameter(this.token.getParameterName(), XOR_CSRF_TOKEN_VALUE);
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "abcde");
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, csrfToken);
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenActualBytesIsEmptyThenReturnsNull() {
this.request.setParameter(this.token.getParameterName(), "");
String tokenValue = this.handler.resolveCsrfTokenValue(this.request, this.token);
assertThat(tokenValue).isNull();
}
private static Answer<Void> fillByteArray() {
return (invocation) -> {
byte[] bytes = invocation.getArgument(0);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -46,6 +46,9 @@ import static org.mockito.Mockito.verify;
*/
public class XorServerCsrfTokenRequestAttributeHandlerTests {
/*
* Token format: 3 random pad bytes + 3 padded bytes.
*/
private static final byte[] XOR_CSRF_TOKEN_BYTES = new byte[] { 1, 1, 1, 96, 99, 98 };
private static final String XOR_CSRF_TOKEN_VALUE = Base64.getEncoder().encodeToString(XOR_CSRF_TOKEN_BYTES);
@@ -188,6 +191,78 @@ public class XorServerCsrfTokenRequestAttributeHandlerTests {
StepVerifier.create(csrfToken).expectNext(this.token.getToken()).verifyComplete();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsShorterThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 2 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), actualToken))
.build();
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, this.token).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenCsrfBytesIsLongerThanRandomBytesThenReturnsNull() {
/*
* Token format: 3 random pad bytes + 4 padded bytes.
*/
byte[] actualBytes = { 1, 1, 1, 96, 99, 98, 97 };
String actualToken = Base64.getEncoder().encodeToString(actualBytes);
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), actualToken))
.build();
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, this.token).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsShorterThanActualBytesThenReturnsNull() {
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE))
.build();
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "a");
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, csrfToken).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenTokenBytesIsLongerThanActualBytesThenReturnsNull() {
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE))
.build();
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "abcde");
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, csrfToken).block();
assertThat(tokenValue).isNull();
}
// gh-13310, gh-15184
@Test
public void resolveCsrfTokenValueWhenActualBytesIsEmptyThenReturnsNull() {
this.exchange = MockServerWebExchange
.builder(MockServerHttpRequest.post("/")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(this.token.getHeaderName(), ""))
.build();
String tokenValue = this.handler.resolveCsrfTokenValue(this.exchange, this.token).block();
assertThat(tokenValue).isNull();
}
private static Answer<Void> fillByteArray() {
return (invocation) -> {
byte[] bytes = invocation.getArgument(0);