Skip to content

Commit c28a896

Browse files
authored
Merge branch 'spring-projects:main' into gh-16012
2 parents 0243afc + fd4f06a commit c28a896

File tree

108 files changed

+1954
-2054
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+1954
-2054
lines changed

.github/workflows/codeql.yml

Lines changed: 6 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,13 @@
1-
# For most projects, this workflow file will not need changing; you simply need
2-
# to commit it to your repository.
3-
#
4-
# You may wish to alter this file to override the set of languages analyzed,
5-
# or to provide custom queries or build logic.
6-
#
7-
# ******** NOTE ********
8-
# We have attempted to detect the languages in your repository. Please check
9-
# the `language` matrix defined below to confirm you have the correct set of
10-
# supported CodeQL languages.
11-
#
121
name: "CodeQL Advanced"
132

143
on:
15-
push: # run if we update the workflow
4+
push:
5+
pull_request:
166
workflow_dispatch:
177
schedule:
18-
- cron: '39 13 * * 4'
8+
# https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule
9+
- cron: '0 5 * * *'
1910

2011
jobs:
21-
analyze:
22-
name: Analyze (${{ matrix.language }})
23-
# Runner size impacts CodeQL analysis time. To learn more, please see:
24-
# - https://gh.io/recommended-hardware-resources-for-running-codeql
25-
# - https://gh.io/supported-runners-and-hardware-resources
26-
# - https://gh.io/using-larger-runners (GitHub.com only)
27-
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
28-
runs-on: ubuntu-latest
29-
permissions:
30-
# required for all workflows
31-
security-events: write
32-
33-
# required to fetch internal or private CodeQL packs
34-
packages: read
35-
36-
# only required for workflows in private repositories
37-
actions: read
38-
contents: read
39-
40-
strategy:
41-
fail-fast: false
42-
matrix:
43-
include:
44-
- language: actions
45-
build-mode: none
46-
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
47-
# Use `c-cpp` to analyze code written in C, C++ or both
48-
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
49-
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
50-
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
51-
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
52-
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
53-
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
54-
steps:
55-
- name: Checkout repository
56-
uses: actions/checkout@v4
57-
58-
# Add any setup steps before running the `github/codeql-action/init` action.
59-
# This includes steps like installing compilers or runtimes (`actions/setup-node`
60-
# or others). This is typically only required for manual builds.
61-
# - name: Setup runtime (example)
62-
# uses: actions/setup-example@v1
63-
64-
# Initializes the CodeQL tools for scanning.
65-
- name: Initialize CodeQL
66-
uses: github/codeql-action/init@v3
67-
with:
68-
languages: ${{ matrix.language }}
69-
build-mode: ${{ matrix.build-mode }}
70-
# If you wish to specify custom queries, you can do so here or in a config file.
71-
# By default, queries listed here will override any specified in a config file.
72-
# Prefix the list here with "+" to use these queries and those in the config file.
73-
74-
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
75-
queries: security-extended,security-and-quality
76-
77-
- name: Perform CodeQL Analysis
78-
uses: github/codeql-action/analyze@v3
79-
with:
80-
category: "/language:${{matrix.language}}"
12+
codeql-analysis-call:
13+
uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1

.github/workflows/continuous-integration-workflow.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
toolchain: 17
4040
with:
4141
java-version: ${{ matrix.java-version }}
42-
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
42+
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
4343
secrets: inherit
4444
deploy-artifacts:
4545
name: Deploy Artifacts

aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -144,7 +144,7 @@ private void privateMethod() {
144144
protected void protectedMethod() {
145145
}
146146

147-
@PreAuthorize("hasRole('X')")
147+
@PreAuthorize("hasRole('A')")
148148
void publicCallsPrivate() {
149149
privateMethod();
150150
}

buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ class RepositoryConventionPlugin implements Plugin<Project> {
8080
}
8181
url = 'https://repo.spring.io/release/'
8282
}
83+
forceMavenRepositories.findAll { it.startsWith('https://') || it.startsWith('file://') }.each { mavenUrl ->
84+
maven {
85+
url mavenUrl
86+
}
87+
}
8388
}
8489
}
8590

config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyDataConfiguration.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,13 +16,22 @@
1616

1717
package org.springframework.security.config.annotation.method.configuration;
1818

