Skip to content

Commit d656dc5

Browse files
authored
Merge branch 'spring-projects:main' into main
2 parents aedbc70 + 4691124 commit d656dc5

File tree

98 files changed

+3912
-795
lines changed

Some content is hidden

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

98 files changed

+3912
-795
lines changed

CONTRIBUTING.adoc

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,41 +89,30 @@ We are excited for your pull request! :heart:
8989
Please do your best to follow these steps.
9090
Don't worry if you don't get them all correct the first time, we will help you.
9191

92-
[[sign-cla]]
93-
1. All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
92+
1. [[sign-cla]] All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
9493
For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring].
95-
[[create-an-issue]]
96-
1. Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
94+
2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
9795
For typos and straightforward bug fixes, starting with a pull request is encouraged.
9896
Please include a description for context and motivation.
9997
Note that the team may close your pull request if it's not a fit for the project.
100-
[[choose-a-branch]]
101-
1. Always check out the branch indicated in the milestone and submit pull requests against it (for example, for milestone `5.8.3` use the `5.8.x` branch).
98+
3. [[choose-a-branch]] Always check out the branch indicated in the milestone and submit pull requests against it (for example, for milestone `5.8.3` use the `5.8.x` branch).
10299
If there is no milestone, choose `main`.
103100
Once merged, the fix will be forwarded-ported to applicable branches including `main`.
104-
[[create-a-local-branch]]
105-
1. Create a local branch
101+
4. [[create-a-local-branch]] Create a local branch
106102
If this is for an issue, consider a branch name with the issue number, like `gh-22276`.
107-
[[write-tests]]
108-
1. Add documentation and JUnit Tests for your changes.
109-
[[update-copyright]]
110-
1. In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year.
111-
[[add-since]]
112-
1. If on `main`, add `@since` JavaDoc attributes to new public APIs that your PR adds
113-
[[change-rnc]]
114-
1. If you are updating the XSD, please instead update the RNC file and then run `./gradlew :spring-security-config:rncToXsd`.
115-
[[format-code]]
116-
1. For each commit, build the code using `./gradlew format check`.
103+
5. [[write-tests]] Add documentation and JUnit Tests for your changes.
104+
6. [[update-copyright]] In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year.
105+
7. [[add-since]] If on `main`, add `@since` JavaDoc attributes to new public APIs that your PR adds
106+
8. [[change-rnc]] If you are updating the XSD, please instead update the RNC file and then run `./gradlew :spring-security-config:rncToXsd`.
107+
9. [[format-code]] For each commit, build the code using `./gradlew format check`.
117108
This command ensures the code meets most of <<code-style,the style guide>>; a notable exception is import order.
118-
[[commit-atomically]]
119-
1. Choose the granularity of your commits consciously and squash commits that represent
109+
10. [[commit-atomically]] Choose the granularity of your commits consciously and squash commits that represent
120110
multiple edits or corrections of the same logical change.
121111
See https://git-scm.com/book/en/Git-Tools-Rewriting-History[Rewriting History section of Pro Git] for an overview of streamlining the commit history.
122-
[[format-commit-messages]]
123-
1. Format commit messages using 55 characters for the subject line, 72 characters per line
112+
11. [[format-commit-messages]] Format commit messages using 55 characters for the subject line, 72 characters per line
124113
for the description, followed by the issue fixed, for example, `Closes gh-22276`.
125114
See the https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines[Commit Guidelines section of Pro Git] for best practices around commit messages, and use `git log` to see some examples.
126-
Present tense is preferred.
115+
Favor imperative tense over present tense (use "Fix" instead of "Fixes"); avoid past tense (use "Fix" instead of "Fixed").
127116
+
128117
[indent=0]
129118
----

config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java

