Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d5b0c4627 | |||
| 45ce971d6c |
@@ -1,16 +1,25 @@
|
||||
name: Merge Dependabot PR
|
||||
|
||||
name: Dependabot Auto-Merge
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- '*.x'
|
||||
- 'main'
|
||||
|
||||
run-name: Merge Dependabot PR ${{ github.ref_name }}
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
merge-dependabot-pr:
|
||||
permissions: write-all
|
||||
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@v7
|
||||
with:
|
||||
mergeArguments: --auto --rebase
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security'
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -14,7 +14,7 @@ permissions:
|
||||
jobs:
|
||||
snapshot-test:
|
||||
name: Test Against Snapshots
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -31,6 +31,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -17,7 +17,7 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
deploy-artifacts:
|
||||
name: Deploy Artifacts
|
||||
needs: [ build]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@releases/v1
|
||||
with:
|
||||
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
|
||||
default-publish-milestones-central: true
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
deploy-schema:
|
||||
name: Deploy Schema
|
||||
needs: [ build ]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@releases/v1
|
||||
with:
|
||||
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
|
||||
java-version: 25
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
perform-release:
|
||||
name: Perform Release
|
||||
needs: [ deploy-artifacts, deploy-schema ]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@releases/v1
|
||||
with:
|
||||
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}
|
||||
project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
|
||||
@@ -64,6 +64,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -16,7 +16,7 @@ permissions:
|
||||
jobs:
|
||||
perform-release:
|
||||
name: Perform Release
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
with:
|
||||
should-perform-release: true
|
||||
project-version: ${{ inputs.version }}
|
||||
|
||||
@@ -9,7 +9,6 @@ permissions:
|
||||
jobs:
|
||||
upgrade_wrapper:
|
||||
name: Execution
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Git configuration
|
||||
|
||||
@@ -30,6 +30,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -46,6 +46,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -12,9 +12,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
update-antora-ui-spring:
|
||||
name: Update on Supported Branches
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on Supported Branches
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.5.x', '7.0.x', 'main' ]
|
||||
@@ -26,9 +25,8 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
antora-file-path: 'docs/antora-playbook.yml'
|
||||
update-antora-ui-spring-docs-build:
|
||||
name: Update on docs-build
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on docs-build
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf
|
||||
name: Update
|
||||
|
||||
@@ -9,7 +9,7 @@ permissions:
|
||||
jobs:
|
||||
update-scheduled-release-version:
|
||||
name: Update Scheduled Release Version
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
secrets: inherit
|
||||
send-notification:
|
||||
name: Send Notification
|
||||
@@ -18,6 +18,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
+2
-2
@@ -31,7 +31,7 @@ If you have a question, check Stack Overflow using
|
||||
https://stackoverflow.com/questions/tagged/spring-security+or+spring-ldap+or+spring-authorization-server+or+spring-session?tab=Newest[this list of tags].
|
||||
Find an existing discussion, or start a new one if necessary.
|
||||
|
||||
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
|
||||
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
|
||||
Reading those discussions helps you to learn about the issue, and helps us to make a decision.
|
||||
|
||||
[[find-an-issue]]
|
||||
@@ -94,7 +94,7 @@ Don't worry if you don't get them all correct the first time, we will help you.
|
||||
|
||||
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.
|
||||
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].
|
||||
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 to discuss with the team first to determine the right fix or enhancement.
|
||||
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.
|
||||
For typos and straightforward bug fixes, starting with a pull request is encouraged.
|
||||
Please include a description for context and motivation.
|
||||
Note that the team may close your pull request if it's not a fit for the project.
|
||||
|
||||
+1
-2
@@ -31,7 +31,6 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Sources method security metadata from major JSR 250 security annotations.
|
||||
@@ -109,7 +108,7 @@ public class Jsr250MethodSecurityMetadataSource extends AbstractFallbackMethodSe
|
||||
if (role == null) {
|
||||
return role;
|
||||
}
|
||||
if (!StringUtils.hasLength(this.defaultRolePrefix)) {
|
||||
if (this.defaultRolePrefix == null || this.defaultRolePrefix.length() == 0) {
|
||||
return role;
|
||||
}
|
||||
if (role.startsWith(this.defaultRolePrefix)) {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.acls.aot.hint;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.security.acls.domain.AclImpl;
|
||||
import org.springframework.security.acls.domain.AuditLogger;
|
||||
import org.springframework.security.acls.domain.BasePermission;
|
||||
import org.springframework.security.acls.domain.GrantedAuthoritySid;
|
||||
import org.springframework.security.acls.domain.ObjectIdentityImpl;
|
||||
import org.springframework.security.acls.domain.PrincipalSid;
|
||||
import org.springframework.security.acls.model.AccessControlEntry;
|
||||
import org.springframework.security.acls.model.Acl;
|
||||
import org.springframework.security.acls.model.AuditableAccessControlEntry;
|
||||
import org.springframework.security.acls.model.ObjectIdentity;
|
||||
import org.springframework.security.acls.model.Sid;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} for ACL (Access Control List) classes.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
class AclRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
registerAclDomainHints(hints);
|
||||
registerJdbcSchemaHints(hints);
|
||||
}
|
||||
|
||||
private void registerAclDomainHints(RuntimeHints hints) {
|
||||
// Register core ACL domain types
|
||||
Stream
|
||||
.of(Acl.class, AccessControlEntry.class, AuditableAccessControlEntry.class, ObjectIdentity.class, Sid.class,
|
||||
AclImpl.class, AccessControlEntry.class, AuditLogger.class, ObjectIdentityImpl.class,
|
||||
PrincipalSid.class, GrantedAuthoritySid.class, BasePermission.class)
|
||||
.forEach((c) -> hints.reflection()
|
||||
.registerType(TypeReference.of(c),
|
||||
(builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS)));
|
||||
|
||||
}
|
||||
|
||||
private void registerJdbcSchemaHints(RuntimeHints hints) {
|
||||
String[] sqlFiles = new String[] { "createAclSchema.sql", "createAclSchemaMySQL.sql",
|
||||
"createAclSchemaOracle.sql", "createAclSchemaPostgres.sql", "createAclSchemaSqlServer.sql",
|
||||
"createAclSchemaWithAclClassIdType.sql", "select.sql" };
|
||||
for (String sqlFile : sqlFiles) {
|
||||
Resource sqlResource = new ClassPathResource(sqlFile);
|
||||
if (sqlResource.exists()) {
|
||||
hints.resources().registerResource(sqlResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* AOT and native image hint support for ACLs.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.acls.aot.hint;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.security.acls.aot.hint.AclRuntimeHints
|
||||
+6
-11
@@ -34,7 +34,6 @@ import org.springframework.util.Assert;
|
||||
* and using the current URL minus the artifact and the corresponding value.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Ngoc Nhan
|
||||
*/
|
||||
final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
|
||||
implements ServiceAuthenticationDetails {
|
||||
@@ -75,9 +74,10 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails that)) {
|
||||
if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails)) {
|
||||
return false;
|
||||
}
|
||||
ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
|
||||
return this.serviceUrl.equals(that.getServiceUrl());
|
||||
}
|
||||
|
||||
@@ -101,11 +101,7 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
|
||||
/**
|
||||
* If present, removes the artifactParameterName and the corresponding value from the
|
||||
* query String.
|
||||
* @param request the current {@link HttpServletRequest} to obtain the
|
||||
* {@link #getServiceUrl()} from.
|
||||
* @param artifactPattern the {@link Pattern} that will be used to clean up the query
|
||||
* string from containing the artifact name and value. This can be created using
|
||||
* {@link #createArtifactPattern(String)}.
|
||||
* @param request
|
||||
* @return the query String minus the artifactParameterName and the corresponding
|
||||
* value.
|
||||
*/
|
||||
@@ -115,7 +111,7 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
|
||||
return null;
|
||||
}
|
||||
String result = artifactPattern.matcher(query).replaceFirst("");
|
||||
if (result.isEmpty()) {
|
||||
if (result.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
// strip off the trailing & only if the artifact was the first query param
|
||||
@@ -126,9 +122,8 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
|
||||
* Creates a {@link Pattern} that can be passed into the constructor. This allows the
|
||||
* {@link Pattern} to be reused for every instance of
|
||||
* {@link DefaultServiceAuthenticationDetails}.
|
||||
* @param artifactParameterName the artifactParameterName that is removed from the
|
||||
* current URL. The result becomes the service url. Cannot be null or an empty String.
|
||||
* @return a {@link Pattern}
|
||||
* @param artifactParameterName
|
||||
* @return
|
||||
*/
|
||||
static Pattern createArtifactPattern(String artifactParameterName) {
|
||||
Assert.hasLength(artifactParameterName, "artifactParameterName is expected to have a length");
|
||||
|
||||
-2
@@ -36,7 +36,6 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
@@ -303,7 +302,6 @@ public class AuthenticationConfiguration {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract("!null -> !null; null -> null")
|
||||
public String encode(CharSequence rawPassword) {
|
||||
return getPasswordEncoder().encode(rawPassword);
|
||||
}
|
||||
|
||||
-2
@@ -30,7 +30,6 @@ import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
@@ -294,7 +293,6 @@ class HttpSecurityConfiguration {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract("!null -> !null; null -> null")
|
||||
public String encode(CharSequence rawPassword) {
|
||||
return getPasswordEncoder().encode(rawPassword);
|
||||
}
|
||||
|
||||
-4
@@ -124,10 +124,6 @@ class AuthorizationFilterParser implements BeanDefinitionParser {
|
||||
List<Element> interceptMessages = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
|
||||
for (Element interceptMessage : interceptMessages) {
|
||||
String accessExpression = interceptMessage.getAttribute(ATT_ACCESS);
|
||||
if (!StringUtils.hasText(accessExpression)) {
|
||||
parserContext.getReaderContext().error("access attribute cannot be empty or null", interceptMessage);
|
||||
continue;
|
||||
}
|
||||
BeanDefinitionBuilder authorizationManager = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(WebExpressionAuthorizationManager.class);
|
||||
authorizationManager.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
|
||||
+1
-2
@@ -142,11 +142,10 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
||||
ManagedMap<BeanMetadataElement, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<>();
|
||||
for (Element urlElt : urlElts) {
|
||||
String access = urlElt.getAttribute(ATT_ACCESS);
|
||||
String path = urlElt.getAttribute(ATT_PATTERN);
|
||||
if (!StringUtils.hasText(access)) {
|
||||
parserContext.getReaderContext().error("access attribute cannot be empty or null", urlElt);
|
||||
continue;
|
||||
}
|
||||
String path = urlElt.getAttribute(ATT_PATTERN);
|
||||
String matcherRef = urlElt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_REQUEST_MATCHER_REF);
|
||||
boolean hasMatcherRef = StringUtils.hasText(matcherRef);
|
||||
if (!hasMatcherRef && !StringUtils.hasText(path)) {
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.Principal;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
@@ -246,15 +245,12 @@ import org.springframework.security.web.savedrequest.SimpleSavedRequest;
|
||||
import org.springframework.security.web.server.firewall.ServerExchangeRejectedException;
|
||||
import org.springframework.security.web.session.HttpSessionCreatedEvent;
|
||||
import org.springframework.security.web.session.HttpSessionIdChangedEvent;
|
||||
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
|
||||
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
|
||||
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
|
||||
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
|
||||
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
|
||||
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
|
||||
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
|
||||
import org.springframework.security.web.webauthn.api.Bytes;
|
||||
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
|
||||
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
|
||||
import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
|
||||
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput;
|
||||
@@ -262,17 +258,12 @@ import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExte
|
||||
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs;
|
||||
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
|
||||
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
|
||||
import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses;
|
||||
import org.springframework.security.web.webauthn.api.TestBytes;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentials;
|
||||
@@ -280,7 +271,6 @@ import org.springframework.security.web.webauthn.api.UserVerificationRequirement
|
||||
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication;
|
||||
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken;
|
||||
import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest;
|
||||
import org.springframework.security.web.webauthn.management.TestPublicKeyCredentialRpEntities;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
final class SerializationSamples {
|
||||
@@ -889,36 +879,6 @@ final class SerializationSamples {
|
||||
generatorByClassName.put(CredentialPropertiesOutput.ExtensionOutput.class,
|
||||
(r) -> new CredentialPropertiesOutput(true).getOutput());
|
||||
|
||||
AttestationConveyancePreference attestationConveyancePreference = AttestationConveyancePreference.DIRECT;
|
||||
ResidentKeyRequirement residentKeyRequirement = ResidentKeyRequirement.REQUIRED;
|
||||
AuthenticatorSelectionCriteria authenticatorSelectionCriteria = AuthenticatorSelectionCriteria.builder()
|
||||
.authenticatorAttachment(AuthenticatorAttachment.PLATFORM)
|
||||
.residentKey(residentKeyRequirement)
|
||||
.userVerification(UserVerificationRequirement.REQUIRED)
|
||||
.build();
|
||||
PublicKeyCredentialParameters publicKeyCredentialParameters = PublicKeyCredentialParameters.RS256;
|
||||
PublicKeyCredentialRpEntity publicKeyCredentialRpEntity = TestPublicKeyCredentialRpEntities.createRpEntity()
|
||||
.build();
|
||||
|
||||
generatorByClassName.put(AttestationConveyancePreference.class, (r) -> attestationConveyancePreference);
|
||||
generatorByClassName.put(ResidentKeyRequirement.class, (r) -> residentKeyRequirement);
|
||||
generatorByClassName.put(AuthenticatorSelectionCriteria.class, (r) -> authenticatorSelectionCriteria);
|
||||
generatorByClassName.put(COSEAlgorithmIdentifier.class, ((r) -> COSEAlgorithmIdentifier.RS256));
|
||||
generatorByClassName.put(PublicKeyCredentialParameters.class, (r) -> publicKeyCredentialParameters);
|
||||
generatorByClassName.put(PublicKeyCredentialRpEntity.class, (r) -> publicKeyCredentialRpEntity);
|
||||
generatorByClassName.put(PublicKeyCredentialCreationOptions.class,
|
||||
(o) -> TestPublicKeyCredentialCreationOptions.createPublicKeyCredentialCreationOptions()
|
||||
.extensions(inputs)
|
||||
.attestation(attestationConveyancePreference)
|
||||
.authenticatorSelection(authenticatorSelectionCriteria)
|
||||
.challenge(TestBytes.get())
|
||||
.excludeCredentials(List.of(descriptor))
|
||||
.rp(publicKeyCredentialRpEntity)
|
||||
.pubKeyCredParams(publicKeyCredentialParameters)
|
||||
.timeout(Duration.ofMinutes(5))
|
||||
.user(TestPublicKeyCredentialUserEntities.userEntity().id(TestBytes.get()).build())
|
||||
.build());
|
||||
|
||||
// One-Time Token
|
||||
DefaultOneTimeToken oneTimeToken = new DefaultOneTimeToken(UUID.randomUUID().toString(), "user",
|
||||
Instant.now().plusSeconds(300));
|
||||
|
||||
+1
-2
@@ -222,8 +222,7 @@ public class WebSecurityConfigurationTests {
|
||||
// SEC-2773
|
||||
@Test
|
||||
public void getMethodDelegatingApplicationListenerWhenWebSecurityConfigurationThenIsStatic() {
|
||||
Method method = ClassUtils.getMethod(WebSecurityConfiguration.class, "delegatingApplicationListener",
|
||||
(Class<?>[]) null);
|
||||
Method method = ClassUtils.getMethod(WebSecurityConfiguration.class, "delegatingApplicationListener", null);
|
||||
assertThat(Modifier.isStatic(method.getModifiers())).isTrue();
|
||||
}
|
||||
|
||||
|
||||
-48
@@ -337,54 +337,6 @@ public class InterceptUrlConfigTests {
|
||||
assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* gh-18503
|
||||
*/
|
||||
@Test
|
||||
public void configWhenInterceptUrlMissingAccessThenException() {
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() -> this.spring.configLocations(this.xml("MissingAccess")).autowire())
|
||||
.withMessageContaining("access attribute cannot be empty or null");
|
||||
}
|
||||
|
||||
/**
|
||||
* gh-18503
|
||||
*/
|
||||
@Test
|
||||
public void configWhenInterceptUrlEmptyAccessThenException() {
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() -> this.spring.configLocations(this.xml("EmptyAccess")).autowire())
|
||||
.withMessageContaining("access attribute cannot be empty or null");
|
||||
}
|
||||
|
||||
/**
|
||||
* gh-18503
|
||||
*/
|
||||
@Test
|
||||
public void configWhenInterceptUrlValidAccessThenLoads() {
|
||||
assertThatNoException().isThrownBy(() -> this.spring.configLocations(this.xml("ValidAccess")).autowire());
|
||||
}
|
||||
|
||||
/**
|
||||
* gh-18503
|
||||
*/
|
||||
@Test
|
||||
public void configWhenUseAuthorizationManagerFalseAndMissingAccessThenException() {
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() -> this.spring.configLocations(this.xml("MissingAccessLegacy")).autowire())
|
||||
.withMessageContaining("access attribute cannot be empty or null");
|
||||
}
|
||||
|
||||
/**
|
||||
* gh-18503
|
||||
*/
|
||||
@Test
|
||||
public void configWhenUseAuthorizationManagerFalseAndEmptyAccessThenException() {
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() -> this.spring.configLocations(this.xml("EmptyAccessLegacy")).autowire())
|
||||
.withMessageContaining("access attribute cannot be empty or null");
|
||||
}
|
||||
|
||||
private static RequestPostProcessor adminCredentials() {
|
||||
return httpBasic("admin", "password");
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http>
|
||||
<intercept-url pattern="/admin/**" access="" />
|
||||
</http>
|
||||
|
||||
<user-service>
|
||||
<user name="user" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
</b:beans>
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http use-authorization-manager="false">
|
||||
<intercept-url pattern="/admin/**" access=""/>
|
||||
</http>
|
||||
|
||||
<user-service>
|
||||
<user name="user" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
</b:beans>
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http>
|
||||
<intercept-url pattern="/admin/**"/>
|
||||
</http>
|
||||
|
||||
<user-service>
|
||||
<user name="user" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
</b:beans>
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http use-authorization-manager="false">
|
||||
<intercept-url pattern="/admin/**"/>
|
||||
</http>
|
||||
|
||||
<user-service>
|
||||
<user name="user" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
</b:beans>
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http>
|
||||
<intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />
|
||||
<http-basic />
|
||||
</http>
|
||||
|
||||
<user-service>
|
||||
<user name="user" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
</b:beans>
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-3
@@ -38,7 +38,6 @@ import org.springframework.util.function.SingletonSupplier;
|
||||
* @author Luke Taylor
|
||||
* @author Evgeniy Cheban
|
||||
* @author Steve Riesenberg
|
||||
* @author Ngoc Nhan
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class SecurityExpressionRoot<T extends @Nullable Object> implements SecurityExpressionOperations {
|
||||
@@ -209,8 +208,7 @@ public abstract class SecurityExpressionRoot<T extends @Nullable Object> impleme
|
||||
/**
|
||||
* Convenience method to access {@link Authentication#getPrincipal()} from
|
||||
* {@link #getAuthentication()}
|
||||
* @return the {@code Principal} being authenticated or the authenticated principal
|
||||
* after authentication.
|
||||
* @return
|
||||
*/
|
||||
public @Nullable Object getPrincipal() {
|
||||
return getAuthentication().getPrincipal();
|
||||
|
||||
-17
@@ -35,8 +35,6 @@ import org.springframework.security.authentication.CredentialsExpiredException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.authentication.ProviderNotFoundException;
|
||||
import org.springframework.security.authentication.RememberMeAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
|
||||
import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent;
|
||||
import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent;
|
||||
@@ -45,7 +43,6 @@ import org.springframework.security.authentication.event.AuthenticationFailureLo
|
||||
import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent;
|
||||
import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent;
|
||||
import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
|
||||
@@ -63,7 +60,6 @@ class CoreSecurityRuntimeHints implements RuntimeHintsRegistrar {
|
||||
registerExceptionEventsHints(hints);
|
||||
registerExpressionEvaluationHints(hints);
|
||||
registerMethodSecurityHints(hints);
|
||||
registerAdditionalAuthenticationTypes(hints);
|
||||
hints.resources().registerResourceBundle("org.springframework.security.messages");
|
||||
registerDefaultJdbcSchemaFileHint(hints);
|
||||
registerSecurityContextHints(hints);
|
||||
@@ -118,17 +114,4 @@ class CoreSecurityRuntimeHints implements RuntimeHintsRegistrar {
|
||||
(builder) -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
}
|
||||
|
||||
private void registerAdditionalAuthenticationTypes(RuntimeHints hints) {
|
||||
// RememberMeAuthenticationToken can be stored in the HTTP session and
|
||||
// deserialized via Jackson (RememberMeAuthenticationTokenMixin exists for both
|
||||
// Jackson 2 and 3), so it needs reflection hints in all native image scenarios.
|
||||
Stream
|
||||
.of(RememberMeAuthenticationToken.class, OneTimeTokenAuthentication.class,
|
||||
UsernamePasswordAuthenticationToken.class)
|
||||
.map(TypeReference::of)
|
||||
.forEach((it) -> hints.reflection()
|
||||
.registerType(it, (builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-5
@@ -69,11 +69,7 @@ public final class AuthoritiesAuthorizationManager implements AuthorizationManag
|
||||
|
||||
private boolean isAuthorized(Authentication authentication, Collection<String> authorities) {
|
||||
for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) {
|
||||
String authority = grantedAuthority.getAuthority();
|
||||
if (authority == null) {
|
||||
continue;
|
||||
}
|
||||
if (authorities.contains(authority)) {
|
||||
if (authorities.contains(grantedAuthority.getAuthority())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,17 +87,9 @@ public final class RsaKeyConverters {
|
||||
"Key is not in PEM-encoded PKCS#8 format, please check that the header begins with "
|
||||
+ PKCS8_PEM_HEADER);
|
||||
StringBuilder base64Encoded = new StringBuilder();
|
||||
if (lines.size() == 1) {
|
||||
base64Encoded.append(lines.get(0)
|
||||
.replace(PKCS8_PEM_HEADER, "")
|
||||
.replace(PKCS8_PEM_FOOTER, "")
|
||||
.replaceAll("\\s+", ""));
|
||||
}
|
||||
else {
|
||||
for (String line : lines) {
|
||||
if (RsaKeyConverters.isNotPkcs8Wrapper(line)) {
|
||||
base64Encoded.append(line);
|
||||
}
|
||||
for (String line : lines) {
|
||||
if (RsaKeyConverters.isNotPkcs8Wrapper(line)) {
|
||||
base64Encoded.append(line);
|
||||
}
|
||||
}
|
||||
byte[] pkcs8 = Base64.getDecoder().decode(base64Encoded.toString());
|
||||
@@ -173,15 +165,9 @@ public final class RsaKeyConverters {
|
||||
@Override
|
||||
public @NonNull RSAPublicKey convert(List<String> lines) {
|
||||
StringBuilder base64Encoded = new StringBuilder();
|
||||
if (lines.size() == 1) {
|
||||
base64Encoded.append(
|
||||
lines.get(0).replace(X509_PEM_HEADER, "").replace(X509_PEM_FOOTER, "").replaceAll("\\s+", ""));
|
||||
}
|
||||
else {
|
||||
for (String line : lines) {
|
||||
if (isNotX509PemWrapper(line)) {
|
||||
base64Encoded.append(line);
|
||||
}
|
||||
for (String line : lines) {
|
||||
if (isNotX509PemWrapper(line)) {
|
||||
base64Encoded.append(line);
|
||||
}
|
||||
}
|
||||
byte[] x509 = Base64.getDecoder().decode(base64Encoded.toString());
|
||||
@@ -210,17 +196,9 @@ public final class RsaKeyConverters {
|
||||
@Override
|
||||
public @NonNull RSAPublicKey convert(List<String> lines) {
|
||||
StringBuilder base64Encoded = new StringBuilder();
|
||||
if (lines.size() == 1) {
|
||||
base64Encoded.append(lines.get(0)
|
||||
.replace(X509_CERT_HEADER, "")
|
||||
.replace(X509_CERT_FOOTER, "")
|
||||
.replaceAll("\\s+", ""));
|
||||
}
|
||||
else {
|
||||
for (String line : lines) {
|
||||
if (isNotX509CertificateWrapper(line)) {
|
||||
base64Encoded.append(line);
|
||||
}
|
||||
for (String line : lines) {
|
||||
if (isNotX509CertificateWrapper(line)) {
|
||||
base64Encoded.append(line);
|
||||
}
|
||||
}
|
||||
byte[] x509 = Base64.getDecoder().decode(base64Encoded.toString());
|
||||
|
||||
+1
@@ -88,6 +88,7 @@ public final class FactorGrantedAuthority implements GrantedAuthority {
|
||||
|
||||
private final Instant issuedAt;
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
private FactorGrantedAuthority(String authority, Instant issuedAt) {
|
||||
Assert.notNull(authority, "authority cannot be null");
|
||||
Assert.notNull(issuedAt, "issuedAt cannot be null");
|
||||
|
||||
-1
@@ -31,7 +31,6 @@ class OneTimeTokenAuthenticationTokenTests {
|
||||
|
||||
// gh-18095
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void shouldBeAbleToDeserializeFromJsonWithDefaultTypingActivated() throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModules(SecurityJackson2Modules.getModules(getClass().getClassLoader()));
|
||||
|
||||
-5
@@ -59,7 +59,6 @@ public class OneTimeTokenReactiveAuthenticationManagerTests {
|
||||
private static final String TOKEN = "token";
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
public void constructorWhenOneTimeTokenServiceNullThenIllegalArgumentException() {
|
||||
ReactiveUserDetailsService userDetailsService = mock(ReactiveUserDetailsService.class);
|
||||
// @formatter:off
|
||||
@@ -69,7 +68,6 @@ public class OneTimeTokenReactiveAuthenticationManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
public void constructorWhenUserDetailsServiceNullThenIllegalArgumentException() {
|
||||
ReactiveOneTimeTokenService oneTimeTokenService = mock(ReactiveOneTimeTokenService.class);
|
||||
// @formatter:off
|
||||
@@ -79,7 +77,6 @@ public class OneTimeTokenReactiveAuthenticationManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void authenticateWhenOneTimeTokenAuthenticationTokenIsPresentThenSuccess() {
|
||||
ReactiveOneTimeTokenService oneTimeTokenService = mock(ReactiveOneTimeTokenService.class);
|
||||
given(oneTimeTokenService.consume(ArgumentMatchers.any(OneTimeTokenAuthenticationToken.class)))
|
||||
@@ -106,7 +103,6 @@ public class OneTimeTokenReactiveAuthenticationManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void authenticateWhenInvalidOneTimeTokenAuthenticationTokenIsPresentThenFail() {
|
||||
ReactiveOneTimeTokenService oneTimeTokenService = mock(ReactiveOneTimeTokenService.class);
|
||||
given(oneTimeTokenService.consume(ArgumentMatchers.any(OneTimeTokenAuthenticationToken.class)))
|
||||
@@ -124,7 +120,6 @@ public class OneTimeTokenReactiveAuthenticationManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void authenticateWhenIncorrectTypeOfAuthenticationIsPresentThenFail() {
|
||||
ReactiveOneTimeTokenService oneTimeTokenService = mock(ReactiveOneTimeTokenService.class);
|
||||
given(oneTimeTokenService.consume(ArgumentMatchers.any(OneTimeTokenAuthenticationToken.class)))
|
||||
|
||||
-20
@@ -17,9 +17,7 @@
|
||||
package org.springframework.security.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -32,13 +30,11 @@ import org.springframework.security.core.Authentication;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuthoritiesAuthorizationManager}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Khyojae
|
||||
*/
|
||||
class AuthoritiesAuthorizationManagerTests {
|
||||
|
||||
@@ -87,20 +83,4 @@ class AuthoritiesAuthorizationManagerTests {
|
||||
assertThat(manager.authorize(authentication, Collections.singleton("ROLE_USER")).isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
// gh-18543
|
||||
void authorizeWhenAuthorityIsNullThenDoesNotThrowNullPointerException() {
|
||||
AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager();
|
||||
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password",
|
||||
Collections.singletonList(() -> null));
|
||||
|
||||
Collection<String> authoritiesContainsThrowsNPE = Set.of("ROLE_USER");
|
||||
|
||||
// must be Collection that throws NPE when .contains(null) is invoked
|
||||
// to replicate the issue in gh-18543
|
||||
assertThatNullPointerException().isThrownBy(() -> authoritiesContainsThrowsNPE.contains(null));
|
||||
assertThat(manager.authorize(() -> authentication, authoritiesContainsThrowsNPE).isGranted()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -61,7 +61,7 @@ public class AuthorityAuthorizationManagerTests {
|
||||
|
||||
@Test
|
||||
public void hasAnyRoleWhenNullThenException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAnyRole((String[]) null))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAnyRole(null))
|
||||
.withMessage("roles cannot be empty");
|
||||
}
|
||||
|
||||
@@ -97,8 +97,7 @@ public class AuthorityAuthorizationManagerTests {
|
||||
|
||||
@Test
|
||||
public void hasAnyAuthorityWhenNullThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AuthorityAuthorizationManager.hasAnyAuthority((String[]) null))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAnyAuthority(null))
|
||||
.withMessage("authorities cannot be empty");
|
||||
}
|
||||
|
||||
|
||||
@@ -120,13 +120,6 @@ public class RsaKeyConvertersTests {
|
||||
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(2048);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkcs8WhenConvertingSingleLinePkcs8PrivateKeyThenOk() {
|
||||
RSAPrivateKey key = this.pkcs8.convert(toInputStream(PKCS8_PRIVATE_KEY.replace("\n", "")));
|
||||
Assertions.assertThat(key).isInstanceOf(RSAPrivateCrtKey.class);
|
||||
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(2048);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkcs8WhenConvertingPkcs1PrivateKeyThenIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.pkcs8.convert(toInputStream(PKCS1_PRIVATE_KEY)));
|
||||
@@ -138,24 +131,12 @@ public class RsaKeyConvertersTests {
|
||||
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenConvertingSingleLineX509PublicKeyThenOk() {
|
||||
RSAPublicKey key = this.x509.convert(toInputStream(X509_PUBLIC_KEY.replace("\n", "")));
|
||||
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenConvertingX509CertificateThenOk() {
|
||||
RSAPublicKey key = this.x509.convert(toInputStream(X509_CERTIFICATE));
|
||||
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenConvertingX509SingleLineCertificateThenOk() {
|
||||
RSAPublicKey key = this.x509.convert(toInputStream(X509_CERTIFICATE.replace("\n", "")));
|
||||
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenConvertingDerEncodedX509PublicKeyThenIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.x509.convert(toInputStream(MALFORMED_X509_KEY)));
|
||||
|
||||
+6
-14
@@ -73,8 +73,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Map::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = filtered as Map<String, String>
|
||||
val result = (filtered as Map<String, String>)
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).containsKey("key2")
|
||||
assertThat(result).containsValue("value2")
|
||||
@@ -96,8 +95,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Map::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = filtered as Map<String, String>
|
||||
val result = (filtered as Map<String, String>)
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
|
||||
@@ -121,8 +119,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Collection::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = filtered as Collection<String>
|
||||
val result = (filtered as Collection<String>)
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).contains("string2")
|
||||
}
|
||||
@@ -143,8 +140,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Collection::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = filtered as Collection<String>
|
||||
val result = (filtered as Collection<String>)
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
|
||||
@@ -168,8 +164,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Array<String>::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = filtered as Array<String>
|
||||
val result = (filtered as Array<String>)
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).contains("string2")
|
||||
}
|
||||
@@ -190,8 +185,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Array<String>::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = filtered as Array<String>
|
||||
val result = (filtered as Array<String>)
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
|
||||
@@ -215,7 +209,6 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Stream::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = (filtered as Stream<String>).toList()
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).contains("string2")
|
||||
@@ -237,7 +230,6 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Stream::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = (filtered as Stream<String>).toList()
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
|
||||
+3
-16
@@ -18,23 +18,9 @@ package org.springframework.security.crypto.password;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An abstract {@link PasswordEncoder} that implementers can use for expecting the
|
||||
* password to be non-{@code null}. Each common password API method is accompanied with an
|
||||
* abstract method with a {@code NonNull} prefix. By implementing this, the concrete class
|
||||
* is specifying what to do with the password when it is non-{@code null}, allowing this
|
||||
* class to handle the {@code null} case.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
public abstract class AbstractValidatingPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
@Override
|
||||
@Contract("!null -> !null; null -> null")
|
||||
public final @Nullable String encode(@Nullable CharSequence rawPassword) {
|
||||
if (rawPassword == null) {
|
||||
return null;
|
||||
@@ -46,7 +32,8 @@ public abstract class AbstractValidatingPasswordEncoder implements PasswordEncod
|
||||
|
||||
@Override
|
||||
public final boolean matches(@Nullable CharSequence rawPassword, @Nullable String encodedPassword) {
|
||||
if (!StringUtils.hasLength(rawPassword) || !StringUtils.hasLength(encodedPassword)) {
|
||||
if (rawPassword == null || rawPassword.length() == 0 || encodedPassword == null
|
||||
|| encodedPassword.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
return matchesNonNull(rawPassword.toString(), encodedPassword);
|
||||
@@ -56,7 +43,7 @@ public abstract class AbstractValidatingPasswordEncoder implements PasswordEncod
|
||||
|
||||
@Override
|
||||
public final boolean upgradeEncoding(@Nullable String encodedPassword) {
|
||||
if (!StringUtils.hasLength(encodedPassword)) {
|
||||
if (encodedPassword == null || encodedPassword.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
return upgradeEncodingNonNull(encodedPassword);
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ public interface PasswordEncoder {
|
||||
* @return A non-null encoded password, unless the rawPassword was null in which case
|
||||
* the result must be null.
|
||||
*/
|
||||
@Contract("!null -> !null; null -> null")
|
||||
@Contract("null -> null; !null -> !null")
|
||||
@Nullable String encode(@Nullable CharSequence rawPassword);
|
||||
|
||||
/**
|
||||
|
||||
@@ -337,9 +337,8 @@ Protecting multipart requests (file uploads) from CSRF attacks causes a https://
|
||||
To prevent a CSRF attack from occurring, the body of the HTTP request must be read to obtain the actual CSRF token.
|
||||
However, reading the body means that the file is uploaded, which means an external site can upload a file.
|
||||
|
||||
There are three options to using CSRF protection with multipart/form-data:
|
||||
There are two options to using CSRF protection with multipart/form-data:
|
||||
|
||||
* <<csrf-considerations-multipart-header,Include CSRF Token in an HTTP Request Header>>
|
||||
* <<csrf-considerations-multipart-body,Place CSRF Token in the Body>>
|
||||
* <<csrf-considerations-multipart-url,Place CSRF Token in the URL>>
|
||||
|
||||
@@ -351,17 +350,9 @@ Before you integrate Spring Security's CSRF protection with multipart file uploa
|
||||
More information about using multipart forms with Spring, see the https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-multipart[1.1.11. Multipart Resolver] section of the Spring reference and the https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/support/MultipartFilter.html[`MultipartFilter` Javadoc].
|
||||
====
|
||||
|
||||
[[csrf-considerations-multipart-header]]
|
||||
==== Include CSRF Token in an HTTP Request Header
|
||||
When JavaScript is available, you can submit multipart requests by sending the CSRF token in an HTTP request header.
|
||||
This avoids placing the token in the URL and avoids processing multipart request bodies before CSRF validation.
|
||||
This is generally the preferred approach for browser applications with JavaScript clients.
|
||||
|
||||
See xref:servlet/exploits/csrf.adoc#csrf-integration-javascript-other[JavaScript applications] for Servlet applications and xref:reactive/exploits/csrf.adoc#webflux-csrf-include-ajax[AJAX and JSON Requests] for Reactive applications.
|
||||
|
||||
[[csrf-considerations-multipart-body]]
|
||||
==== Place CSRF Token in the Body
|
||||
Another option is to include the actual CSRF token in the body of the request.
|
||||
The first option is to include the actual CSRF token in the body of the request.
|
||||
By placing the CSRF token in the body, the body is read before authorization is performed.
|
||||
This means that anyone can place temporary files on your server.
|
||||
However, only authorized users can submit a file that is processed by your application.
|
||||
@@ -369,7 +360,7 @@ In general, this is the recommended approach, because the temporary file upload
|
||||
|
||||
[[csrf-considerations-multipart-url]]
|
||||
==== Include CSRF Token in URL
|
||||
If letting unauthorized users upload temporary files is not acceptable and JavaScript is not available, an alternative is to include the expected CSRF token as a query parameter in the action attribute of the form.
|
||||
If letting unauthorized users upload temporary files is not acceptable, an alternative is to include the expected CSRF token as a query parameter in the action attribute of the form.
|
||||
The disadvantage to this approach is that query parameters can be leaked.
|
||||
More generally, it is considered best practice to place sensitive data within the body or headers to ensure it is not leaked.
|
||||
You can find additional information in https://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3[RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI's].
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
= Kerberos Migrations
|
||||
|
||||
For users leveraging Spring Security's Kerberos support, the Maven and Gradle Coordinates have been changed since the support was moved from an external module into Spring Security.
|
||||
See the xref:servlet/authentication/kerberos/introduction.adoc[Keberos documentation] for the new Maven and Gradle coordinates.
|
||||
@@ -391,8 +391,7 @@ For details, see the <<webflux-csrf-configure-custom-repository>> section.
|
||||
[[webflux-csrf-considerations-multipart]]
|
||||
=== Multipart (file upload)
|
||||
We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart[already discussed] how protecting multipart requests (file uploads) from CSRF attacks causes a https://en.wikipedia.org/wiki/Chicken_or_the_egg[chicken and the egg] problem.
|
||||
When JavaScript is available, we _recommend_ including the CSRF token in an HTTP request header (see <<webflux-csrf-include-ajax,AJAX and JSON Requests>>) to side-step the issue.
|
||||
If JavaScript is not available, this section discusses how to place the CSRF token in the <<webflux-csrf-considerations-multipart-body,body>> and <<webflux-csrf-considerations-multipart-url,url>> within a WebFlux application.
|
||||
This section discusses how to implement placing the CSRF token in the <<webflux-csrf-considerations-multipart-body,body>> and <<webflux-csrf-considerations-multipart-url,url>> within a WebFlux application.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
||||
@@ -51,9 +51,28 @@ image::{figures}/delegatingfilterproxy.png[]
|
||||
The following listing shows pseudo code of `DelegatingFilterProxy`:
|
||||
|
||||
.`DelegatingFilterProxy` Pseudo Code
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
|
||||
Filter delegate = getFilterBean(someBeanName); // <1>
|
||||
delegate.doFilter(request, response); // <2>
|
||||
}
|
||||
----
|
||||
|
||||
include-code::./SampleDelegatingFilterProxy[tag=dofilter,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
||||
val delegate: Filter = getFilterBean(someBeanName) // <1>
|
||||
delegate.doFilter(request, response) // <2>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Lazily get Filter that was registered as a Spring Bean.
|
||||
For the example in <<servlet-delegatingfilterproxy-figure>> `delegate` is an instance of __Bean Filter~0~__.
|
||||
<2> Delegate work to the Spring Bean.
|
||||
@@ -136,7 +155,58 @@ However, there are times that it is beneficial to know the ordering, if you want
|
||||
These security filters are most often declared using an javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[`HttpSecurity`] instance.
|
||||
To exemplify the above paragraph, let's consider the following security configuration:
|
||||
|
||||
include-code::./SecurityConfig[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(Customizer.withDefaults())
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.web.servlet.invoke
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
csrf { }
|
||||
httpBasic { }
|
||||
formLogin { }
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The above configuration will result in the following `Filter` ordering:
|
||||
|
||||
@@ -163,7 +233,7 @@ If you want to see the list of filters invoked for a particular request, you can
|
||||
=== Printing the Security Filters
|
||||
|
||||
Often times, it is useful to see the list of security ``Filter``s that are invoked for a particular request.
|
||||
For example, you want to make sure that the <<adding-filters-to-chain,filter you have added>> is in the list of the security filters.
|
||||
For example, you want to make sure that the <<adding-custom-filter,filter you have added>> is in the list of the security filters.
|
||||
|
||||
The list of filters is printed at DEBUG level on the application startup, so you can see something like the following on the console output for example:
|
||||
|
||||
@@ -178,7 +248,7 @@ But that is not all, you can also configure your application to print the invoca
|
||||
That is helpful to see if the filter you have added is invoked for a particular request or to check where an exception is coming from.
|
||||
To do that, you can configure your application to <<servlet-logging,log the security events>>.
|
||||
|
||||
[[adding-filters-to-chain]]
|
||||
[[adding-custom-filter]]
|
||||
=== Adding Filters to the Filter Chain
|
||||
|
||||
Most of the time, the default <<servlet-security-filters>> are enough to provide security to your application.
|
||||
@@ -190,7 +260,6 @@ javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
* `#addFilterAfter(Filter, Class<?>)` adds your filter after another filter
|
||||
* `#addFilterAt(Filter, Class<?>)` replaces another filter with your filter
|
||||
|
||||
[[adding-custom-filter]]
|
||||
==== Adding a Custom Filter
|
||||
|
||||
If you are creating a filter of your own, you will need to determine its location in the filter chain.
|
||||
@@ -229,7 +298,39 @@ For example, let's say that you want to add a `Filter` that gets a tenant id hea
|
||||
|
||||
First, let's create the `Filter`:
|
||||
|
||||
include-code::./TenantFilter[tag=snippet,indent=0]
|
||||
[source,java]
|
||||
----
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
public class TenantFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
String tenantId = request.getHeader("X-Tenant-Id"); <1>
|
||||
boolean hasAccess = isUserAllowed(tenantId); <2>
|
||||
if (hasAccess) {
|
||||
filterChain.doFilter(request, response); <3>
|
||||
return;
|
||||
}
|
||||
throw new AccessDeniedException("Access denied"); <4>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
The sample code above does the following:
|
||||
|
||||
@@ -248,7 +349,34 @@ The previous description already gives us a clue on where to add the filter, sin
|
||||
|
||||
Based on the rule of thumb, add it after xref:servlet/authentication/anonymous.adoc[ `AnonymousAuthenticationFilter`], the last authentication filter in the chain, like so:
|
||||
|
||||
include-code::./SecurityConfig[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ...
|
||||
.addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class); <1>
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http
|
||||
// ...
|
||||
.addFilterAfter(TenantFilter(), AnonymousAuthenticationFilter::class.java) <1>
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
<1> Use `HttpSecurity#addFilterAfter` to add the `TenantFilter` after the `AnonymousAuthenticationFilter`.
|
||||
|
||||
@@ -277,23 +405,127 @@ public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilte
|
||||
|
||||
This makes so that `HttpSecurity` is the only one adding it.
|
||||
|
||||
[[customizing-filter]]
|
||||
==== Customizing a Spring Security Filter
|
||||
|
||||
Generally, you can use a filter's DSL method to configure Spring Security's filters.
|
||||
For example, the simplest way to add `BasicAuthenticationFilter` is by asking the DSL to do it:
|
||||
|
||||
include-code::./CustomizingFilterTests[tag=basic-default,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
// ...
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
httpBasic { }
|
||||
// ...
|
||||
}
|
||||
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
However, in the event that you want to construct a Spring Security filter yourself, you specify it in the DSL using `addFilterAt` like so:
|
||||
|
||||
include-code::./CustomizingFilterTests[tag=custom-filter,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
BasicAuthenticationFilter basic = new BasicAuthenticationFilter();
|
||||
// ... configure
|
||||
|
||||
http
|
||||
// ...
|
||||
.addFilterAt(basic, BasicAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val basic = BasicAuthenticationFilter()
|
||||
// ... configure
|
||||
|
||||
http
|
||||
// ...
|
||||
.addFilterAt(basic, BasicAuthenticationFilter::class.java)
|
||||
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Note that if that filter has already been added, then Spring Security will throw an exception.
|
||||
For example, calling xref:servlet/authentication/passwords/basic.adoc[ `HttpSecurity#httpBasic`] adds a `BasicAuthenticationFilter` for you.
|
||||
So, the following arrangement fails since there are two calls that are both trying to add `BasicAuthenticationFilter`:
|
||||
|
||||
include-code::./CustomizingFilterTests[tag=incorrect,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
BasicAuthenticationFilter basic = new BasicAuthenticationFilter();
|
||||
// ... configure
|
||||
|
||||
http
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
// ... on no! BasicAuthenticationFilter is added twice!
|
||||
.addFilterAt(basic, BasicAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val basic = BasicAuthenticationFilter()
|
||||
// ... configure
|
||||
|
||||
http {
|
||||
httpBasic { }
|
||||
}
|
||||
|
||||
// ... on no! BasicAuthenticationFilter is added twice!
|
||||
http.addFilterAt(basic, BasicAuthenticationFilter::class.java)
|
||||
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In this case, remove the call to `httpBasic` since you are constructing `BasicAuthenticationFilter` yourself.
|
||||
|
||||
@@ -301,7 +533,10 @@ In this case, remove the call to `httpBasic` since you are constructing `BasicAu
|
||||
====
|
||||
In the event that you are unable to reconfigure `HttpSecurity` to not add a certain filter, you can typically disable the Spring Security filter by calling its DSL's `disable` method like so:
|
||||
|
||||
include-code::./CustomizingFilterTests[tag=disable,indent=0]
|
||||
[source,java]
|
||||
----
|
||||
.httpBasic((basic) -> basic.disable())
|
||||
----
|
||||
====
|
||||
|
||||
[[servlet-exceptiontranslationfilter]]
|
||||
@@ -383,7 +618,53 @@ Or you may want to shut off this feature since you always want to redirect the u
|
||||
To do that, you can use the javadoc:org.springframework.security.web.savedrequest.NullRequestCache[NullRequestCache] implementation.
|
||||
|
||||
.Prevent the Request From Being Saved
|
||||
include-code::./SecurityConfig[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
RequestCache nullRequestCache = new NullRequestCache();
|
||||
http
|
||||
// ...
|
||||
.requestCache((cache) -> cache
|
||||
.requestCache(nullRequestCache)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
||||
val nullRequestCache = NullRequestCache()
|
||||
http {
|
||||
requestCache {
|
||||
requestCache = nullRequestCache
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http auto-config="true">
|
||||
<!-- ... -->
|
||||
<request-cache ref="nullRequestCache"/>
|
||||
</http>
|
||||
|
||||
<b:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[requestcacheawarefilter]]
|
||||
=== RequestCacheAwareFilter
|
||||
|
||||
@@ -3,38 +3,3 @@
|
||||
|
||||
Spring Security Kerberos {spring-security-version} is built and tested with JDK 17,
|
||||
Spring Security {spring-security-version} and Spring Framework {spring-core-version}.
|
||||
|
||||
The dependency coordinates changed with Spring Security 7:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
.pom.xml
|
||||
[source,xml,subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... other dependency elements ... -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-kerberos-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-kerberos-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
Gradle::
|
||||
+
|
||||
.build.gradle
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
dependencies {
|
||||
implementation "org.springframework.security:spring-security-kerberos-core"
|
||||
implementation "org.springframework.security:spring-security-kerberos-web"
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -949,21 +949,6 @@ fun getMessages(): List<Message> { }
|
||||
----
|
||||
======
|
||||
|
||||
[[method-security-has-scope]]
|
||||
=== Using `hasScope` in Method Security
|
||||
|
||||
Because method security expressions can evaluation `AuthorizationManager` instances, you can also use the `hasScope` API by publishing a `DefaultOAuth2AuthorizationManagerFactory` `@Bean`:
|
||||
|
||||
include-code::./MethodSecurityHasScopeConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
and then doing:
|
||||
|
||||
include-code::./MessageService[tag=protected-method,indent=0]
|
||||
|
||||
If you are using xref:servlet/authentication/mfa.adoc[Spring Security's MFA feature], then you can supply its `AuthorizationManagerFactory` instance to ensure that your authentication factors are automatically checked as well by including it in your `DefaultOAuth2AuthorizationManagerFactory` constructor as follows:
|
||||
|
||||
include-code::./MethodSecurityHasScopeMfaConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
[[oauth2resourceserver-jwt-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
|
||||
|
||||
@@ -638,21 +638,6 @@ fun getMessages(): List<Message?> {}
|
||||
----
|
||||
======
|
||||
|
||||
[[method-security-has-scope]]
|
||||
=== Using `hasScope` in Method Security
|
||||
|
||||
Because method security expressions can evaluation `AuthorizationManager` instances, you can also use the `hasScope` API by publishing a `DefaultOAuth2AuthorizationManagerFactory` `@Bean`:
|
||||
|
||||
include-code::./MethodSecurityHasScopeConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
and then doing:
|
||||
|
||||
include-code::./MessageService[tag=protected-method,indent=0]
|
||||
|
||||
If you are using xref:servlet/authentication/mfa.adoc[Spring Security's MFA feature], then you can supply its `AuthorizationManagerFactory` instance to ensure that your authentication factors are automatically checked as well by including it in your `DefaultOAuth2AuthorizationManagerFactory` constructor as follows:
|
||||
|
||||
include-code::./MethodSecurityHasScopeMfaConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
[[oauth2resourceserver-opaque-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[[new]]
|
||||
= What's New in Spring Security 7.1
|
||||
|
||||
* https://github.com/spring-projects/spring-security/pull/18634[gh-18634] - Added javadoc:org.springframework.security.web.util.matcher.InetAddressMatcher[]
|
||||
This is a placeholder for updates to Spring Security 7.1
|
||||
|
||||
@@ -6,7 +6,6 @@ plugins {
|
||||
id 'java-toolchain'
|
||||
id 'test-compile-target-jdk25'
|
||||
id 'compile-warnings-error'
|
||||
id 'javadoc-warnings-error'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.docs'
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@ package org.springframework.security.docs.features.authentication.password4jbcry
|
||||
import com.password4j.BcryptFunction;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.crypto.password4j.BcryptPassword4jPasswordEncoder;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class BcryptUsageTests {
|
||||
@Test
|
||||
void defaultParams() {
|
||||
// tag::default-params[]
|
||||
PasswordEncoder encoder = new BcryptPassword4jPasswordEncoder();
|
||||
PasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
String result = encoder.encode("myPassword");
|
||||
assertThat(encoder.matches("myPassword", result)).isTrue();
|
||||
// end::default-params[]
|
||||
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.addingcustomfilter;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {
|
||||
CustomFilterTests.UserDetailsConfig.class,
|
||||
CustomFilterTests.ApiController.class,
|
||||
SecurityConfig.class })
|
||||
@WebAppConfiguration
|
||||
public class CustomFilterTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.defaultRequest(get("/api").with(user("user")))
|
||||
.apply(springSecurity())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void tenantFilterWhenHeaderMissingThenAccessDenied() {
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.mvc.perform(get("/api")).andReturn());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tenantFilterWhenHeaderPresentThenContinuesFilterChain() throws Exception {
|
||||
this.mvc.perform(get("/api").header("X-Tenant-Id", "some-tenant-id"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("user"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class UserDetailsConfig {
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class ApiController {
|
||||
|
||||
@GetMapping("/api")
|
||||
String api() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.addingcustomfilter;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@Configuration
|
||||
@ContextConfiguration(classes = { SecurityConfig.class })
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
// tag::snippet[]
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ...
|
||||
.addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class); // <1>
|
||||
return http.build();
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.addingcustomfilter;
|
||||
|
||||
// tag::snippet[]
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
public class TenantFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
String tenantId = request.getHeader("X-Tenant-Id"); // <1>
|
||||
boolean hasAccess = isUserAllowed(tenantId); // <2>
|
||||
if (hasAccess) {
|
||||
filterChain.doFilter(request, response); // <3>
|
||||
return;
|
||||
}
|
||||
throw new AccessDeniedException("Access denied"); // <4>
|
||||
}
|
||||
|
||||
private boolean isUserAllowed(String tenantId) {
|
||||
return "some-tenant-id".equals(tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
-203
@@ -1,203 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.customizingfilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
/**
|
||||
* Tests for customizing security filters.
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class CustomizingFilterTests {
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
FilterChainProxy filterChainProxy;
|
||||
|
||||
@Test
|
||||
public void filterChainWhenBasicDefaultThenBasicAuthenticationFilterPresent() {
|
||||
this.spring.register(SecurityConfigBasicDefault.class).autowire();
|
||||
List<Filter> filters = this.filterChainProxy.getFilters("/");
|
||||
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterChainWhenCustomFilterThenCustomFilterPresent() {
|
||||
this.spring.register(SecurityConfigCustom.class).autowire();
|
||||
List<Filter> filters = this.filterChainProxy.getFilters("/");
|
||||
assertThat(filters).extracting("class").contains(SecurityConfigCustom.MyBasicAuthenticationFilter.class);
|
||||
assertThat(filters).extracting("class").doesNotContain(BasicAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenDisableThenNoWwwAuthenticateHeader() throws Exception {
|
||||
this.spring.register(SecurityConfigDisable.class).autowire();
|
||||
this.mvc.perform(get("/")).andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterChainWhenIncorrectThenBothFiltersPresent() {
|
||||
this.spring.register(SecurityConfigIncorrect.class).autowire();
|
||||
List<Filter> filters = this.filterChainProxy.getFilters("/");
|
||||
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter.class);
|
||||
assertThat(filters).extracting("class").contains(SecurityConfigIncorrect.MyBasicAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class SecurityConfigBasicDefault {
|
||||
|
||||
// tag::basic-default[]
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
// ...
|
||||
|
||||
return http.build();
|
||||
}
|
||||
// end::basic-default[]
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class SecurityConfigCustom {
|
||||
|
||||
// tag::custom-filter[]
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
MyBasicAuthenticationFilter basic = new MyBasicAuthenticationFilter();
|
||||
// ... configure
|
||||
|
||||
http
|
||||
// ...
|
||||
.addFilterAt(basic, BasicAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
// end::custom-filter[]
|
||||
|
||||
static class MyBasicAuthenticationFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class SecurityConfigDisable {
|
||||
|
||||
// tag::disable[]
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.httpBasic((basic) -> basic.disable());
|
||||
// ...
|
||||
|
||||
return http.build();
|
||||
}
|
||||
// end::disable[]
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class SecurityConfigIncorrect {
|
||||
|
||||
// tag::incorrect[]
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
MyBasicAuthenticationFilter basic = new MyBasicAuthenticationFilter();
|
||||
// ... configure
|
||||
|
||||
http
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
// ... on no! BasicAuthenticationFilter is added twice!
|
||||
.addFilterAt(basic, BasicAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
// end::incorrect[]
|
||||
|
||||
static class MyBasicAuthenticationFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class UserDetailsConfig {
|
||||
|
||||
@Bean
|
||||
InMemoryUserDetailsManager userDetailsManager() {
|
||||
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
class MessageService {
|
||||
|
||||
// tag::protected-method[]
|
||||
@PreAuthorize("@oauth2.hasScope('message:read')")
|
||||
String readMessage() {
|
||||
return "message";
|
||||
}
|
||||
// end::protected-method[]
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory;
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
class MethodSecurityHasScopeConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
OAuth2AuthorizationManagerFactory<?> oauth2() {
|
||||
return new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
}
|
||||
// end::declare-factory[]
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SecurityTestExecutionListeners
|
||||
public class MethodSecurityHasScopeConfigurationTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this).mockMvcAfterSpringSecurityOk();
|
||||
|
||||
@Autowired
|
||||
private MessageService messages;
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "SCOPE_message:read")
|
||||
void readMessageWhenMessageReadThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration.class, MessageService.class).autowire();
|
||||
this.messages.readMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void readMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration.class, MessageService.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = { "SCOPE_message:read", "FACTOR_BEARER", "FACTOR_X509" })
|
||||
void mfaReadMessageWhenMessageReadAndFactorsThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
|
||||
this.messages.readMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = { "SCOPE_message:read" })
|
||||
void mfaReadMessageWhenMessageReadThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void mfaReadMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
||||
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory;
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
@EnableMultiFactorAuthentication(authorities = { "FACTOR_BEARER", "FACTOR_X509" })
|
||||
class MethodSecurityHasScopeMfaConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
OAuth2AuthorizationManagerFactory<?> oauth2(AuthorizationManagerFactory<?> authz) {
|
||||
return new DefaultOAuth2AuthorizationManagerFactory<>(authz);
|
||||
}
|
||||
// end::declare-factory[]
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.requestcachepreventsavedrequest;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
|
||||
public class SecurityConfig {
|
||||
|
||||
// tag::snippet[]
|
||||
@Bean
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
RequestCache nullRequestCache = new NullRequestCache();
|
||||
http
|
||||
// ...
|
||||
.requestCache((cache) -> cache
|
||||
.requestCache(nullRequestCache)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.servletdelegatingfilterproxy;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A very simple implementation of a DelegatingFilterProxy.
|
||||
*/
|
||||
public class SampleDelegatingFilterProxy extends GenericFilterBean {
|
||||
|
||||
private StaticWebApplicationContext wac;
|
||||
private final String someBeanName;
|
||||
|
||||
public SampleDelegatingFilterProxy(String someBeanName, StaticWebApplicationContext webApplicationContext) {
|
||||
this.wac = webApplicationContext;
|
||||
this.someBeanName = someBeanName;
|
||||
}
|
||||
|
||||
// tag::dofilter[]
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
Filter delegate = getFilterBean(someBeanName); // <1>
|
||||
delegate.doFilter(request, response, chain); // <2>
|
||||
}
|
||||
// end::dofilter[]
|
||||
|
||||
private Filter getFilterBean(String someBeanName) {
|
||||
return this.wac.getBean(someBeanName, Filter.class);
|
||||
}
|
||||
}
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.servletdelegatingfilterproxy;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.web.servlet.MockServletContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class SampleDelegatingFilterProxyTests {
|
||||
|
||||
@Test
|
||||
void testFilter() throws ServletException, IOException {
|
||||
ServletContext sc = new MockServletContext();
|
||||
StaticWebApplicationContext wac = new StaticWebApplicationContext();
|
||||
wac.registerSingleton("targetFilter", MockFilter.class);
|
||||
wac.setServletContext(sc);
|
||||
wac.refresh();
|
||||
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
|
||||
|
||||
MockFilter targetFilter = (MockFilter) wac.getBean("targetFilter");
|
||||
MockFilterConfig proxyConfig = new MockFilterConfig(sc);
|
||||
proxyConfig.addInitParameter("targetBeanName", "targetFilter");
|
||||
SampleDelegatingFilterProxy filterProxy = new SampleDelegatingFilterProxy("targetFilter", wac);
|
||||
filterProxy.init(proxyConfig);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filterProxy.doFilter(request, response, null);
|
||||
|
||||
assertThat(targetFilter.filterConfig).isNull();
|
||||
assertThat(request.getAttribute("called")).isEqualTo(Boolean.TRUE);
|
||||
|
||||
filterProxy.destroy();
|
||||
assertThat(targetFilter.filterConfig).isNull();
|
||||
}
|
||||
|
||||
private static class MockFilter implements Filter {
|
||||
|
||||
private FilterConfig filterConfig;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
this.filterConfig = filterConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws java.io.IOException, ServletException {
|
||||
request.setAttribute("called", Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.filterConfig = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockFilterConfig implements FilterConfig {
|
||||
private final ServletContext servletContext;
|
||||
|
||||
private final String filterName;
|
||||
|
||||
private final Map<String, String> initParameters = new LinkedHashMap<>();
|
||||
|
||||
public MockFilterConfig(ServletContext servletContext) {
|
||||
this.servletContext = servletContext;
|
||||
this.filterName = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterName() {
|
||||
return this.filterName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getServletContext() {
|
||||
return this.servletContext;
|
||||
}
|
||||
|
||||
public void addInitParameter(String name, String value) {
|
||||
Assert.notNull(name, "Parameter name must not be null");
|
||||
this.initParameters.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInitParameter(String name) {
|
||||
Assert.notNull(name, "Parameter name must not be null");
|
||||
return this.initParameters.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getInitParameterNames() {
|
||||
return Collections.enumeration(this.initParameters.keySet());
|
||||
}
|
||||
}
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.servletsecurityfilters;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = { SampleSecurityConfigTests.UserDetailsConfig.class, SecurityConfig.class })
|
||||
@WebAppConfiguration
|
||||
public class SampleSecurityConfigTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.defaultRequest(get("/api").with(user("user")))
|
||||
.defaultRequest(post("/api").with(csrf()))
|
||||
.apply(springSecurity())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGet() throws Exception {
|
||||
this.mvc.perform(get("/api")
|
||||
.with(httpBasic("user", "password")))
|
||||
// Security check was successful
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(authenticated().withUsername("user"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnauthenticated() throws Exception {
|
||||
this.mvc.perform(get("/api"))
|
||||
// Security check was successful
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCsrf() throws Exception {
|
||||
this.mvc.perform(post("/api")
|
||||
.with(csrf())
|
||||
.with(httpBasic("user", "password"))
|
||||
).andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class UserDetailsConfig {
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.docs.servlet.servletsecurityfilters;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(Customizer.withDefaults())
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+2
-1
@@ -3,6 +3,7 @@ package org.springframework.security.kt.docs.features.authentication.password4jb
|
||||
import com.password4j.BcryptFunction
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.security.crypto.password4j.BcryptPassword4jPasswordEncoder
|
||||
|
||||
@@ -13,7 +14,7 @@ class BcryptUsageTests {
|
||||
@Test
|
||||
fun defaultParams() {
|
||||
// tag::default-params[]
|
||||
val encoder: PasswordEncoder = BcryptPassword4jPasswordEncoder()
|
||||
val encoder: PasswordEncoder = BCryptPasswordEncoder()
|
||||
val result = encoder.encode("myPassword")
|
||||
Assertions.assertThat(encoder.matches("myPassword", result)).isTrue()
|
||||
// end::default-params[]
|
||||
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.addingcustomfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
import org.springframework.test.context.web.WebAppConfiguration
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import org.springframework.web.context.WebApplicationContext
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(
|
||||
classes = [
|
||||
CustomFilterTests.UserDetailsConfig::class,
|
||||
CustomFilterTests.ApiController::class,
|
||||
SecurityConfig::class
|
||||
]
|
||||
)
|
||||
@WebAppConfiguration
|
||||
class CustomFilterTests {
|
||||
|
||||
@Autowired
|
||||
private lateinit var context: WebApplicationContext
|
||||
|
||||
private lateinit var mvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply<DefaultMockMvcBuilder>(springSecurity())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tenantFilterWhenHeaderMissingThenAccessDenied() {
|
||||
assertThatExceptionOfType(Exception::class.java)
|
||||
.isThrownBy { this.mvc.perform(get("/api").with(user("user"))).andReturn() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tenantFilterWhenHeaderPresentThenContinuesFilterChain() {
|
||||
this.mvc.perform(get("/api")
|
||||
.with(user("user"))
|
||||
.header("X-Tenant-Id", "some-tenant-id"))
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(authenticated().withUsername("user"))
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class UserDetailsConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
@Suppress("DEPRECATION")
|
||||
val user: UserDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return InMemoryUserDetailsManager(user)
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
class ApiController {
|
||||
|
||||
@GetMapping("/api")
|
||||
fun api(): String {
|
||||
return "ok"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.addingcustomfilter
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
// tag::snippet[]
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http
|
||||
// ...
|
||||
.addFilterAfter(TenantFilter(), AnonymousAuthenticationFilter::class.java) // <1>
|
||||
return http.build()
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.addingcustomfilter
|
||||
|
||||
import jakarta.servlet.Filter
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.ServletException
|
||||
import jakarta.servlet.ServletRequest
|
||||
import jakarta.servlet.ServletResponse
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.access.AccessDeniedException
|
||||
import java.io.IOException
|
||||
|
||||
// tag::snippet[]
|
||||
class TenantFilter : Filter {
|
||||
|
||||
@Throws(IOException::class, ServletException::class)
|
||||
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain) {
|
||||
val request = servletRequest as HttpServletRequest
|
||||
val response = servletResponse as HttpServletResponse
|
||||
|
||||
val tenantId = request.getHeader("X-Tenant-Id") // <1>
|
||||
val hasAccess = isUserAllowed(tenantId) // <2>
|
||||
if (hasAccess) {
|
||||
filterChain.doFilter(request, response) // <3>
|
||||
return
|
||||
}
|
||||
throw AccessDeniedException("Access denied") // <4>
|
||||
}
|
||||
|
||||
private fun isUserAllowed(tenantId: String?): Boolean {
|
||||
return "some-tenant-id" == tenantId
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.customizingfilter
|
||||
|
||||
import jakarta.servlet.Filter
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.ServletRequest
|
||||
import jakarta.servlet.ServletResponse
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.web.FilterChainProxy
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
|
||||
/**
|
||||
* Tests for customizing security filters.
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
class CustomizingFilterTests {
|
||||
|
||||
@JvmField
|
||||
val spring = SpringTestContext(this)
|
||||
|
||||
@Autowired
|
||||
lateinit var mvc: MockMvc
|
||||
|
||||
@Autowired
|
||||
lateinit var filterChainProxy: FilterChainProxy
|
||||
|
||||
@Test
|
||||
fun `filter chain when basic default then BasicAuthenticationFilter present`() {
|
||||
spring.register(SecurityConfigBasicDefault::class.java).autowire()
|
||||
val filters = filterChainProxy.getFilters("/")
|
||||
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filter chain when custom filter then custom filter present`() {
|
||||
spring.register(SecurityConfigCustom::class.java).autowire()
|
||||
val filters = filterChainProxy.getFilters("/")
|
||||
assertThat(filters).extracting("class").contains(SecurityConfigCustom.MyBasicAuthenticationFilter::class.java)
|
||||
assertThat(filters).extracting("class").doesNotContain(BasicAuthenticationFilter::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filter chain when incorrect then both filters present`() {
|
||||
spring.register(SecurityConfigIncorrect::class.java).autowire()
|
||||
val filters = filterChainProxy.getFilters("/")
|
||||
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter::class.java)
|
||||
assertThat(filters).extracting("class").contains(SecurityConfigIncorrect.MyBasicAuthenticationFilter::class.java)
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfigBasicDefault {
|
||||
|
||||
// tag::basic-default[]
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
httpBasic { }
|
||||
// ...
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
// end::basic-default[]
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfigCustom {
|
||||
|
||||
// tag::custom-filter[]
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val basic = MyBasicAuthenticationFilter()
|
||||
// ... configure
|
||||
|
||||
http
|
||||
// ...
|
||||
.addFilterAt(basic, BasicAuthenticationFilter::class.java)
|
||||
|
||||
return http.build()
|
||||
}
|
||||
// end::custom-filter[]
|
||||
|
||||
class MyBasicAuthenticationFilter : Filter {
|
||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration @EnableWebSecurity
|
||||
open class SecurityConfigDisable {
|
||||
|
||||
// tag::disable[]
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
httpBasic {
|
||||
disable()
|
||||
}
|
||||
// ...
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
// end::disable[]
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfigIncorrect {
|
||||
|
||||
// tag::incorrect[]
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val basic = MyBasicAuthenticationFilter()
|
||||
// ... configure
|
||||
|
||||
http {
|
||||
httpBasic { }
|
||||
}
|
||||
|
||||
// ... on no! BasicAuthenticationFilter is added twice!
|
||||
http.addFilterAt(basic, BasicAuthenticationFilter::class.java)
|
||||
|
||||
return http.build()
|
||||
}
|
||||
// end::incorrect[]
|
||||
|
||||
class MyBasicAuthenticationFilter : Filter {
|
||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
|
||||
@Service
|
||||
open class MessageService {
|
||||
// tag::protected-method[]
|
||||
@PreAuthorize("@oauth2.hasScope('message:read')")
|
||||
open fun readMessage(): String {
|
||||
return "message"
|
||||
}
|
||||
// end::protected-method[]
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
open class MethodSecurityHasScopeConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
open fun oauth2(): OAuth2AuthorizationManagerFactory<Any> {
|
||||
return DefaultOAuth2AuthorizationManagerFactory()
|
||||
}
|
||||
// end::declare-factory[]
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.access.AccessDeniedException
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@SecurityTestExecutionListeners
|
||||
class MethodSecurityHasScopeConfigurationTests {
|
||||
@JvmField
|
||||
val spring: SpringTestContext = SpringTestContext(this).mockMvcAfterSpringSecurityOk()
|
||||
|
||||
@Autowired
|
||||
var messages: MessageService? = null
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = ["SCOPE_message:read"])
|
||||
fun readMessageWhenMessageReadThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration::class.java, MessageService::class.java).autowire()
|
||||
this.messages!!.readMessage()
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun readMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration::class.java, MessageService::class.java).autowire()
|
||||
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
|
||||
.isThrownBy({ this.messages!!.readMessage() })
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = ["SCOPE_message:read", "FACTOR_BEARER", "FACTOR_X509"])
|
||||
fun mfaReadMessageWhenMessageReadAndFactorsThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
|
||||
this.messages!!.readMessage()
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = ["SCOPE_message:read"])
|
||||
fun mfaReadMessageWhenMessageReadThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
|
||||
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
|
||||
.isThrownBy({ this.messages!!.readMessage() })
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun mfaReadMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
|
||||
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
|
||||
.isThrownBy({ this.messages!!.readMessage() })
|
||||
}
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory
|
||||
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
@EnableMultiFactorAuthentication(authorities = ["FACTOR_BEARER", "FACTOR_X509"])
|
||||
open class MethodSecurityHasScopeMfaConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
open fun oauth2(authz: AuthorizationManagerFactory<Any>): OAuth2AuthorizationManagerFactory<Any> {
|
||||
return DefaultOAuth2AuthorizationManagerFactory(authz)
|
||||
} // end::declare-factory[]
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.requestcachepreventsavedrequest
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.savedrequest.NullRequestCache
|
||||
|
||||
open class SecurityConfig {
|
||||
|
||||
// tag::snippet[]
|
||||
@Bean
|
||||
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
||||
val nullRequestCache = NullRequestCache()
|
||||
http {
|
||||
requestCache {
|
||||
requestCache = nullRequestCache
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.servletdelegatingfilterproxy;
|
||||
|
||||
import jakarta.servlet.Filter
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.ServletException
|
||||
import jakarta.servlet.ServletRequest
|
||||
import jakarta.servlet.ServletResponse
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* A very simple implementation of a DelegatingFilterProxy.
|
||||
*/
|
||||
class SampleDelegatingFilterProxy(
|
||||
private val someBeanName: String,
|
||||
private var wac: StaticWebApplicationContext
|
||||
) : GenericFilterBean() {
|
||||
|
||||
// tag::dofilter[]
|
||||
@Throws(IOException::class, ServletException::class)
|
||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain?) {
|
||||
val delegate: Filter = getFilterBean(someBeanName) // <1>
|
||||
delegate.doFilter(request, response, chain) // <2>
|
||||
}
|
||||
// end::dofilter[]
|
||||
|
||||
private fun getFilterBean(someBeanName: String): Filter {
|
||||
return wac.getBean(someBeanName, Filter::class.java)
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.servletdelegatingfilterproxy
|
||||
|
||||
import jakarta.servlet.Filter
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.FilterConfig
|
||||
import jakarta.servlet.ServletContext
|
||||
import jakarta.servlet.ServletException
|
||||
import jakarta.servlet.ServletRequest
|
||||
import jakarta.servlet.ServletResponse
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.security.web.servlet.MockServletContext
|
||||
import org.springframework.util.Assert
|
||||
import org.springframework.web.context.WebApplicationContext
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext
|
||||
import java.io.IOException
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import java.util.Enumeration
|
||||
import java.util.Collections
|
||||
import kotlin.collections.LinkedHashMap
|
||||
|
||||
class SampleDelegatingFilterProxyTests {
|
||||
|
||||
@Test
|
||||
@Throws(ServletException::class, IOException::class)
|
||||
fun testFilter() {
|
||||
val sc: ServletContext = MockServletContext()
|
||||
val wac = StaticWebApplicationContext()
|
||||
wac.registerSingleton("targetFilter", MockFilter::class.java)
|
||||
wac.setServletContext(sc)
|
||||
wac.refresh()
|
||||
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac)
|
||||
|
||||
val targetFilter = wac.getBean("targetFilter") as MockFilter
|
||||
val proxyConfig = MockFilterConfig(sc)
|
||||
proxyConfig.addInitParameter("targetBeanName", "targetFilter")
|
||||
val filterProxy = SampleDelegatingFilterProxy("targetFilter", wac)
|
||||
filterProxy.init(proxyConfig)
|
||||
|
||||
val request = MockHttpServletRequest()
|
||||
val response = MockHttpServletResponse()
|
||||
filterProxy.doFilter(request, response, null)
|
||||
|
||||
assertThat(targetFilter.filterConfig).isNull()
|
||||
assertThat(request.getAttribute("called")).isEqualTo(true)
|
||||
|
||||
filterProxy.destroy()
|
||||
assertThat(targetFilter.filterConfig).isNull()
|
||||
}
|
||||
|
||||
private class MockFilter : Filter {
|
||||
var filterConfig: FilterConfig? = null
|
||||
|
||||
@Throws(ServletException::class)
|
||||
override fun init(filterConfig: FilterConfig) {
|
||||
this.filterConfig = filterConfig
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ServletException::class)
|
||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain?) {
|
||||
request.setAttribute("called", true)
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
filterConfig = null
|
||||
}
|
||||
}
|
||||
|
||||
private class MockFilterConfig(
|
||||
private val servletContext: ServletContext
|
||||
) : FilterConfig {
|
||||
private val filterName: String = ""
|
||||
private val initParameters = LinkedHashMap<String, String>()
|
||||
|
||||
override fun getFilterName(): String = filterName
|
||||
|
||||
override fun getServletContext(): ServletContext = servletContext
|
||||
|
||||
fun addInitParameter(name: String, value: String) {
|
||||
Assert.notNull(name, "Parameter name must not be null")
|
||||
initParameters[name] = value
|
||||
}
|
||||
|
||||
override fun getInitParameter(name: String): String? {
|
||||
Assert.notNull(name, "Parameter name must not be null")
|
||||
return initParameters[name]
|
||||
}
|
||||
|
||||
override fun getInitParameterNames(): Enumeration<String> =
|
||||
Collections.enumeration(initParameters.keys)
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kt.docs.servlet.servletsecurityfilters
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
csrf { }
|
||||
httpBasic { }
|
||||
formLogin { }
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2004-present 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!-- tag::snippet[] -->
|
||||
<http auto-config="true">
|
||||
<!-- ... -->
|
||||
<request-cache ref="nullRequestCache"/>
|
||||
</http>
|
||||
|
||||
<b:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
|
||||
<!-- end::snippet[] -->
|
||||
|
||||
</b:beans>
|
||||
+18
-18
@@ -4,39 +4,39 @@ io-rsocket = "1.1.5"
|
||||
io-spring-javaformat = "0.0.47"
|
||||
io-spring-nohttp = "0.0.11"
|
||||
jakarta-websocket = "2.2.0"
|
||||
org-apache-maven-resolver = "1.9.27"
|
||||
org-apache-maven-resolver = "1.9.25"
|
||||
org-aspectj = "1.9.25.1"
|
||||
org-bouncycastle = "1.83"
|
||||
org-eclipse-jetty = "11.0.26"
|
||||
org-jetbrains-kotlin = "2.3.10"
|
||||
org-jetbrains-kotlinx = "1.10.2"
|
||||
org-mockito = "5.21.0"
|
||||
org-opensaml5 = "5.2.1"
|
||||
org-springframework = "7.0.5"
|
||||
org-opensaml5 = "5.2.0"
|
||||
org-springframework = "7.0.3"
|
||||
com-password4j = "1.8.4"
|
||||
|
||||
[libraries]
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.32"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.21.1"
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.28"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.21.0"
|
||||
com-google-inject-guice = "com.google.inject:guice:3.0"
|
||||
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.6"
|
||||
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.33"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.4"
|
||||
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.26.1"
|
||||
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
|
||||
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
|
||||
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.4"
|
||||
com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.10.0"
|
||||
commons-collections = "commons-collections:commons-collections:3.2.2"
|
||||
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.2.1"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.16.3"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.16.2"
|
||||
io-mockk = "io.mockk:mockk:1.14.9"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.3"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.2"
|
||||
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
||||
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
|
||||
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
|
||||
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.14"
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
|
||||
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0"
|
||||
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
|
||||
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0"
|
||||
@@ -65,27 +65,27 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on",
|
||||
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" }
|
||||
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
|
||||
org-hamcrest = "org.hamcrest:hamcrest:2.2"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.2.5.Final"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.2.4.Final"
|
||||
org-hsqldb = "org.hsqldb:hsqldb:2.7.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" }
|
||||
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
|
||||
org-junit-junit-bom = "org.junit:junit-bom:6.0.3"
|
||||
org-junit-junit-bom = "org.junit:junit-bom:6.0.2"
|
||||
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
|
||||
org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" }
|
||||
org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" }
|
||||
org-python-jython = { module = "org.python:jython", version = "2.5.3" }
|
||||
org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.41.0"
|
||||
org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.41.0"
|
||||
org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.40.0"
|
||||
org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.40.0"
|
||||
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
|
||||
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
|
||||
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.3"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.2"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.2"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.1"
|
||||
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
|
||||
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.1.0"
|
||||
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.4"
|
||||
|
||||
com-google-code-gson-gson = "com.google.code.gson:gson:2.13.2"
|
||||
com-thaiopensource-trag = "com.thaiopensource:trang:20091111"
|
||||
@@ -102,7 +102,7 @@ org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanne
|
||||
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
|
||||
|
||||
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.11'
|
||||
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.0.RELEASE'
|
||||
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.29.7.RELEASE'
|
||||
com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" }
|
||||
|
||||
[plugins]
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.kerberos.aot.hint;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} for Kerberos authentication classes.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
class KerberosRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
// Krb5LoginModule is referenced as a plain string in LoginConfig and loaded by
|
||||
// JAAS via Class.forName at runtime, so it needs an explicit reflection hint.
|
||||
hints.reflection()
|
||||
.registerType(TypeReference.of("com.sun.security.auth.module.Krb5LoginModule"),
|
||||
(builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* AOT and native image hint support for Kerberos authentication.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.kerberos.aot.hint;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.security.kerberos.aot.hint.KerberosRuntimeHints
|
||||
-6
@@ -30,7 +30,6 @@ import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.OctetSequenceKey;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -113,11 +112,6 @@ public class JwtClientAssertionAuthenticationProviderTests {
|
||||
.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
AuthorizationServerContextHolder.resetContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
|
||||
-6
@@ -25,7 +25,6 @@ import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -112,11 +111,6 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
AuthorizationServerContextHolder.resetContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
|
||||
-6
@@ -22,7 +22,6 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -96,11 +95,6 @@ public class OAuth2AuthorizationConsentAuthenticationProviderTests {
|
||||
.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
AuthorizationServerContextHolder.resetContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
|
||||
-6
@@ -25,7 +25,6 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -97,11 +96,6 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests {
|
||||
mockAuthorizationServerContext();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
AuthorizationServerContextHolder.resetContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
|
||||
-20
@@ -254,8 +254,6 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
@@ -276,21 +274,6 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link ReactiveOAuth2AuthorizationSuccessHandler} to use for handling
|
||||
* successful refresh token response, defaults to
|
||||
* {@link RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler}.
|
||||
* @param authorizationSuccessHandler the
|
||||
* {@link ReactiveOAuth2AuthorizationSuccessHandler} to use
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
* @since 7.1
|
||||
*/
|
||||
public RefreshTokenGrantBuilder authorizationSuccessHandler(
|
||||
ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
|
||||
this.authorizationSuccessHandler = authorizationSuccessHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if
|
||||
@@ -327,9 +310,6 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.authorizationSuccessHandler != null) {
|
||||
authorizedClientProvider.setAuthorizationSuccessHandler(this.authorizationSuccessHandler);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
|
||||
-305
@@ -1,305 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.oauth2.client;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.ReactiveOidcIdTokenDecoderFactory;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveOAuth2AuthorizationSuccessHandler} that refreshes an {@link OidcUser}
|
||||
* in the {@link SecurityContext} if the refreshed {@link OidcIdToken} is valid according
|
||||
* to <a href=
|
||||
* "https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse">OpenID
|
||||
* Connect Core 1.0 - Section 12.2 Successful Refresh Response</a>
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 7.1
|
||||
* @see RefreshTokenReactiveOAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
|
||||
implements ReactiveOAuth2AuthorizationSuccessHandler {
|
||||
|
||||
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
||||
|
||||
private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce";
|
||||
|
||||
private static final String REFRESH_TOKEN_RESPONSE_ERROR_URI = "https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse";
|
||||
|
||||
// @formatter:off
|
||||
private static final Mono<ServerWebExchange> currentServerWebExchangeMono = Mono.deferContextual(Mono::just)
|
||||
.filter((c) -> c.hasKey(ServerWebExchange.class))
|
||||
.map((c) -> c.get(ServerWebExchange.class));
|
||||
// @formatter:on
|
||||
|
||||
private ServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
|
||||
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
|
||||
|
||||
private ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = new OidcReactiveOAuth2UserService();
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities) -> authorities;
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
@Override
|
||||
public Mono<Void> onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient, Authentication principal,
|
||||
Map<String, Object> attributes) {
|
||||
// The response must contain the openid scope.
|
||||
if (!authorizedClient.getAccessToken().getScopes().contains(OidcScopes.OPENID)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
// The response must contain an id_token.
|
||||
String idToken = extractIdToken(attributes);
|
||||
if (!StringUtils.hasText(idToken)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
if (!(principal instanceof OAuth2AuthenticationToken authenticationToken)
|
||||
|| authenticationToken.getClass() != OAuth2AuthenticationToken.class) {
|
||||
// If the application customizes the authentication result, then a custom
|
||||
// handler should be provided.
|
||||
return Mono.empty();
|
||||
}
|
||||
// The current principal must be an OidcUser.
|
||||
if (!(authenticationToken.getPrincipal() instanceof OidcUser existingOidcUser)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
|
||||
// The registrationId must match the one used to log in.
|
||||
if (!authenticationToken.getAuthorizedClientRegistrationId().equals(clientRegistration.getRegistrationId())) {
|
||||
return Mono.empty();
|
||||
}
|
||||
// Create, validate OidcIdToken and refresh OidcUser in the SecurityContext.
|
||||
return Mono.justOrEmpty((ServerWebExchange) attributes.get(ServerWebExchange.class.getName()))
|
||||
.switchIfEmpty(currentServerWebExchangeMono)
|
||||
.flatMap((exchange) -> {
|
||||
ReactiveJwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
||||
return jwtDecoder.decode(idToken).onErrorMap(JwtException.class, (ex) -> {
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(),
|
||||
null);
|
||||
return new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
|
||||
})
|
||||
.map((jwt) -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
|
||||
jwt.getClaims()))
|
||||
.doOnNext((oidcIdToken) -> validateIdToken(existingOidcUser, oidcIdToken))
|
||||
.flatMap((oidcIdToken) -> {
|
||||
OidcUserRequest userRequest = new OidcUserRequest(clientRegistration,
|
||||
authorizedClient.getAccessToken(), oidcIdToken);
|
||||
return this.userService.loadUser(userRequest);
|
||||
})
|
||||
.flatMap((oidcUser) -> refreshSecurityContext(exchange, clientRegistration, authenticationToken,
|
||||
oidcUser));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link ServerSecurityContextRepository} to use for refreshing a
|
||||
* {@link SecurityContext}, defaults to
|
||||
* {@link WebSessionServerSecurityContextRepository}.
|
||||
* @param serverSecurityContextRepository the {@link ServerSecurityContextRepository}
|
||||
* to use
|
||||
*/
|
||||
public void setServerSecurityContextRepository(ServerSecurityContextRepository serverSecurityContextRepository) {
|
||||
Assert.notNull(serverSecurityContextRepository, "serverSecurityContextRepository cannot be null");
|
||||
this.serverSecurityContextRepository = serverSecurityContextRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link ReactiveJwtDecoderFactory} to use for decoding refreshed oidc
|
||||
* id-token, defaults to {@link ReactiveOidcIdTokenDecoderFactory}.
|
||||
* @param jwtDecoderFactory the {@link ReactiveJwtDecoderFactory} to use
|
||||
*/
|
||||
public void setJwtDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory) {
|
||||
Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null");
|
||||
this.jwtDecoderFactory = jwtDecoderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link ReactiveOAuth2UserService} to use for loading an {@link OidcUser}
|
||||
* from refreshed oidc id-token, defaults to {@link OidcReactiveOAuth2UserService}.
|
||||
* @param userService the {@link ReactiveOAuth2UserService} to use
|
||||
*/
|
||||
public void setUserService(ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService) {
|
||||
Assert.notNull(userService, "userService cannot be null");
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link GrantedAuthoritiesMapper} to use for mapping
|
||||
* {@link GrantedAuthority}s, defaults to no-op implementation.
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} to use
|
||||
*/
|
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OidcIdToken#getIssuedAt()} to match the existing
|
||||
* {@link OidcUser#getIdToken()}'s issuedAt time, defaults to 60 seconds.
|
||||
* @param clockSkew the maximum acceptable clock skew to use
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
Assert.notNull(clockSkew, "clockSkew cannot be null");
|
||||
Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0");
|
||||
this.clockSkew = clockSkew;
|
||||
}
|
||||
|
||||
private String extractIdToken(Map<String, Object> attributes) {
|
||||
if (attributes.get(OidcParameterNames.ID_TOKEN) instanceof String idToken) {
|
||||
return idToken;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void validateIdToken(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
// OpenID Connect Core 1.0 - Section 12.2 Successful Refresh Response
|
||||
// If an ID Token is returned as a result of a token refresh request, the
|
||||
// following requirements apply:
|
||||
// its iss Claim Value MUST be the same as in the ID Token issued when the
|
||||
// original authentication occurred,
|
||||
validateIssuer(existingOidcUser, idToken);
|
||||
// its sub Claim Value MUST be the same as in the ID Token issued when the
|
||||
// original authentication occurred,
|
||||
validateSubject(existingOidcUser, idToken);
|
||||
// its iat Claim MUST represent the time that the new ID Token is issued,
|
||||
validateIssuedAt(existingOidcUser, idToken);
|
||||
// its aud Claim Value MUST be the same as in the ID Token issued when the
|
||||
// original authentication occurred,
|
||||
validateAudience(existingOidcUser, idToken);
|
||||
// if the ID Token contains an auth_time Claim, its value MUST represent the time
|
||||
// of the original authentication - not the time that the new ID token is issued,
|
||||
validateAuthenticatedAt(existingOidcUser, idToken);
|
||||
// it SHOULD NOT have a nonce Claim, even when the ID Token issued at the time of
|
||||
// the original authentication contained nonce; however, if it is present, its
|
||||
// value MUST be the same as in the ID Token issued at the time of the original
|
||||
// authentication,
|
||||
validateNonce(existingOidcUser, idToken);
|
||||
}
|
||||
|
||||
private void validateIssuer(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
if (!idToken.getIssuer().toString().equals(existingOidcUser.getIdToken().getIssuer().toString())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid issuer",
|
||||
REFRESH_TOKEN_RESPONSE_ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateSubject(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
if (!idToken.getSubject().equals(existingOidcUser.getIdToken().getSubject())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid subject",
|
||||
REFRESH_TOKEN_RESPONSE_ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIssuedAt(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
if (!idToken.getIssuedAt().isAfter(existingOidcUser.getIdToken().getIssuedAt().minus(this.clockSkew))) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid issued at time",
|
||||
REFRESH_TOKEN_RESPONSE_ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAudience(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
if (!isValidAudience(existingOidcUser, idToken)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid audience",
|
||||
REFRESH_TOKEN_RESPONSE_ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidAudience(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
List<String> idTokenAudiences = idToken.getAudience();
|
||||
Set<String> oidcUserAudiences = new HashSet<>(existingOidcUser.getIdToken().getAudience());
|
||||
if (idTokenAudiences.size() != oidcUserAudiences.size()) {
|
||||
return false;
|
||||
}
|
||||
for (String audience : idTokenAudiences) {
|
||||
if (!oidcUserAudiences.contains(audience)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void validateAuthenticatedAt(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
if (idToken.getAuthenticatedAt() == null) {
|
||||
return;
|
||||
}
|
||||
if (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid authenticated at time",
|
||||
REFRESH_TOKEN_RESPONSE_ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNonce(OidcUser existingOidcUser, OidcIdToken idToken) {
|
||||
if (!StringUtils.hasText(idToken.getNonce())) {
|
||||
return;
|
||||
}
|
||||
if (!idToken.getNonce().equals(existingOidcUser.getIdToken().getNonce())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE, "Invalid nonce",
|
||||
REFRESH_TOKEN_RESPONSE_ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<Void> refreshSecurityContext(ServerWebExchange exchange, ClientRegistration clientRegistration,
|
||||
OAuth2AuthenticationToken authenticationToken, OidcUser oidcUser) {
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
|
||||
.mapAuthorities(oidcUser.getAuthorities());
|
||||
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(oidcUser, mappedAuthorities,
|
||||
clientRegistration.getRegistrationId());
|
||||
authenticationResult.setDetails(authenticationToken.getDetails());
|
||||
SecurityContext securityContext = new SecurityContextImpl(authenticationResult);
|
||||
return this.serverSecurityContextRepository.save(exchange, securityContext);
|
||||
}
|
||||
|
||||
}
|
||||
+6
-33
@@ -21,9 +21,7 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -42,7 +40,6 @@ import org.springframework.util.Assert;
|
||||
* {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AuthorizedClientProvider
|
||||
* @see WebClientReactiveRefreshTokenTokenResponseClient
|
||||
@@ -52,8 +49,6 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient();
|
||||
|
||||
private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
@@ -101,17 +96,12 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
|
||||
.flatMap(this.accessTokenResponseClient::getTokenResponse)
|
||||
.onErrorMap(OAuth2AuthorizationException.class,
|
||||
(e) -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(), e))
|
||||
.flatMap((tokenResponse) -> {
|
||||
OAuth2AuthorizedClient refreshedClient = new OAuth2AuthorizedClient(clientRegistration,
|
||||
context.getPrincipal().getName(), tokenResponse.getAccessToken(),
|
||||
tokenResponse.getRefreshToken());
|
||||
Map<String, Object> attributes = new HashMap<>(context.getAttributes());
|
||||
attributes.putAll(tokenResponse.getAdditionalParameters());
|
||||
return this.authorizationSuccessHandler
|
||||
.onAuthorizationSuccess(refreshedClient, context.getPrincipal(),
|
||||
Collections.unmodifiableMap(attributes))
|
||||
.thenReturn(refreshedClient);
|
||||
});
|
||||
.map((tokenResponse) -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()));
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(OAuth2Token token) {
|
||||
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,19 +116,6 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link ReactiveOAuth2AuthorizationSuccessHandler} to use for handling
|
||||
* successful refresh token response, defaults to
|
||||
* {@link RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler}.
|
||||
* @param authorizationSuccessHandler the
|
||||
* {@link ReactiveOAuth2AuthorizationSuccessHandler} to use
|
||||
* @since 7.1
|
||||
*/
|
||||
public void setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
|
||||
Assert.notNull(authorizationSuccessHandler, "authorizationSuccessHandler cannot be null");
|
||||
this.authorizationSuccessHandler = authorizationSuccessHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
@@ -166,8 +143,4 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(OAuth2Token token) {
|
||||
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-36
@@ -16,13 +16,8 @@
|
||||
|
||||
package org.springframework.security.oauth2.client.aot.hint;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} for OAuth2 Client
|
||||
@@ -32,20 +27,9 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
class OAuth2ClientRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
private static final boolean r2dbcPresent;
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
|
||||
r2dbcPresent = ClassUtils.isPresent("io.r2dbc.spi.Row", classLoader)
|
||||
&& ClassUtils.isPresent("org.springframework.r2dbc.core.DatabaseClient", classLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
registerOAuth2ClientSchemaFilesHints(hints);
|
||||
if (r2dbcPresent) {
|
||||
registerR2dbcHints(hints);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerOAuth2ClientSchemaFilesHints(RuntimeHints hints) {
|
||||
@@ -54,23 +38,4 @@ class OAuth2ClientRuntimeHints implements RuntimeHintsRegistrar {
|
||||
.registerPattern("org/springframework/security/oauth2/client/oauth2-client-schema-postgres.sql");
|
||||
}
|
||||
|
||||
private void registerR2dbcHints(RuntimeHints hints) {
|
||||
// Register R2DBC OAuth2 client service types
|
||||
hints.reflection()
|
||||
.registerType(
|
||||
TypeReference
|
||||
.of("org.springframework.security.oauth2.client.R2dbcReactiveOAuth2AuthorizedClientService"),
|
||||
(builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS));
|
||||
|
||||
// Register OAuth2 client types that may be serialized in R2DBC scenarios
|
||||
hints.reflection()
|
||||
.registerTypes(java.util.List.of(
|
||||
TypeReference.of("org.springframework.security.oauth2.client.OAuth2AuthorizedClient"),
|
||||
TypeReference
|
||||
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken")),
|
||||
(builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-10
@@ -105,7 +105,7 @@ public final class ClientRegistrations {
|
||||
* @return the {@link ClientRegistration} built from the configuration
|
||||
*/
|
||||
public static ClientRegistration.Builder fromOidcConfiguration(Map<String, Object> configuration) {
|
||||
OIDCProviderMetadata metadata = parseInput(configuration, OIDCProviderMetadata::parse);
|
||||
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
|
||||
ClientRegistration.Builder builder = withProviderConfiguration(metadata, metadata.getIssuer().getValue());
|
||||
builder.jwkSetUri(metadata.getJWKSetURI().toASCIIString());
|
||||
if (metadata.getUserInfoEndpointURI() != null) {
|
||||
@@ -292,15 +292,6 @@ public final class ClientRegistrations {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
|
||||
private static <T> T parseInput(Map<String, Object> body, ThrowingFunction<JSONObject, T, ParseException> parser) {
|
||||
try {
|
||||
return parse(body, parser);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T parse(Map<String, Object> body, ThrowingFunction<JSONObject, T, ParseException> parser) {
|
||||
try {
|
||||
return parser.apply(new JSONObject(body));
|
||||
|
||||
+3
-3
@@ -318,14 +318,14 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
|
||||
return Mono.justOrEmpty(serverWebExchange)
|
||||
.switchIfEmpty(currentServerWebExchangeMono)
|
||||
.flatMap((exchange) -> {
|
||||
Map<String, Object> contextAttributes = new HashMap<>();
|
||||
contextAttributes.put(ServerWebExchange.class.getName(), serverWebExchange);
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
String scope = exchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.SCOPE);
|
||||
if (StringUtils.hasText(scope)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME,
|
||||
StringUtils.delimitedListToStringArray(scope, " "));
|
||||
}
|
||||
return Mono.just(Collections.unmodifiableMap(contextAttributes));
|
||||
return Mono.just(contextAttributes);
|
||||
})
|
||||
.defaultIfEmpty(Collections.emptyMap());
|
||||
// @formatter:on
|
||||
|
||||
+2
-34
@@ -51,8 +51,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
@@ -98,7 +96,6 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
* @author Rob Winch
|
||||
* @author Joe Grandja
|
||||
* @author Phil Clay
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.1
|
||||
*/
|
||||
public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements ExchangeFilterFunction {
|
||||
@@ -142,8 +139,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||
|
||||
private ClientResponseHandler clientResponseHandler;
|
||||
|
||||
private ServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
|
||||
/**
|
||||
* Constructs a {@code ServerOAuth2AuthorizedClientExchangeFilterFunction} using the
|
||||
* provided parameters.
|
||||
@@ -335,11 +330,8 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||
}
|
||||
|
||||
private Mono<ClientResponse> exchangeAndHandleResponse(ClientRequest request, ExchangeFunction next) {
|
||||
// Re-request an Authentication from serverSecurityContextRepository since it
|
||||
// might have been changed during provider invocation.
|
||||
return effectiveAuthentication(request).flatMap((authentication) -> next.exchange(request)
|
||||
.transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono))
|
||||
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication)));
|
||||
return next.exchange(request)
|
||||
.transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono));
|
||||
}
|
||||
|
||||
private Mono<OAuth2AuthorizedClient> authorizedClient(ClientRequest request) {
|
||||
@@ -370,17 +362,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private Mono<Authentication> effectiveAuthentication(ClientRequest request) {
|
||||
// @formatter:off
|
||||
return effectiveServerWebExchange(request)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.flatMap(this.serverSecurityContextRepository::load)
|
||||
.mapNotNull(SecurityContext::getAuthentication)
|
||||
.switchIfEmpty(this.currentAuthenticationMono);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Mono} the emits the {@code clientRegistrationId} that is active
|
||||
* for the given request.
|
||||
@@ -464,19 +445,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||
this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link ServerSecurityContextRepository} to use for re-obtaining a
|
||||
* {@link SecurityContext} if it has been refreshed during provider invocation,
|
||||
* defaults to {@link WebSessionServerSecurityContextRepository}.
|
||||
* @param serverSecurityContextRepository the {@link ServerSecurityContextRepository}
|
||||
* to use
|
||||
* @since 7.1
|
||||
*/
|
||||
public void setServerSecurityContextRepository(ServerSecurityContextRepository serverSecurityContextRepository) {
|
||||
Assert.notNull(serverSecurityContextRepository, "serverSecurityContextRepository cannot be null");
|
||||
this.serverSecurityContextRepository = serverSecurityContextRepository;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ClientResponseHandler {
|
||||
|
||||
|
||||
-398
@@ -1,398 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.oauth2.client;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
|
||||
|
||||
@Test
|
||||
void setServerSecurityContextRepositoryWhenNullThenException() {
|
||||
assertThatException()
|
||||
.isThrownBy(() -> new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler()
|
||||
.setServerSecurityContextRepository(null))
|
||||
.withMessage("serverSecurityContextRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setJwtDecoderFactoryWhenNullThenException() {
|
||||
assertThatException()
|
||||
.isThrownBy(() -> new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler().setJwtDecoderFactory(null))
|
||||
.withMessage("jwtDecoderFactory cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setAuthoritiesMapperWhenNullThenException() {
|
||||
assertThatException()
|
||||
.isThrownBy(() -> new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler().setAuthoritiesMapper(null))
|
||||
.withMessage("authoritiesMapper cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setUserServiceWhenNullThenException() {
|
||||
assertThatException()
|
||||
.isThrownBy(() -> new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler().setUserService(null))
|
||||
.withMessage("userService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setClockSkewWhenNullThenException() {
|
||||
assertThatException()
|
||||
.isThrownBy(() -> new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler().setClockSkew(null))
|
||||
.withMessage("clockSkew cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenValidThenSecurityContextRefreshed() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", principal.getAudience());
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyComplete();
|
||||
StepVerifier.create(serverSecurityContextRepository.load(exchange).map(SecurityContext::getAuthentication))
|
||||
.expectNext(authenticationToken)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenIssuerNotSameThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", "https://issuer.com");
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", principal.getAudience());
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyErrorMessage("[invalid_id_token] Invalid issuer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenSubNotSameThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", "invalid_sub");
|
||||
claims.put("aud", principal.getAudience());
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyErrorMessage("[invalid_id_token] Invalid subject");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenIatNotAfterThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", principal.getAudience());
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt().minus(Duration.ofDays(1)));
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyErrorMessage("[invalid_id_token] Invalid issued at time");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenAudEmptyThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", Collections.emptyList());
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyErrorMessage("[invalid_id_token] Invalid audience");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenAudNotContainThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", List.of("invalid_client-id"));
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyErrorMessage("[invalid_id_token] Invalid audience");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenAuthTimeNotSameThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", principal.getAudience());
|
||||
claims.put("auth_time", principal.getIssuedAt());
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyErrorMessage("[invalid_id_token] Invalid authenticated at time");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenNonceNotSameThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
DefaultOidcUser principal = TestOidcUsers.create();
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", principal.getAudience());
|
||||
claims.put("nonce", "invalid_nonce");
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
WebSessionServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
handler.setServerSecurityContextRepository(serverSecurityContextRepository);
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyErrorMessage("[invalid_nonce] Invalid nonce");
|
||||
}
|
||||
|
||||
private static OAuth2AccessToken createAccessToken() {
|
||||
Instant issuedAt = Instant.now().minus(Duration.ofDays(1));
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofMinutes(60));
|
||||
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token-1234", issuedAt, expiresAt,
|
||||
Set.of(OidcScopes.OPENID));
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user