19+
import java.util.List;
20+
1921
import org.springframework.aop.framework.AopInfrastructureBean;
2022
import org.springframework.beans.factory.config.BeanDefinition;
2123
import org.springframework.context.annotation.Bean;
2224
import org.springframework.context.annotation.Configuration;
2325
import org.springframework.context.annotation.Role;
26+
import org.springframework.core.Ordered;
27+
import org.springframework.data.domain.PageImpl;
28+
import org.springframework.data.domain.SliceImpl;
29+
import org.springframework.data.geo.GeoPage;
30+
import org.springframework.data.geo.GeoResult;
31+
import org.springframework.data.geo.GeoResults;
2432
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
2533
import org.springframework.security.authorization.AuthorizationProxyFactory;
34+
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
2635
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
2736

2837
@Configuration(proxyBeanMethods = false)
@@ -34,4 +43,45 @@ static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(Authorizat
3443
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
3544
}
3645

46+
@Bean
47+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
48+
DataTargetVisitor dataTargetVisitor() {
49+
return new DataTargetVisitor();
50+
}
51+
52+
private static final class DataTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor, Ordered {
53+
54+
private static final int DEFAULT_ORDER = 200;
55+
56+
@Override
57+
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
58+
if (target instanceof GeoResults<?> geoResults) {
59+
return new GeoResults<>(proxyFactory.proxy(geoResults.getContent()), geoResults.getAverageDistance());
60+
}
61+
if (target instanceof GeoResult<?> geoResult) {
62+
return new GeoResult<>(proxyFactory.proxy(geoResult.getContent()), geoResult.getDistance());
63+
}
64+
if (target instanceof GeoPage<?> geoPage) {
65+
GeoResults<?> results = new GeoResults<>(proxyFactory.proxy(geoPage.getContent()),
66+
geoPage.getAverageDistance());
67+
return new GeoPage<>(results, geoPage.getPageable(), geoPage.getTotalElements());
68+
}
69+
if (target instanceof PageImpl<?> page) {
70+
List<?> content = proxyFactory.proxy(page.getContent());
71+
return new PageImpl<>(content, page.getPageable(), page.getTotalElements());
72+
}
73+
if (target instanceof SliceImpl<?> slice) {
74+
List<?> content = proxyFactory.proxy(slice.getContent());
75+
return new SliceImpl<>(content, slice.getPageable(), slice.hasNext());
76+
}
77+
return null;
78+
}
79+
80+
@Override
81+
public int getOrder() {
82+
return DEFAULT_ORDER;
83+
}
84+
85+
}
86+
3787
}

config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,48 @@
1616

1717
package org.springframework.security.config.annotation.method.configuration;
1818

19+
import java.util.List;
1920
import java.util.Map;
2021

22+
import jakarta.servlet.http.HttpServletRequest;
23+
import jakarta.servlet.http.HttpServletResponse;
24+
2125
import org.springframework.beans.factory.config.BeanDefinition;
2226
import org.springframework.context.annotation.Bean;
2327
import org.springframework.context.annotation.Configuration;
2428
import org.springframework.context.annotation.Role;
2529
import org.springframework.http.HttpEntity;
2630
import org.springframework.http.ResponseEntity;
31+
import org.springframework.security.access.AccessDeniedException;
2732
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
33+
import org.springframework.security.web.util.ThrowableAnalyzer;
34+
import org.springframework.web.servlet.HandlerExceptionResolver;
2835
import org.springframework.web.servlet.ModelAndView;
2936
import org.springframework.web.servlet.View;
37+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
38+
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
3039