Lines changed: 3 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.
@@ -31,6 +31,7 @@
3131
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
3232
import org.springframework.security.web.authentication.logout.LogoutFilter;
3333
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenFilter;
34+
import org.springframework.security.web.authentication.ott.OneTimeTokenAuthenticationFilter;
3435
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
3536
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
3637
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
@@ -101,6 +102,7 @@ final class FilterOrderRegistration {
101102
"org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter",
102103
order.next());
103104
put(UsernamePasswordAuthenticationFilter.class, order.next());
105+
put(OneTimeTokenAuthenticationFilter.class, order.next());
104106
order.next(); // gh-8105
105107
put(DefaultResourcesFilter.class, order.next());
106108
put(DefaultLoginPageGeneratingFilter.class, order.next());

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@
5757
public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
5858
extends AbstractHttpConfigurer<AuthorizeHttpRequestsConfigurer<H>, H> {
5959

60-
static final AuthorizationManager<RequestAuthorizationContext> permitAllAuthorizationManager = (a,
61-
o) -> new AuthorizationDecision(true);
60+
static final AuthorizationDecision AUTHORIZATION_DECISION = new AuthorizationDecision(true);
61+
62+
static final AuthorizationManager<RequestAuthorizationContext> PERMIT_ALL_AUTHORIZATION_MANAGER = (a,
63+
o) -> AUTHORIZATION_DECISION;
6264

6365
private final AuthorizationManagerRequestMatcherRegistry registry;
6466

@@ -287,7 +289,7 @@ public AuthorizedUrl not() {
287289
* customizations
288290
*/
289291
public AuthorizationManagerRequestMatcherRegistry permitAll() {
290-
return access(permitAllAuthorizationManager);
292+
return access(PERMIT_ALL_AUTHORIZATION_MANAGER);
291293
}
292294

293295
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http
6363
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
6464
}
6565
else {
66-
httpConfigurer.addFirst(matcher, AuthorizeHttpRequestsConfigurer.permitAllAuthorizationManager);
66+
httpConfigurer.addFirst(matcher, AuthorizeHttpRequestsConfigurer.PERMIT_ALL_AUTHORIZATION_MANAGER);
6767
}
6868
}
6969
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.web.configurers.oauth2.server.resource;
18+
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.regex.Matcher;
22+
import java.util.regex.Pattern;
23+
24+
import jakarta.servlet.http.HttpServletRequest;
25+
26+
import org.springframework.http.HttpHeaders;
27+
import org.springframework.http.HttpStatus;
28+
import org.springframework.security.authentication.AuthenticationManager;
29+
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
30+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
31+
import org.springframework.security.core.Authentication;
32+
import org.springframework.security.oauth2.core.OAuth2AccessToken;
33+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
34+
import org.springframework.security.oauth2.core.OAuth2Error;
35+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
36+
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationProvider;
37+
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken;
38+
import org.springframework.security.web.authentication.AuthenticationConverter;
39+
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
40+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
41+
import org.springframework.security.web.authentication.AuthenticationFilter;
42+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
43+
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
44+
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
45+
import org.springframework.security.web.util.matcher.RequestMatcher;
46+
import org.springframework.util.CollectionUtils;
47+
import org.springframework.util.StringUtils;
48+
49+
/**
50+
* @author Joe Grandja
51+
* @since 6.5
52+
* @see DPoPAuthenticationProvider
53+
*/
54+
final class DPoPAuthenticationConfigurer<B extends HttpSecurityBuilder<B>>
55+
extends AbstractHttpConfigurer<DPoPAuthenticationConfigurer<B>, B> {
56+
57+
private RequestMatcher requestMatcher;
58+
59+
private AuthenticationConverter authenticationConverter;
60+
61+
private AuthenticationSuccessHandler authenticationSuccessHandler;
62+
63+
private AuthenticationFailureHandler authenticationFailureHandler;
64+
65+
@Override
66+
public void configure(B http) {
67+
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
68+
http.authenticationProvider(new DPoPAuthenticationProvider(authenticationManager));
69+
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager,
70+
getAuthenticationConverter());
71+
authenticationFilter.setRequestMatcher(getRequestMatcher());
72+
authenticationFilter.setSuccessHandler(getAuthenticationSuccessHandler());
73+
authenticationFilter.setFailureHandler(getAuthenticationFailureHandler());
74+
authenticationFilter.setSecurityContextRepository(new RequestAttributeSecurityContextRepository());
75+
authenticationFilter = postProcess(authenticationFilter);
76+
http.addFilter(authenticationFilter);
77+
}
78+
79+
private RequestMatcher getRequestMatcher() {
80+
if (this.requestMatcher == null) {
81+
this.requestMatcher = new DPoPRequestMatcher();
82+
}
83+
return this.requestMatcher;
84+
}
85+
86+
private AuthenticationConverter getAuthenticationConverter() {
87+
if (this.authenticationConverter == null) {
88+
this.authenticationConverter = new DPoPAuthenticationConverter();
89+
}
90+
return this.authenticationConverter;
91+
}
92+
93+
private AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
94+
if (this.authenticationSuccessHandler == null) {
95+
this.authenticationSuccessHandler = (request, response, authentication) -> {
96+
// No-op - will continue on filter chain
97+
};
98+
}
99+
return this.authenticationSuccessHandler;
100+
}
101+
102+
private AuthenticationFailureHandler getAuthenticationFailureHandler() {
103+
if (this.authenticationFailureHandler == null) {
104+
this.authenticationFailureHandler = new AuthenticationEntryPointFailureHandler(
105+
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
106+
}
107+
return this.authenticationFailureHandler;
108+
}
109+
110+
private static final class DPoPRequestMatcher implements RequestMatcher {
111+
112+
@Override
113+
public boolean matches(HttpServletRequest request) {
114+
String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
115+
if (!StringUtils.hasText(authorization)) {
116+
return false;
117+
}
118+
return StringUtils.startsWithIgnoreCase(authorization, OAuth2AccessToken.TokenType.DPOP.getValue());
119+
}
120+
121+
}
122+
123+
private static final class DPoPAuthenticationConverter implements AuthenticationConverter {
124+
125+
private static final Pattern AUTHORIZATION_PATTERN = Pattern.compile("^DPoP (?<token>[a-zA-Z0-9-._~+/]+=*)$",
126+
Pattern.CASE_INSENSITIVE);
127+
128+
@Override
129+
public Authentication convert(HttpServletRequest request) {
130+
List<String> authorizationList = Collections.list(request.getHeaders(HttpHeaders.AUTHORIZATION));
131+
if (CollectionUtils.isEmpty(authorizationList)) {
132+
return null;
133+
}
134+
if (authorizationList.size() != 1) {
135+
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST,
136+
"Found multiple Authorization headers.", null);
137+
throw new OAuth2AuthenticationException(error);
138+
}
139+
String authorization = authorizationList.get(0);
140+
if (!StringUtils.startsWithIgnoreCase(authorization, OAuth2AccessToken.TokenType.DPOP.getValue())) {
141+
return null;
142+
}
143+
Matcher matcher = AUTHORIZATION_PATTERN.matcher(authorization);
144+
if (!matcher.matches()) {
145+
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "DPoP access token is malformed.",
146+
null);
147+
throw new OAuth2AuthenticationException(error);
148+
}
149+
String accessToken = matcher.group("token");
150+
List<String> dPoPProofList = Collections
151+
.list(request.getHeaders(OAuth2AccessToken.TokenType.DPOP.getValue()));
152+
if (CollectionUtils.isEmpty(dPoPProofList) || dPoPProofList.size() != 1) {
153+
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST,
154+
"DPoP proof is missing or invalid.", null);
155+
throw new OAuth2AuthenticationException(error);
156+
}
157+
String dPoPProof = dPoPProofList.get(0);
158+
return new DPoPAuthenticationToken(accessToken, dPoPProof, request.getMethod(),
159+
request.getRequestURL().toString());
160+
}
161+
162+
}
163+
164+
}

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

Lines changed: 4 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.
@@ -152,6 +152,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
152152

153153
private final ApplicationContext context;
154154

155+
private final DPoPAuthenticationConfigurer<H> dPoPAuthenticationConfigurer = new DPoPAuthenticationConfigurer<>();
156+
155157
private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
156158

157159
private BearerTokenResolver bearerTokenResolver;
@@ -283,6 +285,7 @@ public void configure(H http) {
283285
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
284286
filter = postProcess(filter);
285287
http.addFilter(filter);
288+
this.dPoPAuthenticationConfigurer.configure(http);
286289
}
287290

288291
private void validateConfiguration() {

0 commit comments

Comments
 (0)