3140
@Configuration
32-
class AuthorizationProxyWebConfiguration {
41+
class AuthorizationProxyWebConfiguration implements WebMvcConfigurer {
3342

3443
@Bean
3544
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
3645
AuthorizationAdvisorProxyFactory.TargetVisitor webTargetVisitor() {
3746
return new WebTargetVisitor();
3847
}
3948

49+
@Override
50+
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
51+
for (int i = 0; i < resolvers.size(); i++) {
52+
HandlerExceptionResolver resolver = resolvers.get(i);
53+
if (resolver instanceof DefaultHandlerExceptionResolver) {
54+
resolvers.add(i, new AccessDeniedExceptionResolver());
55+
return;
56+
}
57+
}
58+
resolvers.add(new AccessDeniedExceptionResolver());
59+
}
60+
4061
static class WebTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor {
4162

4263
@Override
@@ -51,7 +72,7 @@ public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target
5172
if (target instanceof ModelAndView mav) {
5273
View view = mav.getView();
5374
String viewName = mav.getViewName();
54-
Map<String, Object> model = (Map<String, Object>) proxyFactory.proxy(mav.getModel());
75+
Map<String, Object> model = proxyFactory.proxy(mav.getModel());
5576
ModelAndView proxied = (view != null) ? new ModelAndView(view, model)
5677
: new ModelAndView(viewName, model);
5778
proxied.setStatus(mav.getStatus());
@@ -62,4 +83,24 @@ public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target
6283

6384
}
6485

86+
static class AccessDeniedExceptionResolver implements HandlerExceptionResolver {
87+
88+
final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();
89+
90+
@Override
91+
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
92+
Exception ex) {
93+
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
94+
Throwable accessDeniedException = this.throwableAnalyzer
95+
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
96+
if (accessDeniedException != null) {
97+
return new ModelAndView((model, req, res) -> {
98+
throw ex;
99+
});
100+
}
101+
return null;
102+
}
103+
104+
}
105+
65106
}

config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,9 +19,11 @@
1919
import java.util.ArrayList;
2020
import java.util.LinkedHashMap;
2121
import java.util.List;
22+
import java.util.function.Supplier;
2223

2324
import io.micrometer.observation.ObservationRegistry;
2425
import jakarta.servlet.http.HttpServletRequest;
26+
import jakarta.servlet.http.HttpServletResponse;
2527

2628
import org.springframework.context.ApplicationContext;
2729
import org.springframework.security.access.AccessDeniedException;
@@ -34,20 +36,25 @@
3436
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
3537
import org.springframework.security.web.access.ObservationMarkingAccessDeniedHandler;
3638
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
39+
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
3740
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
3841
import org.springframework.security.web.csrf.CsrfFilter;
3942
import org.springframework.security.web.csrf.CsrfLogoutHandler;
43+
import org.springframework.security.web.csrf.CsrfToken;
4044
import org.springframework.security.web.csrf.CsrfTokenRepository;
45+
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
4146
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
4247
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
4348
import org.springframework.security.web.csrf.MissingCsrfTokenException;
49+
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
4450
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
4551
import org.springframework.security.web.session.InvalidSessionStrategy;
4652
import org.springframework.security.web.util.matcher.AndRequestMatcher;
4753
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
4854
import org.springframework.security.web.util.matcher.OrRequestMatcher;
4955
import org.springframework.security.web.util.matcher.RequestMatcher;
5056
import org.springframework.util.Assert;
57+
import org.springframework.util.StringUtils;
5158

5259
/**
5360
* Adds
@@ -214,6 +221,21 @@ public CsrfConfigurer<H> sessionAuthenticationStrategy(
214221
return this;
215222
}
216223

224+
/**
225+
* <p>
226+
* Sensible CSRF defaults when used in combination with a single page application.
227+
* Creates a cookie-based token repository and a custom request handler to resolve the
228+
* actual token value instead of the encoded token.
229+
* </p>
230+
* @return the {@link CsrfConfigurer} for further customizations
231+
* @since 7.0
232+
*/
233+
public CsrfConfigurer<H> spa() {
234+
this.csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
235+
this.requestHandler = new SpaCsrfTokenRequestHandler();
236+
return this;
237+
}
238+
217239
@SuppressWarnings("unchecked")
218240
@Override
219241
public void configure(H http) {
@@ -375,4 +397,27 @@ protected IgnoreCsrfProtectionRegistry chainRequestMatchers(List<RequestMatcher>
375397

376398
}
377399

400+
private static final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
401+
402+
private final CsrfTokenRequestAttributeHandler plain = new CsrfTokenRequestAttributeHandler();
403+
404+
private final CsrfTokenRequestAttributeHandler xor = new XorCsrfTokenRequestAttributeHandler();
405+
406+
SpaCsrfTokenRequestHandler() {
407+
this.xor.setCsrfRequestAttributeName(null);
408+
}
409+
410+
@Override
411+
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
412+
this.xor.handle(request, response, csrfToken);
413+
}
414+
415+
@Override
416+
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
417+
String headerValue = request.getHeader(csrfToken.getHeaderName());
418+
return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken);
419+
}
420+
421+
}
422+
378423
}

0 commit comments

Comments
 (0)