Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33d83158dd | |||
| 5870a45a3d | |||
| cdcfb5d6c2 | |||
| 7e8c179a3f | |||
| 086f2394cb | |||
| 46c89f1305 | |||
| a9f5a54916 | |||
| 3629186b20 | |||
| a21a840035 | |||
| 3e9acc89e9 | |||
| 796e4d6b6c | |||
| 587aa495f7 | |||
| 96b2a4fd35 | |||
| eee0dac835 | |||
| 2bf0b0a75e | |||
| e4e82d89d9 | |||
| 161b0f303d | |||
| dcf5cc9e06 | |||
| c885cee719 | |||
| d239806987 | |||
| 6a88784064 | |||
| e59f5a9771 | |||
| 90533d26b5 | |||
| 5437bf517c | |||
| 5bd4db1a13 | |||
| d6874d9048 | |||
| f184d13096 | |||
| beff600d95 | |||
| ec4cad6657 | |||
| 230dae602f | |||
| 527f816ff8 | |||
| 62c68cb7ac | |||
| 148e7843bf | |||
| ce018ff003 | |||
| 5496eca360 | |||
| 2e7f61d794 | |||
| c6017e7c28 | |||
| 88dc249fde | |||
| ebb842e654 | |||
| 41f1706b06 | |||
| ba38cdad0e | |||
| 3b9a2c3def | |||
| f01ffd136c | |||
| 298a75ebbd | |||
| b30a1d4cae | |||
| 19cde35bc1 | |||
| 773e86701e | |||
| 207680ba02 | |||
| 5b44972e14 | |||
| 6bd2f1ca97 | |||
| 7b39800606 | |||
| 1e2900328b | |||
| 850c0a4690 | |||
| f532807836 | |||
| 48826201b1 | |||
| ceb278c908 | |||
| e7212b37f7 | |||
| a9aefafb76 | |||
| 9f0b2a29ee | |||
| e076a4b838 | |||
| a7a8494ee4 | |||
| d159a2e8fe | |||
| 7ea7749dba | |||
| 133eb89708 | |||
| d16f2655c9 | |||
| 37c6e217e9 | |||
| d880aa95d0 | |||
| 8572764583 | |||
| a45888958c | |||
| 694bc038d2 | |||
| ff0eeaddea | |||
| 8c6658d890 | |||
| dd7472272a | |||
| 902aff451f | |||
| bef63469dd | |||
| d5328df82d | |||
| 644c5ed1e1 | |||
| 672902a8f3 | |||
| a529607d42 | |||
| 78d2be9bd5 | |||
| 55895f3b08 | |||
| ef5c1a72db | |||
| fd1246a0b0 | |||
| 16f7590740 | |||
| 70213258ef | |||
| caee65b2e4 | |||
| cbd1340e78 | |||
| cdd3ebed2c | |||
| 5a6c87d999 | |||
| aa9c1bab67 | |||
| 1736a8680e | |||
| 88f6c5a01c | |||
| dce96b012a | |||
| 0e7566ede3 | |||
| 4c780bf8d4 | |||
| 470cb46e38 | |||
| 8fd9997a47 | |||
| 982ee7dc17 | |||
| 7173f675c9 | |||
| dcbc0d6070 | |||
| 33c0ebdffe | |||
| 2175f2878d | |||
| 538536aa4d | |||
| 62d2fd0efd | |||
| ba2d0cc169 | |||
| 55a704d2a7 | |||
| c02124ffb4 | |||
| 5a3f1c8b4b | |||
| af03f4567d | |||
| 7eaab95639 | |||
| 4e52eda0f5 | |||
| 60a6b3845d | |||
| 4cbeea78a8 | |||
| b3b53e47ea | |||
| e4fc79afc5 | |||
| e977c2a3e9 | |||
| 68aef2056e | |||
| edd6182fb0 | |||
| 35d738f583 | |||
| 2c4e0c55ae | |||
| f0fe62f219 | |||
| a21c5161e8 | |||
| 9bb9c930d2 | |||
| 0e90f762d3 | |||
| 33a108f974 | |||
| a65c182798 | |||
| 07bb9db24e | |||
| 725e35977b | |||
| 5bffa4e454 | |||
| b4c8fdf91d | |||
| 7c43fc111f | |||
| ed2b654f71 | |||
| 2442ba3b47 | |||
| 1dfbc2d9e4 | |||
| b4c5a82c2e | |||
| c5cba930b0 | |||
| 8f167d93a7 | |||
| aa9e1b5088 | |||
| 4ca0de9c2d | |||
| a7f9ccb6d6 | |||
| 24e3bb11bc | |||
| 87ee464dce | |||
| 077439c73e | |||
| 1b2d2ed611 | |||
| 0cbec13f87 | |||
| 44f9396bad | |||
| 3acd2c65d9 | |||
| 81abc453fe | |||
| 7c45ebd81c | |||
| 99aee99b34 | |||
| b41ec0ae4b | |||
| 55e82d137f | |||
| 654b15d8c2 | |||
| 1665824637 | |||
| 8ae7cb175b | |||
| 9101bf1f7d | |||
| b73155df26 | |||
| c4d8693063 | |||
| 6695784a3f | |||
| 801e808f67 | |||
| f104d1aeea | |||
| 3b7f714f00 | |||
| 9a714424d5 | |||
| aa9bf83c6d | |||
| 63f48167bd | |||
| fbeb82ef62 | |||
| b5e9c4cb9f | |||
| 3cd9b77273 | |||
| 478c3d0313 | |||
| ee9f5a2d5e | |||
| 17064fc7fb | |||
| 58747d2621 | |||
| fdf4689f79 | |||
| c89647a56e | |||
| 9f44f3b79a | |||
| c534e5ea5b | |||
| adc9f0f36e | |||
| 7da803f00c | |||
| 67853d585e | |||
| 9b1e9c5db9 | |||
| 133c87a643 | |||
| 8b0ad045d3 | |||
| 5e2209aeef | |||
| 2482e8e446 | |||
| 046a1fc811 | |||
| 3002a82705 | |||
| 9b2910cd42 | |||
| 3af85a562a | |||
| 1d3cb3f28e | |||
| 752a56a9d9 | |||
| cd128fb66d | |||
| d3e5955642 | |||
| f6ea99d8a3 |
@@ -54,6 +54,31 @@ updates:
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- version-update:semver-minor
|
||||
- package-ecosystem: gradle
|
||||
target-branch: 6.3.x
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
time: '03:00'
|
||||
timezone: Etc/UTC
|
||||
labels:
|
||||
- 'type: dependency-upgrade'
|
||||
registries:
|
||||
- spring-milestones
|
||||
ignore:
|
||||
- dependency-name: com.nimbusds:nimbus-jose-jwt
|
||||
- dependency-name: org.python:jython
|
||||
- dependency-name: org.apache.directory.server:*
|
||||
- dependency-name: org.junit:junit-bom
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- dependency-name: org.mockito:mockito-bom
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- dependency-name: '*'
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- version-update:semver-minor
|
||||
- package-ecosystem: gradle
|
||||
target-branch: main
|
||||
directory: /
|
||||
@@ -83,6 +108,7 @@ updates:
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- version-update:semver-minor
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: 5.8.x
|
||||
directory: /
|
||||
@@ -93,6 +119,26 @@ updates:
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: 6.2.x
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: 6.3.x
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: main
|
||||
directory: /
|
||||
@@ -125,6 +171,11 @@ updates:
|
||||
directory: /docs
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: npm
|
||||
target-branch: 6.3.x
|
||||
directory: /docs
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: npm
|
||||
target-branch: 6.2.x
|
||||
directory: /docs
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
toolchain: 17
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.1.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2023.1.+ --stacktrace
|
||||
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ --stacktrace
|
||||
secrets: inherit
|
||||
check-samples:
|
||||
name: Check Samples
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}')
|
||||
./gradlew publishMavenJavaPublicationToLocalRepository
|
||||
./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR"
|
||||
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests
|
||||
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" check
|
||||
check-tangles:
|
||||
name: Check for Package Tangles
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v3
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Upgrade Wrappers
|
||||
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
|
||||
env:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# List of active maintenance branches.
|
||||
branch: [ main, 6.2.x, 6.1.x, 5.8.x ]
|
||||
branch: [ main, 6.3.x, 6.2.x, 5.8.x ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Update Antora UI Spring
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-antora-ui-spring:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on Supported Branches
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@852920ba3fb1f28b35a2f13201133bc00ef33677
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: ${{ matrix.branch }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
antora-file-path: 'docs/antora-playbook.yml'
|
||||
update-antora-ui-spring-docs-build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on docs-build
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@852920ba3fb1f28b35a2f13201133bc00ef33677
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: 'docs-build'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
+1
-1
@@ -18,7 +18,7 @@ Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_COND
|
||||
See https://docs.spring.io/spring-security/reference/getting-spring-security.html[Getting Spring Security] for how to obtain Spring Security.
|
||||
|
||||
== Documentation
|
||||
Be sure to read the https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference].
|
||||
Be sure to read the https://docs.spring.io/spring-security/reference/[Spring Security Reference].
|
||||
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
|
||||
|
||||
== Quick Start
|
||||
|
||||
+18
-1
@@ -17,10 +17,13 @@
|
||||
package org.springframework.security.acls.domain;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.acls.model.Acl;
|
||||
import org.springframework.security.acls.model.Sid;
|
||||
import org.springframework.security.acls.model.SidRetrievalStrategy;
|
||||
@@ -59,6 +62,8 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
|
||||
|
||||
private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
|
||||
|
||||
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
|
||||
|
||||
/**
|
||||
* Constructor. The only mandatory parameter relates to the system-wide
|
||||
* {@link GrantedAuthority} instances that can be held to always permit ACL changes.
|
||||
@@ -100,7 +105,9 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
|
||||
}
|
||||
|
||||
// Iterate this principal's authorities to determine right
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
|
||||
Collection<? extends GrantedAuthority> reachableGrantedAuthorities = this.roleHierarchy
|
||||
.getReachableGrantedAuthorities(authentication.getAuthorities());
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities);
|
||||
if (acl.getOwner() instanceof GrantedAuthoritySid
|
||||
&& authorities.contains(((GrantedAuthoritySid) acl.getOwner()).getGrantedAuthority())) {
|
||||
return;
|
||||
@@ -162,4 +169,14 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
|
||||
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RoleHierarchy} to use. The default is to use a
|
||||
* {@link NullRoleHierarchy}
|
||||
* @since 6.4
|
||||
*/
|
||||
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
|
||||
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
|
||||
this.roleHierarchy = roleHierarchy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
||||
import org.springframework.security.acls.model.Acl;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -34,6 +35,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -86,6 +88,15 @@ public class AclAuthorizationStrategyImplTests {
|
||||
this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityCheckWhenRoleReachableByHierarchyThenAuthorized() {
|
||||
given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH_B"));
|
||||
this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN"));
|
||||
this.strategy.setRoleHierarchy(RoleHierarchyImpl.fromHierarchy("ROLE_AUTH > ROLE_AUTH_B"));
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityCheckWhenCustomSecurityContextHolderStrategyThenUses() {
|
||||
given(this.securityContextHolderStrategy.getContext()).willReturn(this.context);
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ wrapperUpgrade {
|
||||
gradle {
|
||||
'spring-security' {
|
||||
repo = 'spring-projects/spring-security'
|
||||
baseBranch = '6.1.x' // runs only on 6.1.x and the update is merged forward to main
|
||||
baseBranch = '6.2.x' // runs only on 6.2.x and the update is merged forward to main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -56,6 +56,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Scott Battaglia
|
||||
* @author Kim Youngwoong
|
||||
*/
|
||||
public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
|
||||
|
||||
@@ -63,7 +64,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
|
||||
|
||||
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService;
|
||||
|
||||
private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
|
||||
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
|
||||
|
||||
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||
|
||||
@@ -187,6 +188,17 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
|
||||
this.authenticationUserDetailsService = authenticationUserDetailsService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UserDetailsChecker to be used for checking the status of retrieved user
|
||||
* details. This allows customization of the UserDetailsChecker implementation.
|
||||
* @param userDetailsChecker the UserDetailsChecker to be set
|
||||
* @since 6.4
|
||||
*/
|
||||
public void setUserDetailsChecker(final UserDetailsChecker userDetailsChecker) {
|
||||
Assert.notNull(userDetailsChecker, "userDetailsChecker cannot be null");
|
||||
this.userDetailsChecker = userDetailsChecker;
|
||||
}
|
||||
|
||||
public void setServiceProperties(final ServiceProperties serviceProperties) {
|
||||
this.serviceProperties = serviceProperties;
|
||||
}
|
||||
|
||||
+27
@@ -18,6 +18,7 @@ package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
@@ -31,11 +32,13 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
||||
import org.springframework.security.cas.ServiceProperties;
|
||||
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
|
||||
@@ -55,6 +58,7 @@ import static org.mockito.Mockito.verify;
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Scott Battaglia
|
||||
* @author Kim Youngwoong
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class CasAuthenticationProviderTests {
|
||||
@@ -320,6 +324,29 @@ public class CasAuthenticationProviderTests {
|
||||
assertThat(cap.supports(CasAuthenticationToken.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetUserDetailsChecker() throws AuthenticationException {
|
||||
CasAuthenticationProvider cap = new CasAuthenticationProvider();
|
||||
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
|
||||
cap.setKey("qwerty");
|
||||
cap.setTicketValidator(new MockTicketValidator(true));
|
||||
cap.setServiceProperties(makeServiceProperties());
|
||||
cap.afterPropertiesSet();
|
||||
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("ST-123");
|
||||
|
||||
AtomicInteger checkCount = new AtomicInteger(0);
|
||||
UserDetailsChecker userDetailsChecker = new UserDetailsChecker() {
|
||||
@Override
|
||||
public void check(UserDetails user) {
|
||||
checkCount.incrementAndGet();
|
||||
}
|
||||
};
|
||||
cap.setUserDetailsChecker(userDetailsChecker);
|
||||
cap.authenticate(token);
|
||||
|
||||
assertThat(checkCount.get()).isEqualTo(1);
|
||||
}
|
||||
|
||||
private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -65,7 +65,7 @@ dependencies {
|
||||
testImplementation 'jakarta.websocket:jakarta.websocket-api'
|
||||
testImplementation 'jakarta.websocket:jakarta.websocket-client-api'
|
||||
testImplementation 'ldapsdk:ldapsdk:4.1'
|
||||
testImplementation('net.sourceforge.htmlunit:htmlunit') {
|
||||
testImplementation('org.htmlunit:htmlunit') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
exclude group: 'xml-apis', module: 'xml-apis'
|
||||
}
|
||||
@@ -80,7 +80,7 @@ dependencies {
|
||||
testImplementation "org.hibernate.orm:hibernate-core"
|
||||
testImplementation 'org.hsqldb:hsqldb'
|
||||
testImplementation 'org.mockito:mockito-core'
|
||||
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
|
||||
testImplementation('org.seleniumhq.selenium:htmlunit3-driver') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
exclude group: 'xml-apis', module: 'xml-apis'
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2009-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -96,7 +96,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
||||
pc.getReaderContext()
|
||||
.fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
|
||||
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
|
||||
+ "with Spring Security 6.3. Please update your schema declarations to the 6.3 schema.",
|
||||
+ "with Spring Security 6.4. Please update your schema declarations to the 6.4 schema.",
|
||||
element);
|
||||
}
|
||||
String name = pc.getDelegate().getLocalName(element);
|
||||
@@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
||||
|
||||
private boolean matchesVersionInternal(Element element) {
|
||||
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
|
||||
return schemaLocation.matches("(?m).*spring-security-6\\.3.*.xsd.*")
|
||||
return schemaLocation.matches("(?m).*spring-security-6\\.4.*.xsd.*")
|
||||
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|
||||
|| !schemaLocation.matches("(?m).*spring-security.*");
|
||||
}
|
||||
|
||||
+12
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -63,6 +63,7 @@ import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandle
|
||||
import org.springframework.security.web.firewall.ObservationMarkingRequestRejectedHandler;
|
||||
import org.springframework.security.web.firewall.RequestRejectedHandler;
|
||||
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -296,8 +297,18 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
requestMatcherPrivilegeEvaluatorsEntries
|
||||
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
|
||||
}
|
||||
boolean anyRequestConfigured = false;
|
||||
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
|
||||
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
|
||||
Assert.isTrue(!anyRequestConfigured,
|
||||
"A filter chain that matches any request has already been configured, which means that this filter chain ["
|
||||
+ securityFilterChain
|
||||
+ "] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last.");
|
||||
if (securityFilterChain instanceof DefaultSecurityFilterChain defaultSecurityFilterChain) {
|
||||
if (defaultSecurityFilterChain.getRequestMatcher() instanceof AnyRequestMatcher) {
|
||||
anyRequestConfigured = true;
|
||||
}
|
||||
}
|
||||
securityFilterChains.add(securityFilterChain);
|
||||
requestMatcherPrivilegeEvaluatorsEntries
|
||||
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
|
||||
|
||||
+4
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -285,7 +285,9 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
|
||||
.getClientRegistrationRepository(getBuilder());
|
||||
return new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
|
||||
ResolvableType resolvableType = ResolvableType.forClass(OAuth2AuthorizationRequestResolver.class);
|
||||
OAuth2AuthorizationRequestResolver bean = getBeanOrNull(resolvableType);
|
||||
return (bean != null) ? bean : new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
|
||||
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -31,7 +31,6 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.jwt.BadJwtException;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
@@ -81,8 +80,7 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio
|
||||
.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier))
|
||||
.build();
|
||||
decoder.setJwtValidator(jwtValidator.apply(clientRegistration));
|
||||
decoder.setClaimSetConverter(
|
||||
new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));
|
||||
decoder.setClaimSetConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter());
|
||||
return decoder;
|
||||
};
|
||||
}
|
||||
|
||||
+82
-12
@@ -16,9 +16,13 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.saml2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@@ -33,6 +37,7 @@ import org.springframework.security.saml2.provider.service.authentication.Abstra
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
|
||||
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.OpenSamlAuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
@@ -50,6 +55,7 @@ import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatchers;
|
||||
@@ -111,7 +117,13 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
|
||||
private String loginPage;
|
||||
|
||||
private String authenticationRequestUri = Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI;
|
||||
private String authenticationRequestUri = "/saml2/authenticate";
|
||||
|
||||
private String[] authenticationRequestParams = { "registrationId={registrationId}" };
|
||||
|
||||
private RequestMatcher authenticationRequestMatcher = RequestMatchers.anyOf(
|
||||
new AntPathRequestMatcher(Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI),
|
||||
new AntPathQueryRequestMatcher(this.authenticationRequestUri, this.authenticationRequestParams));
|
||||
|
||||
private Saml2AuthenticationRequestResolver authenticationRequestResolver;
|
||||
|
||||
@@ -196,11 +208,31 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
* Request
|
||||
* @return the {@link Saml2LoginConfigurer} for further configuration
|
||||
* @since 6.0
|
||||
* @deprecated Use {@link #authenticationRequestUriQuery} instead
|
||||
*/
|
||||
public Saml2LoginConfigurer<B> authenticationRequestUri(String authenticationRequestUri) {
|
||||
Assert.state(authenticationRequestUri.contains("{registrationId}"),
|
||||
"authenticationRequestUri must contain {registrationId} path variable");
|
||||
this.authenticationRequestUri = authenticationRequestUri;
|
||||
return authenticationRequestUriQuery(authenticationRequestUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the URL that the SAML Authentication Request will be sent to. This method
|
||||
* also supports query parameters like so: <pre>
|
||||
* authenticationRequestUriQuery("/saml/authenticate?registrationId={registrationId}")
|
||||
* </pre> {@link RelyingPartyRegistrations}
|
||||
* @param authenticationRequestUriQuery the URI and query to use for the SAML 2.0
|
||||
* Authentication Request
|
||||
* @return the {@link Saml2LoginConfigurer} for further configuration
|
||||
* @since 6.0
|
||||
*/
|
||||
public Saml2LoginConfigurer<B> authenticationRequestUriQuery(String authenticationRequestUriQuery) {
|
||||
Assert.state(authenticationRequestUriQuery.contains("{registrationId}"),
|
||||
"authenticationRequestUri must contain {registrationId} path variable or query value");
|
||||
String[] parts = authenticationRequestUriQuery.split("[?&]");
|
||||
this.authenticationRequestUri = parts[0];
|
||||
this.authenticationRequestParams = new String[parts.length - 1];
|
||||
System.arraycopy(parts, 1, this.authenticationRequestParams, 0, parts.length - 1);
|
||||
this.authenticationRequestMatcher = new AntPathQueryRequestMatcher(this.authenticationRequestUri,
|
||||
this.authenticationRequestParams);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -255,7 +287,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
else {
|
||||
Map<String, String> providerUrlMap = getIdentityProviderUrlMap(this.authenticationRequestUri,
|
||||
this.relyingPartyRegistrationRepository);
|
||||
this.authenticationRequestParams, this.relyingPartyRegistrationRepository);
|
||||
boolean singleProvider = providerUrlMap.size() == 1;
|
||||
if (singleProvider) {
|
||||
// Setup auto-redirect to provider login page
|
||||
@@ -336,8 +368,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
|
||||
relyingPartyRegistrationRepository(http));
|
||||
openSaml4AuthenticationRequestResolver
|
||||
.setRequestMatcher(new AntPathRequestMatcher(this.authenticationRequestUri));
|
||||
openSaml4AuthenticationRequestResolver.setRequestMatcher(this.authenticationRequestMatcher);
|
||||
return openSaml4AuthenticationRequestResolver;
|
||||
}
|
||||
|
||||
@@ -382,20 +413,28 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
return;
|
||||
}
|
||||
loginPageGeneratingFilter.setSaml2LoginEnabled(true);
|
||||
loginPageGeneratingFilter.setSaml2AuthenticationUrlToProviderName(
|
||||
this.getIdentityProviderUrlMap(this.authenticationRequestUri, this.relyingPartyRegistrationRepository));
|
||||
loginPageGeneratingFilter
|
||||
.setSaml2AuthenticationUrlToProviderName(this.getIdentityProviderUrlMap(this.authenticationRequestUri,
|
||||
this.authenticationRequestParams, this.relyingPartyRegistrationRepository));
|
||||
loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());
|
||||
loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, String> getIdentityProviderUrlMap(String authRequestPrefixUrl,
|
||||
private Map<String, String> getIdentityProviderUrlMap(String authRequestPrefixUrl, String[] authRequestQueryParams,
|
||||
RelyingPartyRegistrationRepository idpRepo) {
|
||||
Map<String, String> idps = new LinkedHashMap<>();
|
||||
if (idpRepo instanceof Iterable) {
|
||||
Iterable<RelyingPartyRegistration> repo = (Iterable<RelyingPartyRegistration>) idpRepo;
|
||||
repo.forEach((p) -> idps.put(authRequestPrefixUrl.replace("{registrationId}", p.getRegistrationId()),
|
||||
p.getRegistrationId()));
|
||||
StringBuilder authRequestQuery = new StringBuilder("?");
|
||||
for (String authRequestQueryParam : authRequestQueryParams) {
|
||||
authRequestQuery.append(authRequestQueryParam + "&");
|
||||
}
|
||||
authRequestQuery.deleteCharAt(authRequestQuery.length() - 1);
|
||||
String authenticationRequestUriQuery = authRequestPrefixUrl + authRequestQuery;
|
||||
repo.forEach(
|
||||
(p) -> idps.put(authenticationRequestUriQuery.replace("{registrationId}", p.getRegistrationId()),
|
||||
p.getRegistrationId()));
|
||||
}
|
||||
return idps;
|
||||
}
|
||||
@@ -437,4 +476,35 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
}
|
||||
|
||||
static class AntPathQueryRequestMatcher implements RequestMatcher {
|
||||
|
||||
private final RequestMatcher matcher;
|
||||
|
||||
AntPathQueryRequestMatcher(String path, String... params) {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
matchers.add(new AntPathRequestMatcher(path));
|
||||
for (String param : params) {
|
||||
String[] parts = param.split("=");
|
||||
if (parts.length == 1) {
|
||||
matchers.add(new ParameterRequestMatcher(parts[0]));
|
||||
}
|
||||
else {
|
||||
matchers.add(new ParameterRequestMatcher(parts[0], parts[1]));
|
||||
}
|
||||
}
|
||||
this.matcher = new AndRequestMatcher(matchers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return matcher(request).isMatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchResult matcher(HttpServletRequest request) {
|
||||
return this.matcher.matcher(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.security.config.annotation.web.configurers.saml2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -60,6 +58,7 @@ import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
@@ -508,23 +507,6 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
|
||||
}
|
||||
|
||||
private static class ParameterRequestMatcher implements RequestMatcher {
|
||||
|
||||
Predicate<String> test = Objects::nonNull;
|
||||
|
||||
String name;
|
||||
|
||||
ParameterRequestMatcher(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return this.test.test(request.getParameter(this.name));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Saml2RelyingPartyInitiatedLogoutFilter extends LogoutFilter {
|
||||
|
||||
Saml2RelyingPartyInitiatedLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) {
|
||||
|
||||
+2
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.security.config.http;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.w3c.dom.Element;
|
||||
@@ -44,6 +42,7 @@ import org.springframework.security.web.authentication.logout.SecurityContextLog
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -228,23 +227,6 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser {
|
||||
return this.logoutFilter;
|
||||
}
|
||||
|
||||
private static class ParameterRequestMatcher implements RequestMatcher {
|
||||
|
||||
Predicate<String> test = Objects::nonNull;
|
||||
|
||||
String name;
|
||||
|
||||
ParameterRequestMatcher(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return this.test.test(request.getParameter(this.name));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Saml2RequestMatcher implements RequestMatcher {
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
|
||||
+6
-3
@@ -4532,9 +4532,12 @@ public class ServerHttpSecurity {
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationRequestRedirectWebFilter getRedirectWebFilter() {
|
||||
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter;
|
||||
if (this.authorizationRequestResolver != null) {
|
||||
return new OAuth2AuthorizationRequestRedirectWebFilter(this.authorizationRequestResolver);
|
||||
ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = this.authorizationRequestResolver;
|
||||
if (authorizationRequestResolver == null) {
|
||||
authorizationRequestResolver = getBeanOrNull(ServerOAuth2AuthorizationRequestResolver.class);
|
||||
}
|
||||
if (authorizationRequestResolver != null) {
|
||||
return new OAuth2AuthorizationRequestRedirectWebFilter(authorizationRequestResolver);
|
||||
}
|
||||
return new OAuth2AuthorizationRequestRedirectWebFilter(getClientRegistrationRepository());
|
||||
}
|
||||
|
||||
+67
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,17 +16,21 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web
|
||||
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
|
||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager
|
||||
import org.springframework.security.authorization.AuthorizationDecision
|
||||
import org.springframework.security.authorization.AuthorizationManager
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.web.access.IpAddressAuthorizationManager
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter
|
||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
|
||||
import org.springframework.security.web.access.IpAddressAuthorizationManager
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher
|
||||
@@ -41,10 +45,29 @@ import java.util.function.Supplier
|
||||
* @since 5.7
|
||||
* @property shouldFilterAllDispatcherTypes whether the [AuthorizationFilter] should filter all dispatcher types
|
||||
*/
|
||||
class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() {
|
||||
class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
|
||||
@Deprecated("""
|
||||
Add authorization rules to DispatcherType directly.
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
|
||||
// ...
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
""")
|
||||
var shouldFilterAllDispatcherTypes: Boolean? = null
|
||||
|
||||
private val authorizationRules = mutableListOf<AuthorizationManagerRule>()
|
||||
private val rolePrefix: String
|
||||
private val roleHierarchy: RoleHierarchy
|
||||
|
||||
private val HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"
|
||||
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
|
||||
@@ -190,7 +213,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() {
|
||||
* @return the [AuthorizationManager] with the provided authority
|
||||
*/
|
||||
fun hasAuthority(authority: String): AuthorizationManager<RequestAuthorizationContext> {
|
||||
return AuthorityAuthorizationManager.hasAuthority(authority)
|
||||
val manager = AuthorityAuthorizationManager.hasAuthority<RequestAuthorizationContext>(authority)
|
||||
return withRoleHierarchy(manager)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,7 +224,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() {
|
||||
* @return the [AuthorizationManager] with the provided authorities
|
||||
*/
|
||||
fun hasAnyAuthority(vararg authorities: String): AuthorizationManager<RequestAuthorizationContext> {
|
||||
return AuthorityAuthorizationManager.hasAnyAuthority(*authorities)
|
||||
val manager = AuthorityAuthorizationManager.hasAnyAuthority<RequestAuthorizationContext>(*authorities)
|
||||
return withRoleHierarchy(manager)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +235,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() {
|
||||
* @return the [AuthorizationManager] with the provided role
|
||||
*/
|
||||
fun hasRole(role: String): AuthorizationManager<RequestAuthorizationContext> {
|
||||
return AuthorityAuthorizationManager.hasRole(role)
|
||||
val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(role))
|
||||
return withRoleHierarchy(manager)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +246,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() {
|
||||
* @return the [AuthorizationManager] with the provided roles
|
||||
*/
|
||||
fun hasAnyRole(vararg roles: String): AuthorizationManager<RequestAuthorizationContext> {
|
||||
return AuthorityAuthorizationManager.hasAnyRole(*roles)
|
||||
val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(*roles))
|
||||
return withRoleHierarchy(manager)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,4 +300,37 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.rolePrefix = "ROLE_"
|
||||
this.roleHierarchy = NullRoleHierarchy()
|
||||
}
|
||||
|
||||
constructor(context: ApplicationContext) {
|
||||
val rolePrefix = resolveRolePrefix(context)
|
||||
this.rolePrefix = rolePrefix
|
||||
val roleHierarchy = resolveRoleHierarchy(context)
|
||||
this.roleHierarchy = roleHierarchy
|
||||
}
|
||||
|
||||
private fun resolveRolePrefix(context: ApplicationContext): String {
|
||||
val beanNames = context.getBeanNamesForType(GrantedAuthorityDefaults::class.java)
|
||||
if (beanNames.isNotEmpty()) {
|
||||
return context.getBean(GrantedAuthorityDefaults::class.java).rolePrefix
|
||||
}
|
||||
return "ROLE_";
|
||||
}
|
||||
|
||||
private fun resolveRoleHierarchy(context: ApplicationContext): RoleHierarchy {
|
||||
val beanNames = context.getBeanNamesForType(RoleHierarchy::class.java)
|
||||
if (beanNames.isNotEmpty()) {
|
||||
return context.getBean(RoleHierarchy::class.java)
|
||||
}
|
||||
return NullRoleHierarchy()
|
||||
}
|
||||
|
||||
private fun withRoleHierarchy(manager: AuthorityAuthorizationManager<RequestAuthorizationContext>): AuthorityAuthorizationManager<RequestAuthorizationContext> {
|
||||
manager.setRoleHierarchy(this.roleHierarchy)
|
||||
return manager
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -77,6 +77,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
||||
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
|
||||
|
||||
var authenticationManager: AuthenticationManager? = null
|
||||
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
|
||||
|
||||
/**
|
||||
* Applies a [SecurityConfigurerAdapter] to this [HttpSecurity]
|
||||
@@ -263,6 +264,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
||||
* access for requests
|
||||
* @see [AuthorizeRequestsDsl]
|
||||
*/
|
||||
@Deprecated(message = "Since 6.4. Use authorizeHttpRequests instead")
|
||||
fun authorizeRequests(authorizeRequestsConfiguration: AuthorizeRequestsDsl.() -> Unit) {
|
||||
val authorizeRequestsCustomizer = AuthorizeRequestsDsl().apply(authorizeRequestsConfiguration).get()
|
||||
this.http.authorizeRequests(authorizeRequestsCustomizer)
|
||||
@@ -297,7 +299,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
||||
* @since 5.7
|
||||
*/
|
||||
fun authorizeHttpRequests(authorizeHttpRequestsConfiguration: AuthorizeHttpRequestsDsl.() -> Unit) {
|
||||
val authorizeHttpRequestsCustomizer = AuthorizeHttpRequestsDsl().apply(authorizeHttpRequestsConfiguration).get()
|
||||
val authorizeHttpRequestsCustomizer = AuthorizeHttpRequestsDsl(this.context).apply(authorizeHttpRequestsConfiguration).get()
|
||||
this.http.authorizeHttpRequests(authorizeHttpRequestsCustomizer)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
|
||||
class Saml2Dsl {
|
||||
var relyingPartyRegistrationRepository: RelyingPartyRegistrationRepository? = null
|
||||
var loginPage: String? = null
|
||||
var authenticationRequestUriQuery: String? = null
|
||||
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
|
||||
var authenticationFailureHandler: AuthenticationFailureHandler? = null
|
||||
var failureUrl: String? = null
|
||||
@@ -88,6 +89,9 @@ class Saml2Dsl {
|
||||
defaultSuccessUrlOption?.also {
|
||||
saml2Login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
|
||||
}
|
||||
authenticationRequestUriQuery?.also {
|
||||
saml2Login.authenticationRequestUriQuery(authenticationRequestUriQuery)
|
||||
}
|
||||
authenticationSuccessHandler?.also { saml2Login.successHandler(authenticationSuccessHandler) }
|
||||
authenticationFailureHandler?.also { saml2Login.failureHandler(authenticationFailureHandler) }
|
||||
authenticationManager?.also { saml2Login.authenticationManager(authenticationManager) }
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.security.config.web.server
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilter
|
||||
@@ -65,6 +66,7 @@ operator fun ServerHttpSecurity.invoke(httpConfiguration: ServerHttpSecurityDsl.
|
||||
class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val init: ServerHttpSecurityDsl.() -> Unit) {
|
||||
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
var securityContextRepository: ServerSecurityContextRepository? = null
|
||||
|
||||
/**
|
||||
* Allows configuring the [ServerHttpSecurity] to only be invoked when matching the
|
||||
@@ -718,6 +720,7 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in
|
||||
internal fun build(): SecurityWebFilterChain {
|
||||
init()
|
||||
authenticationManager?.also { this.http.authenticationManager(authenticationManager) }
|
||||
securityContextRepository?.also { this.http.securityContextRepository(securityContextRepository) }
|
||||
return this.http.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.3.xsd
|
||||
#
|
||||
# Copyright 2002-2024 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.4.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd
|
||||
@@ -23,7 +40,8 @@ http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/spri
|
||||
http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.3.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.4.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd
|
||||
|
||||
+1349
File diff suppressed because it is too large
Load Diff
+3821
File diff suppressed because it is too large
Load Diff
+31
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -318,6 +318,14 @@ public class WebSecurityConfigurationTests {
|
||||
assertThat(privilegeEvaluator.isAllowed("/ignoring1/child", null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestThenException() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(MultipleAnyRequestSecurityFilterChainConfig.class).autowire())
|
||||
.havingRootCause()
|
||||
.isExactlyInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
|
||||
Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER");
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse();
|
||||
@@ -819,4 +827,26 @@ public class WebSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class MultipleAnyRequestSecurityFilterChainConfig {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
SecurityFilterChain api1(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests((auth) -> auth.anyRequest().authenticated());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
SecurityFilterChain api2(HttpSecurity http) throws Exception {
|
||||
http.securityMatcher("/app/**").authorizeHttpRequests((auth) -> auth.anyRequest().authenticated());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -52,6 +53,7 @@ public class NamespaceHttpFirewallTests {
|
||||
MockMvc mvc;
|
||||
|
||||
@Test
|
||||
@Disabled("MockMvc uses UriComponentsBuilder::fromUriString which was changed in https://github.com/spring-projects/spring-framework/issues/32513")
|
||||
public void requestWhenPathContainsDoubleDotsThenBehaviorMatchesNamespace() throws Exception {
|
||||
this.rule.register(HttpFirewallConfig.class).autowire();
|
||||
this.mvc.perform(get("/public/../private/")).andExpect(status().isBadRequest());
|
||||
|
||||
+27
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -285,6 +285,22 @@ public class OAuth2ClientConfigurerTests {
|
||||
verify(authorizationRedirectStrategy).sendRedirect(any(), any(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenCustomAuthorizationRequestResolverBeanPresentThenAuthorizationRequestResolverUsed()
|
||||
throws Exception {
|
||||
OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = authorizationRequestResolver;
|
||||
authorizationRequestResolver = mock(OAuth2AuthorizationRequestResolver.class);
|
||||
given(authorizationRequestResolver.resolve(any()))
|
||||
.willAnswer((invocation) -> defaultAuthorizationRequestResolver.resolve(invocation.getArgument(0)));
|
||||
this.spring.register(OAuth2ClientInLambdaConfig.class, AuthorizationRequestResolverConfig.class).autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/oauth2/authorization/registration-1"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
verify(authorizationRequestResolver).resolve(any());
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -362,4 +378,14 @@ public class OAuth2ClientConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AuthorizationRequestResolverConfig {
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizationRequestResolver authorizationRequestResolver() {
|
||||
return authorizationRequestResolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.util.UrlUtils;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
@@ -41,6 +40,7 @@ import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.htmlunit.util.UrlUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
|
||||
+33
-1
@@ -101,6 +101,7 @@ import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
@@ -113,6 +114,7 @@ import static org.springframework.security.config.annotation.SecurityContextChan
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -343,6 +345,19 @@ public class Saml2LoginConfigurerTests {
|
||||
any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticationRequestWhenCustomAuthenticationRequestPathRepositoryThenUses() throws Exception {
|
||||
this.spring.register(CustomAuthenticationRequestUriQuery.class).autowire();
|
||||
MockHttpServletRequestBuilder request = get("/custom/auth/sso");
|
||||
this.mvc.perform(request)
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/custom/auth/sso?entityId=registration-id"));
|
||||
request.queryParam("entityId", registration.getRegistrationId());
|
||||
MvcResult result = this.mvc.perform(request).andExpect(status().isFound()).andReturn();
|
||||
String redirectedUrl = result.getResponse().getRedirectedUrl();
|
||||
assertThat(redirectedUrl).startsWith(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenAutowires()
|
||||
throws Exception {
|
||||
@@ -390,7 +405,7 @@ public class Saml2LoginConfigurerTests {
|
||||
.andExpect(redirectedUrl("http://localhost/login"));
|
||||
this.mvc.perform(get("/").accept(MediaType.TEXT_HTML))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("http://localhost/saml2/authenticate/registration-id"));
|
||||
.andExpect(header().string("Location", startsWith("http://localhost/saml2/authenticate")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -669,6 +684,23 @@ public class Saml2LoginConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomAuthenticationRequestUriQuery {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
|
||||
.saml2Login((saml2) -> saml2.authenticationRequestUriQuery("/custom/auth/sso?entityId={registrationId}"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -65,7 +65,7 @@ public class XsdDocumentedTests {
|
||||
|
||||
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
|
||||
|
||||
String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.3.xsd";
|
||||
String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.4.xsd";
|
||||
|
||||
XmlSupport xml = new XmlSupport();
|
||||
|
||||
@@ -151,8 +151,8 @@ public class XsdDocumentedTests {
|
||||
.list((dir, name) -> name.endsWith(".xsd"));
|
||||
// @formatter:on
|
||||
assertThat(schemas.length)
|
||||
.withFailMessage("the count is equal to 25, if not then schemaDocument needs updating")
|
||||
.isEqualTo(25);
|
||||
.withFailMessage("the count is equal to 26, if not then schemaDocument needs updating")
|
||||
.isEqualTo(26);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -18,13 +18,13 @@ package org.springframework.security.config.http;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
@@ -71,7 +71,7 @@ public class AccessDeniedConfigTests {
|
||||
@WithMockUser
|
||||
public void configureWhenAccessDeniedHandlerRefThenAutowire() throws Exception {
|
||||
this.spring.configLocations(this.xml("AccessDeniedHandler")).autowire();
|
||||
this.mvc.perform(get("/")).andExpect(status().is(HttpStatus.GONE_410));
|
||||
this.mvc.perform(get("/")).andExpect(status().is(HttpStatus.GONE.value()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,7 +90,7 @@ public class AccessDeniedConfigTests {
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
AccessDeniedException accessDeniedException) {
|
||||
response.setStatus(HttpStatus.GONE_410);
|
||||
response.setStatus(HttpStatus.GONE.value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ import java.util.List;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
@@ -566,7 +566,7 @@ public class CsrfConfigTests {
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
AccessDeniedException accessDeniedException) {
|
||||
response.setStatus(HttpStatus.IM_A_TEAPOT_418);
|
||||
response.setStatus(HttpStatus.I_AM_A_TEAPOT.value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
@@ -64,6 +64,7 @@ import org.springframework.security.oauth2.client.registration.InMemoryReactiveC
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
@@ -457,6 +458,7 @@ public class OAuth2LoginTests {
|
||||
OidcUser user = TestOidcUsers.create();
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = config.userService;
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(user));
|
||||
ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = config.authorizationRequestResolver;
|
||||
// @formatter:off
|
||||
webTestClient.get()
|
||||
.uri("/login/oauth2/code/google")
|
||||
@@ -466,6 +468,7 @@ public class OAuth2LoginTests {
|
||||
verify(config.jwtDecoderFactory).createDecoder(any());
|
||||
verify(tokenResponseClient).getTokenResponse(any());
|
||||
verify(securityContextRepository).save(any(), any());
|
||||
verify(authorizationRequestResolver).resolve(any());
|
||||
}
|
||||
|
||||
// gh-5562
|
||||
@@ -837,6 +840,10 @@ public class OAuth2LoginTests {
|
||||
|
||||
ServerSecurityContextRepository securityContextRepository = mock(ServerSecurityContextRepository.class);
|
||||
|
||||
ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = spy(
|
||||
new DefaultServerOAuth2AuthorizationRequestResolver(new InMemoryReactiveClientRegistrationRepository(
|
||||
TestClientRegistrations.clientRegistration().build())));
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
|
||||
// @formatter:off
|
||||
@@ -864,6 +871,11 @@ public class OAuth2LoginTests {
|
||||
return this.jwtDecoderFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver() {
|
||||
return this.authorizationRequestResolver;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
|
||||
return this.tokenResponseClient;
|
||||
|
||||
+1
-1
@@ -26,7 +26,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.util.UrlUtils;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
@@ -40,6 +39,7 @@ import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.htmlunit.util.UrlUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
+7
-6
@@ -24,10 +24,11 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.FormEncodingType;
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.WebRequest;
|
||||
import com.gargoylesoftware.htmlunit.util.NameValuePair;
|
||||
import org.htmlunit.FormEncodingType;
|
||||
import org.htmlunit.WebClient;
|
||||
import org.htmlunit.WebRequest;
|
||||
import org.htmlunit.util.Cookie;
|
||||
import org.htmlunit.util.NameValuePair;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -117,8 +118,8 @@ final class HtmlUnitWebTestClient {
|
||||
request.cookie(cookieName, cookieValue);
|
||||
}
|
||||
}
|
||||
Set<com.gargoylesoftware.htmlunit.util.Cookie> managedCookies = this.webClient.getCookies(webRequest.getUrl());
|
||||
for (com.gargoylesoftware.htmlunit.util.Cookie cookie : managedCookies) {
|
||||
Set<Cookie> managedCookies = this.webClient.getCookies(webRequest.getUrl());
|
||||
for (Cookie cookie : managedCookies) {
|
||||
request.cookie(cookie.getName(), cookie.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -20,10 +20,10 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.WebRequest;
|
||||
import com.gargoylesoftware.htmlunit.WebResponse;
|
||||
import com.gargoylesoftware.htmlunit.WebResponseData;
|
||||
import com.gargoylesoftware.htmlunit.util.NameValuePair;
|
||||
import org.htmlunit.WebRequest;
|
||||
import org.htmlunit.WebResponse;
|
||||
import org.htmlunit.WebResponseData;
|
||||
import org.htmlunit.util.NameValuePair;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
package org.springframework.security.htmlunit.server;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.WebConnection;
|
||||
import org.htmlunit.WebClient;
|
||||
import org.htmlunit.WebConnection;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
+4
-4
@@ -18,10 +18,10 @@ package org.springframework.security.htmlunit.server;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.WebConnection;
|
||||
import com.gargoylesoftware.htmlunit.WebRequest;
|
||||
import com.gargoylesoftware.htmlunit.WebResponse;
|
||||
import org.htmlunit.WebClient;
|
||||
import org.htmlunit.WebConnection;
|
||||
import org.htmlunit.WebRequest;
|
||||
import org.htmlunit.WebResponse;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.reactive.server.FluxExchangeResult;
|
||||
|
||||
+112
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import jakarta.servlet.DispatcherType
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException
|
||||
@@ -24,10 +25,13 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
|
||||
import org.springframework.security.authorization.AuthorizationDecision
|
||||
import org.springframework.security.authorization.AuthorizationManager
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.core.Authentication
|
||||
@@ -55,7 +59,6 @@ import org.springframework.web.servlet.config.annotation.PathMatchConfigurer
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
import org.springframework.web.util.WebUtils
|
||||
import java.util.function.Supplier
|
||||
import jakarta.servlet.DispatcherType
|
||||
|
||||
/**
|
||||
* Tests for [AuthorizeHttpRequestsDsl]
|
||||
@@ -835,7 +838,6 @@ class AuthorizeHttpRequestsDslTests {
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
open class HasIpAddressConfig {
|
||||
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
@@ -853,4 +855,110 @@ class AuthorizeHttpRequestsDslTests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hasRole when prefixed by configured role prefix should fail to configure`() {
|
||||
assertThatThrownBy { this.spring.register(RoleValidationConfig::class.java).autowire() }
|
||||
.isInstanceOf(UnsatisfiedDependencyException::class.java)
|
||||
.hasRootCauseInstanceOf(IllegalArgumentException::class.java)
|
||||
.hasMessageContaining(
|
||||
"ROLE_JUNIPER should not start with ROLE_ since ROLE_ is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead."
|
||||
)
|
||||
assertThatThrownBy { this.spring.register(RoleValidationConfig::class.java, GrantedAuthorityDefaultsConfig::class.java).autowire() }
|
||||
.isInstanceOf(UnsatisfiedDependencyException::class.java)
|
||||
.hasRootCauseInstanceOf(IllegalArgumentException::class.java)
|
||||
.hasMessageContaining(
|
||||
"CUSTOM_JUNIPER should not start with CUSTOM_ since CUSTOM_ is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead."
|
||||
)
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
open class RoleValidationConfig {
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize("/role", hasAnyRole("ROLE_JUNIPER"))
|
||||
authorize("/custom", hasRole("CUSTOM_JUNIPER"))
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class GrantedAuthorityDefaultsConfig {
|
||||
@Bean
|
||||
open fun grantedAuthorityDefaults(): GrantedAuthorityDefaults {
|
||||
return GrantedAuthorityDefaults("CUSTOM_")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hasRole when role hierarchy configured then honor hierarchy`() {
|
||||
this.spring.register(RoleHierarchyConfig::class.java).autowire()
|
||||
this.mockMvc.get("/protected") {
|
||||
with(httpBasic("admin", "password"))
|
||||
}.andExpect {
|
||||
status {
|
||||
isOk()
|
||||
}
|
||||
}
|
||||
this.mockMvc.get("/protected") {
|
||||
with(httpBasic("user", "password"))
|
||||
}.andExpect {
|
||||
status {
|
||||
isOk()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
open class RoleHierarchyConfig {
|
||||
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize("/protected", hasRole("USER"))
|
||||
}
|
||||
httpBasic { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun roleHierarchy(): RoleHierarchy {
|
||||
return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_USER")
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
val admin = User.withDefaultPasswordEncoder()
|
||||
.username("admin")
|
||||
.password("password")
|
||||
.roles("ADMIN")
|
||||
.build()
|
||||
return InMemoryUserDetailsManager(user, admin)
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class PathController {
|
||||
|
||||
@RequestMapping("/protected")
|
||||
fun path() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+42
-1
@@ -43,11 +43,13 @@ import org.springframework.security.saml2.provider.service.registration.TestRely
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.MvcResult
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.util.Base64
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [Saml2Dsl]
|
||||
@@ -136,6 +138,23 @@ class Saml2DslTests {
|
||||
verify(exactly = 1) { Saml2LoginCustomAuthenticationManagerConfig.AUTHENTICATION_MANAGER.authenticate(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun authenticationRequestWhenCustomAuthenticationRequestPathRepositoryThenUses() {
|
||||
this.spring.register(CustomAuthenticationRequestUriQuery::class.java).autowire()
|
||||
val registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
|
||||
val request = MockMvcRequestBuilders.get("/custom/auth/sso")
|
||||
this.mockMvc.perform(request)
|
||||
.andExpect(MockMvcResultMatchers.status().isFound())
|
||||
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/custom/auth/sso?entityId=simplesamlphp"))
|
||||
request.queryParam("entityId", registration.registrationId)
|
||||
val result: MvcResult =
|
||||
this.mockMvc.perform(request).andExpect(MockMvcResultMatchers.status().isFound()).andReturn()
|
||||
val redirectedUrl = result.response.redirectedUrl
|
||||
Assertions.assertThat(redirectedUrl)
|
||||
.startsWith(registration.assertingPartyDetails.singleSignOnServiceLocation)
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class Saml2LoginCustomAuthenticationManagerConfig {
|
||||
@@ -162,4 +181,26 @@ class Saml2DslTests {
|
||||
return repository
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class CustomAuthenticationRequestUriQuery {
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
saml2Login {
|
||||
authenticationRequestUriQuery = "/custom/auth/sso?entityId={registrationId}"
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun relyingPartyRegistrationRepository(): RelyingPartyRegistrationRepository? {
|
||||
return InMemoryRelyingPartyRegistrationRepository(TestRelyingPartyRegistrations.relyingPartyRegistration().build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,7 +35,9 @@ import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository
|
||||
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
|
||||
@@ -251,4 +253,31 @@ class ServerHttpSecurityDslTests {
|
||||
return Mono.empty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `security context repository when configured in DSL then used`() {
|
||||
this.spring.register(SecurityContextRepositoryConfig::class.java).autowire()
|
||||
mockkObject(SecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY)
|
||||
every {
|
||||
SecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY.load(any())
|
||||
} returns Mono.empty()
|
||||
this.client.get().uri("/").exchange()
|
||||
verify(exactly = 1) { SecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY.load(any()) }
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
open class SecurityContextRepositoryConfig {
|
||||
companion object {
|
||||
val SECURITY_CONTEXT_REPOSITORY: ServerSecurityContextRepository = NoOpServerSecurityContextRepository.getInstance()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
securityContextRepository = SECURITY_CONTEXT_REPOSITORY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2002-2024 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<b:beans xmlns="http://www.springframework.org/schema/security"
|
||||
xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/security org/springframework/security/config/spring-security-6.3.xsd">
|
||||
http://www.springframework.org/schema/security org/springframework/security/config/spring-security-6.4.xsd">
|
||||
|
||||
<tx:annotation-driven />
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
management platform(project(":spring-security-dependencies"))
|
||||
@@ -31,6 +33,9 @@ dependencies {
|
||||
testImplementation "org.springframework:spring-test"
|
||||
testImplementation 'org.skyscreamer:jsonassert'
|
||||
testImplementation 'org.springframework:spring-test'
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-reflect'
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
testImplementation 'io.mockk:mockk'
|
||||
|
||||
testRuntimeOnly 'org.hsqldb:hsqldb'
|
||||
}
|
||||
@@ -57,3 +62,12 @@ Callable<String> springVersion() {
|
||||
return (Callable<String>) { project.configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts
|
||||
.find { it.name == 'spring-core' }.moduleVersion.id.version }
|
||||
}
|
||||
|
||||
tasks.withType(KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
languageVersion = "1.7"
|
||||
apiVersion = "1.7"
|
||||
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings"]
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
+31
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -52,6 +52,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @author Evgeniy Cheban
|
||||
* @author Blagoja Stamatovski
|
||||
* @since 3.0
|
||||
*/
|
||||
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
|
||||
@@ -109,12 +110,13 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the {@code filterTarget} object (which must be either a collection, array,
|
||||
* map or stream), by evaluating the supplied expression.
|
||||
* Filters the {@code filterTarget} object (which must be either a {@link Collection},
|
||||
* {@code Array}, {@link Map} or {@link Stream}), by evaluating the supplied
|
||||
* expression.
|
||||
* <p>
|
||||
* If a {@code Collection} or {@code Map} is used, the original instance will be
|
||||
* modified to contain the elements for which the permission expression evaluates to
|
||||
* {@code true}. For an array, a new array instance will be returned.
|
||||
* Returns new instances of the same type as the supplied {@code filterTarget} object
|
||||
* @return The filtered {@link Collection}, {@code Array}, {@link Map} or
|
||||
* {@link Stream}
|
||||
*/
|
||||
@Override
|
||||
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
|
||||
@@ -151,9 +153,17 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
|
||||
}
|
||||
}
|
||||
this.logger.debug(LogMessage.format("Retaining elements: %s", retain));
|
||||
filterTarget.clear();
|
||||
filterTarget.addAll(retain);
|
||||
return filterTarget;
|
||||
try {
|
||||
filterTarget.clear();
|
||||
filterTarget.addAll(retain);
|
||||
return filterTarget;
|
||||
}
|
||||
catch (UnsupportedOperationException readonly) {
|
||||
this.logger.trace(LogMessage.format(
|
||||
"Collection threw exception: %s. Will return a new instance instead of mutating its state.",
|
||||
readonly.getMessage()));
|
||||
return retain;
|
||||
}
|
||||
}
|
||||
|
||||
private Object filterArray(Object[] filterTarget, Expression filterExpression, EvaluationContext ctx,
|
||||
@@ -178,7 +188,7 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private <K, V> Object filterMap(final Map<K, V> filterTarget, Expression filterExpression, EvaluationContext ctx,
|
||||
private <K, V> Object filterMap(Map<K, V> filterTarget, Expression filterExpression, EvaluationContext ctx,
|
||||
MethodSecurityExpressionOperations rootObject) {
|
||||
Map<K, V> retain = new LinkedHashMap<>(filterTarget.size());
|
||||
this.logger.debug(LogMessage.format("Filtering map with %s elements", filterTarget.size()));
|
||||
@@ -189,9 +199,17 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
|
||||
}
|
||||
}
|
||||
this.logger.debug(LogMessage.format("Retaining elements: %s", retain));
|
||||
filterTarget.clear();
|
||||
filterTarget.putAll(retain);
|
||||
return filterTarget;
|
||||
try {
|
||||
filterTarget.clear();
|
||||
filterTarget.putAll(retain);
|
||||
return filterTarget;
|
||||
}
|
||||
catch (UnsupportedOperationException readonly) {
|
||||
this.logger.trace(LogMessage.format(
|
||||
"Map threw exception: %s. Will return a new instance instead of mutating its state.",
|
||||
readonly.getMessage()));
|
||||
return retain;
|
||||
}
|
||||
}
|
||||
|
||||
private Object filterStream(final Stream<?> filterTarget, Expression filterExpression, EvaluationContext ctx,
|
||||
|
||||
@@ -31,11 +31,11 @@ DigestAuthenticationFilter.nonceExpired=Nonce expirou/tempo esgotado
|
||||
DigestAuthenticationFilter.nonceNotNumeric=Nonce token deveria ter um primero token num\u00E9rico, mas tem {0}
|
||||
DigestAuthenticationFilter.nonceNotTwoTokens=Nonce token deveria ter dois tokens mas tem {0}
|
||||
DigestAuthenticationFilter.usernameNotFound=Usu\u00E1rio {0} n\u00E3o encontrado
|
||||
#ExceptionTranslationFilter.insufficientAuthentication=Full authentication is required to access this resource
|
||||
ExceptionTranslationFilter.insufficientAuthentication=Autentica\u00e7\u00e3o completa \u00e9 necess\u00e1ria para acessar este recurso
|
||||
JdbcDaoImpl.noAuthority=Usu\u00E1rio {0} n\u00E3o tem permiss\u00E3o
|
||||
JdbcDaoImpl.notFound=Usu\u00E1rio {0} n\u00E3o encontrado
|
||||
LdapAuthenticationProvider.badCredentials=Usu\u00E1rio inexistente ou senha inv\u00E1lida
|
||||
#LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed
|
||||
LdapAuthenticationProvider.badLdapConnection=Conex\u00E3o com servidor LDAP falhou
|
||||
LdapAuthenticationProvider.credentialsExpired=Credenciais expiradas
|
||||
LdapAuthenticationProvider.disabled=Usu\u00E1rio desabilitado
|
||||
LdapAuthenticationProvider.expired=Conta expirada
|
||||
@@ -43,7 +43,7 @@ LdapAuthenticationProvider.locked=Conta bloqueada
|
||||
LdapAuthenticationProvider.emptyUsername=Nome vazio n\u00E3o permitido
|
||||
LdapAuthenticationProvider.onlySupports=Somente UsernamePasswordAuthenticationToken \u00E9 suportado
|
||||
PasswordComparisonAuthenticator.badCredentials=Usu\u00E1rio inexistente ou senha inv\u00E1lida
|
||||
#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
|
||||
PersistentTokenBasedRememberMeServices.cookieStolen=Diverg\u00eancia inv\u00e1lida em remember-me token (Series/token). Implica um ataque pr\u00e9vio de roubo de cookies.
|
||||
ProviderManager.providerNotFound=Nenhum AuthenticationProvider encontrado para {0}
|
||||
RememberMeAuthenticationProvider.incorrectKey=O RememberMeAuthenticationToken apresentado n\u00E3o cont\u00E9m a chave esperada
|
||||
RunAsImplAuthenticationProvider.incorrectKey=O RunAsUserToken apresentado n\u00E3o cont\u00E9m a chave esperada
|
||||
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.access.expression.method
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.aopalliance.intercept.MethodInvocation
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.expression.EvaluationContext
|
||||
import org.springframework.expression.Expression
|
||||
import org.springframework.security.core.Authentication
|
||||
import java.util.stream.Stream
|
||||
import kotlin.reflect.jvm.internal.impl.load.kotlin.JvmType
|
||||
import kotlin.reflect.jvm.javaMethod
|
||||
|
||||
/**
|
||||
* @author Blagoja Stamatovski
|
||||
*/
|
||||
class DefaultMethodSecurityExpressionHandlerKotlinTests {
|
||||
private object Foo {
|
||||
fun bar() {
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var authentication: Authentication
|
||||
private lateinit var methodInvocation: MethodInvocation
|
||||
|
||||
private val handler: MethodSecurityExpressionHandler = DefaultMethodSecurityExpressionHandler()
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
authentication = mockk()
|
||||
methodInvocation = mockk()
|
||||
|
||||
every { methodInvocation.`this` } returns { Foo }
|
||||
every { methodInvocation.method } answers { Foo::bar.javaMethod!! }
|
||||
every { methodInvocation.arguments } answers { arrayOf<JvmType.Object>() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters non-empty maps`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject.key eq 'key2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val nonEmptyMap: Map<String, String> = mapOf(
|
||||
"key1" to "value1",
|
||||
"key2" to "value2",
|
||||
"key3" to "value3",
|
||||
)
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ nonEmptyMap,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Map::class.java)
|
||||
val result = (filtered as Map<String, String>)
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).containsKey("key2")
|
||||
assertThat(result).containsValue("value2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters empty maps`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject.key eq 'key2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val emptyMap: Map<String, String> = emptyMap()
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ emptyMap,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Map::class.java)
|
||||
val result = (filtered as Map<String, String>)
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters non-empty collections`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val nonEmptyCollection: Collection<String> = listOf(
|
||||
"string1",
|
||||
"string2",
|
||||
"string1",
|
||||
)
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ nonEmptyCollection,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Collection::class.java)
|
||||
val result = (filtered as Collection<String>)
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).contains("string2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters empty collections`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val emptyCollection: Collection<String> = emptyList()
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ emptyCollection,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Collection::class.java)
|
||||
val result = (filtered as Collection<String>)
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters non-empty arrays`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val nonEmptyArray: Array<String> = arrayOf(
|
||||
"string1",
|
||||
"string2",
|
||||
"string1",
|
||||
)
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ nonEmptyArray,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Array<String>::class.java)
|
||||
val result = (filtered as Array<String>)
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).contains("string2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters empty arrays`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val emptyArray: Array<String> = emptyArray()
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ emptyArray,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Array<String>::class.java)
|
||||
val result = (filtered as Array<String>)
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters non-empty streams`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val nonEmptyStream: Stream<String> = listOf(
|
||||
"string1",
|
||||
"string2",
|
||||
"string1",
|
||||
).stream()
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ nonEmptyStream,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Stream::class.java)
|
||||
val result = (filtered as Stream<String>).toList()
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result).contains("string2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filters empty streams`() {
|
||||
val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'")
|
||||
val context: EvaluationContext = handler.createEvaluationContext(
|
||||
/* authentication = */ authentication,
|
||||
/* invocation = */ methodInvocation,
|
||||
)
|
||||
val emptyStream: Stream<String> = emptyList<String>().stream()
|
||||
|
||||
val filtered: Any = handler.filter(
|
||||
/* filterTarget = */ emptyStream,
|
||||
/* filterExpression = */ expression,
|
||||
/* ctx = */ context,
|
||||
)
|
||||
|
||||
assertThat(filtered).isInstanceOf(Stream::class.java)
|
||||
val result = (filtered as Stream<String>).toList()
|
||||
assertThat(result).hasSize(0)
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ dependencies {
|
||||
api libs.jakarta.websocket.jakarta.websocket.client.api
|
||||
api libs.ldapsdk
|
||||
api libs.net.sourceforge.htmlunit
|
||||
api libs.org.htmlunit.htmlunit
|
||||
api libs.org.apache.directory.server.apacheds.entry
|
||||
api libs.org.apache.directory.server.apacheds.core
|
||||
api libs.org.apache.directory.server.apacheds.protocol.ldap
|
||||
|
||||
@@ -24,6 +24,7 @@ asciidoc:
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
- '@springio/asciidoctor-extensions/javadoc-extension'
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ nav:
|
||||
ext:
|
||||
collector:
|
||||
run:
|
||||
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntoraYml
|
||||
command: gradlew -q -PbuildSrc.skipTests=true :spring-security-docs:generateAntoraResources
|
||||
local: true
|
||||
scan:
|
||||
dir: ./build/generated-antora-resources
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* xref:migration/index.adoc[Migrating to 6.2]
|
||||
** xref:migration/authorization.adoc[Authorization Changes]
|
||||
* xref:getting-spring-security.adoc[Getting Spring Security]
|
||||
* xref:attachment$api/java/index.html[Javadoc]
|
||||
* xref:features/index.adoc[Features]
|
||||
** xref:features/authentication/index.adoc[Authentication]
|
||||
*** xref:features/authentication/password-storage.adoc[Password Storage]
|
||||
|
||||
@@ -598,7 +598,7 @@ With the above configuration, when a password manager navigates to `/.well-known
|
||||
There are some scenarios where you need to check whether a password has been compromised, for example, if you are creating an application that deals with sensitive data, it is often needed that you perform some check on user's passwords in order to assert its reliability.
|
||||
One of these checks can be if the password has been compromised, usually because it has been found in a https://wikipedia.org/wiki/Data_breach[data breach].
|
||||
|
||||
To facilitate that, Spring Security provides integration with the https://haveibeenpwned.com/API/v3#PwnedPasswords[Have I Been Pwned API] via the {security-api-url}org/springframework/security/core/password/HaveIBeenPwnedRestApiPasswordChecker.html[`HaveIBeenPwnedRestApiPasswordChecker` implementation] of the {security-api-url}org/springframework/security/core/password/CompromisedPasswordChecker.html[`CompromisedPasswordChecker` interface].
|
||||
To facilitate that, Spring Security provides integration with the https://haveibeenpwned.com/API/v3#PwnedPasswords[Have I Been Pwned API] via the javadoc:org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker[] implementation of the javadoc:org.springframework.security.authentication.password.CompromisedPasswordChecker[] interface.
|
||||
|
||||
You can either use the `CompromisedPasswordChecker` API by yourself or, if you are using xref:servlet/authentication/passwords/dao-authentication-provider.adoc[the `DaoAuthenticationProvider]` via xref:servlet/authentication/passwords/index.adoc[Spring Security authentication mechanisms], you can provide a `CompromisedPasswordChecker` bean, and it will be automatically picked up by Spring Security configuration.
|
||||
|
||||
|
||||
@@ -208,11 +208,6 @@ The user receives an email at https://email.example.org that includes a link to
|
||||
If the user clicks on the link, they would rightfully expect to be authenticated to the social media site.
|
||||
However, if the `SameSite` attribute is `Strict`, the cookie would not be sent and so the user would not be authenticated.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
We could improve the protection and usability of `SameSite` protection against CSRF attacks by implementing https://github.com/spring-projects/spring-security/issues/7537[gh-7537].
|
||||
====
|
||||
|
||||
Another obvious consideration is that, in order for the `SameSite` attribute to protect users, the browser must support the `SameSite` attribute.
|
||||
Most modern browsers do https://developer.mozilla.org/en-US/docs/Web/HTTP/headers/Set-Cookie#Browser_compatibility[support the SameSite attribute].
|
||||
However, older browsers that are still in use may not.
|
||||
|
||||
@@ -8,9 +8,9 @@ The code is distributed as part of the core module but has no dependencies on an
|
||||
|
||||
[[spring-security-crypto-encryption]]
|
||||
== Encryptors
|
||||
The {security-api-url}org/springframework/security/crypto/encrypt/Encryptors.html[`Encryptors`] class provides factory methods for constructing symmetric encryptors.
|
||||
This class lets you create {security-api-url}org/springframework/security/crypto/encrypt/BytesEncryptor.html[`BytesEncryptor`] instances to encrypt data in raw `byte[]` form.
|
||||
You can also construct {security-api-url}org/springframework/security/crypto/encrypt/TextEncryptor.html[TextEncryptor] instances to encrypt text strings.
|
||||
The javadoc:org.springframework.security.crypto.encrypt.Encryptors[] class provides factory methods for constructing symmetric encryptors.
|
||||
This class lets you create javadoc:org.springframework.security.crypto.encrypt.BytesEncryptor[] instances to encrypt data in raw `byte[]` form.
|
||||
You can also construct javadoc:org.springframework.security.crypto.encrypt.TextEncryptor[] instances to encrypt text strings.
|
||||
Encryptors are thread-safe.
|
||||
|
||||
[NOTE]
|
||||
@@ -101,9 +101,9 @@ Encrypted results are returned as hex-encoded strings for easy storage on the fi
|
||||
|
||||
[[spring-security-crypto-keygenerators]]
|
||||
== Key Generators
|
||||
The {security-api-url}org/springframework/security/crypto/keygen/KeyGenerators.html[`KeyGenerators`] class provides a number of convenience factory methods for constructing different types of key generators.
|
||||
By using this class, you can create a {security-api-url}org/springframework/security/crypto/keygen/BytesKeyGenerator.html[`BytesKeyGenerator`] to generate `byte[]` keys.
|
||||
You can also construct a {security-api-url}org/springframework/security/crypto/keygen/StringKeyGenerator.html`[StringKeyGenerator]` to generate string keys.
|
||||
The javadoc:org.springframework.security.crypto.keygen.KeyGenerators[] class provides a number of convenience factory methods for constructing different types of key generators.
|
||||
By using this class, you can create a javadoc:org.springframework.security.crypto.keygen.BytesKeyGenerator[] to generate `byte[]` keys.
|
||||
You can also construct a javadoc:org.springframework.security.crypto.keygen.StringKeyGenerator[] to generate string keys.
|
||||
`KeyGenerators` is a thread-safe class.
|
||||
|
||||
=== BytesKeyGenerator
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Similar to xref:servlet/authentication/session-management.adoc#ns-concurrent-sessions[Servlet's Concurrent Sessions Control], Spring Security also provides support to limit the number of concurrent sessions a user can have in a Reactive application.
|
||||
|
||||
When you set up Concurrent Sessions Control in Spring Security, it monitors authentications carried out through Form Login, xref:reactive/oauth2/login/index.adoc[OAuth 2.0 Login], and HTTP Basic authentication by hooking into the way those authentication mechanisms handle authentication success.
|
||||
More specifically, the session management DSL will add the {security-api-url}org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.html[ConcurrentSessionControlServerAuthenticationSuccessHandler] and the {security-api-url}org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.html[RegisterSessionServerAuthenticationSuccessHandler] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter.
|
||||
More specifically, the session management DSL will add the javadoc:org.springframework.security.web.server.authentication.ConcurrentSessionControlServerAuthenticationSuccessHandler[] and the javadoc:org.springframework.security.web.server.authentication.RegisterSessionServerAuthenticationSuccessHandler[] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter.
|
||||
|
||||
The following sections contains examples of how to configure Concurrent Sessions Control.
|
||||
|
||||
@@ -197,9 +197,9 @@ If you also need to invalidate the session against the Identity Provider you mus
|
||||
[[concurrent-sessions-control-custom-strategy]]
|
||||
== Handling Maximum Number of Sessions Exceeded
|
||||
|
||||
By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler].
|
||||
Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler].
|
||||
If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[ServerMaximumSessionsExceededHandler].
|
||||
By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the javadoc:org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler[].
|
||||
Spring Security also provides another implementation that prevents the user from creating new sessions by using the javadoc:org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler[].
|
||||
If you want to use your own strategy, you can provide a different implementation of javadoc:org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler[].
|
||||
|
||||
.Configuring maximumSessionsExceededHandler
|
||||
[tabs]
|
||||
@@ -254,9 +254,9 @@ open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
[[reactive-concurrent-sessions-control-specify-session-registry]]
|
||||
== Specifying a `ReactiveSessionRegistry`
|
||||
|
||||
In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved.
|
||||
In order to keep track of the user's sessions, Spring Security uses a javadoc:org.springframework.security.core.session.ReactiveSessionRegistry[], and, every time a user logs in, their session information is saved.
|
||||
|
||||
Spring Security ships with {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementation of `ReactiveSessionRegistry`.
|
||||
Spring Security ships with javadoc:org.springframework.security.core.session.InMemoryReactiveSessionRegistry[] implementation of `ReactiveSessionRegistry`.
|
||||
|
||||
To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ class HelloWebfluxSecurityConfig {
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Make sure to import the `org.springframework.security.config.annotation.web.invoke` function to enable the Kotlin DSL in your class, as the IDE will not always auto-import the method, causing compilation issues.
|
||||
Make sure to import the `org.springframework.security.config.web.server.invoke` function to enable the Kotlin DSL in your class, as the IDE will not always auto-import the method, causing compilation issues.
|
||||
|
||||
This configuration explicitly sets up all the same things as our minimal configuration.
|
||||
From here, you can more easily make changes to the defaults.
|
||||
|
||||
@@ -41,7 +41,7 @@ return http {
|
||||
== Bearer Token Propagation
|
||||
|
||||
Now that you have a bearer token, you can pass that to downstream services.
|
||||
This is possible with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServerBearerExchangeFilterFunction.html[ServerBearerExchangeFilterFunction]`:
|
||||
This is possible with javadoc:org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServerBearerExchangeFilterFunction[]:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -70,7 +70,7 @@ fun rest(): WebClient {
|
||||
----
|
||||
======
|
||||
|
||||
When the `WebClient` shown in the preceding example performs requests, Spring Security looks up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential.
|
||||
When the `WebClient` shown in the preceding example performs requests, Spring Security looks up the current `Authentication` and extract any javadoc:org.springframework.security.oauth2.core.AbstractOAuth2Token[] credential.
|
||||
Then, it propagates that token in the `Authorization` header -- for example:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -234,7 +234,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Calling `{security-api-url}org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.html#fromIssuerLocation-java.lang.String-[ReactiveJwtDecoders#fromIssuerLocation]` invokes the Provider Configuration or Authorization Server Metadata endpoint to derive the JWK Set URI.
|
||||
Calling javadoc:org.springframework.security.oauth2.jwt.ReactiveJwtDecoders#fromIssuerLocation-java.lang.String-[ReactiveJwtDecoders#fromIssuerLocation] invokes the Provider Configuration or Authorization Server Metadata endpoint to derive the JWK Set URI.
|
||||
If the application does not expose a `ReactiveJwtDecoder` bean, Spring Boot exposes the above default one.
|
||||
====
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ Given an Opaque Token, Resource Server:
|
||||
. Inspects the response for an `{ 'active' : true }` attribute.
|
||||
. Maps each scope to an authority with a prefix of `SCOPE_`.
|
||||
|
||||
By default, the resulting `Authentication#getPrincipal` is a Spring Security `{security-api-url}org/springframework/security/oauth2/core/OAuth2AuthenticatedPrincipal.html[OAuth2AuthenticatedPrincipal]` object, and `Authentication#getName` maps to the token's `sub` property, if one is present.
|
||||
By default, the resulting `Authentication#getPrincipal` is a Spring Security javadoc:org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal[] object, and `Authentication#getName` maps to the token's `sub` property, if one is present.
|
||||
|
||||
From here, you may want to jump to:
|
||||
|
||||
|
||||
@@ -956,7 +956,7 @@ client
|
||||
----
|
||||
======
|
||||
|
||||
You can also specify a complete `Jwt`, for which `{security-api-url}org/springframework/security/oauth2/jwt/Jwt.Builder.html[Jwt.Builder]` is quite handy:
|
||||
You can also specify a complete `Jwt`, for which javadoc:org.springframework.security.oauth2.jwt.Jwt$Builder[] is quite handy:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -6,4 +6,4 @@ This appendix provides a reference to the elements available in the security nam
|
||||
If you haven't used the namespace before, please read the xref:servlet/configuration/xml-namespace.adoc#ns-config[introductory chapter] on namespace configuration, as this is intended as a supplement to the information there.
|
||||
Using a good quality XML editor while editing a configuration based on the schema is recommended as this will provide contextual information on which elements and attributes are available as well as comments explaining their purpose.
|
||||
The namespace is written in https://relaxng.org/[RELAX NG] Compact format and later converted into an XSD schema.
|
||||
If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.3.rnc[schema file] directly.
|
||||
If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.4.rnc[schema file] directly.
|
||||
|
||||
@@ -118,7 +118,7 @@ image::{figures}/filterchainproxy.png[]
|
||||
[[servlet-securityfilterchain]]
|
||||
== SecurityFilterChain
|
||||
|
||||
{security-api-url}org/springframework/security/web/SecurityFilterChain.html[`SecurityFilterChain`] is used by <<servlet-filterchainproxy>> to determine which Spring Security `Filter` instances should be invoked for the current request.
|
||||
javadoc:org.springframework.security.web.SecurityFilterChain[] is used by <<servlet-filterchainproxy>> to determine which Spring Security `Filter` instances should be invoked for the current request.
|
||||
|
||||
The following image shows the role of `SecurityFilterChain`.
|
||||
|
||||
@@ -250,11 +250,11 @@ If you want to see the list of filters invoked for a particular request, you can
|
||||
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-custom-filter,filter you have added>> is in the list of the security filters.
|
||||
|
||||
The list of filters is printed at INFO level on the application startup, so you can see something like the following on the console output for example:
|
||||
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:
|
||||
|
||||
[source,text,role="terminal"]
|
||||
----
|
||||
2023-06-14T08:55:22.321-03:00 INFO 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
|
||||
2023-06-14T08:55:22.321-03:00 DEBUG 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
|
||||
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
|
||||
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
|
||||
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
|
||||
@@ -392,7 +392,7 @@ public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilte
|
||||
== Handling Security Exceptions
|
||||
|
||||
|
||||
The {security-api-url}org/springframework/security/web/access/ExceptionTranslationFilter.html[`ExceptionTranslationFilter`] allows translation of {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`] and {security-api-url}/org/springframework/security/core/AuthenticationException.html[`AuthenticationException`] into HTTP responses.
|
||||
The javadoc:org.springframework.security.web.access.ExceptionTranslationFilter[] allows translation of javadoc:org.springframework.security.access.AccessDeniedException[] and javadoc:org.springframework.security.core.AuthenticationException[] into HTTP responses.
|
||||
|
||||
`ExceptionTranslationFilter` is inserted into the <<servlet-filterchainproxy>> as one of the <<servlet-security-filters>>.
|
||||
|
||||
@@ -447,7 +447,7 @@ In Spring Security this is done by saving the `HttpServletRequest` using a <<req
|
||||
[[requestcache]]
|
||||
=== RequestCache
|
||||
|
||||
The `HttpServletRequest` is saved in the {security-api-url}org/springframework/security/web/savedrequest/RequestCache.html[`RequestCache`].
|
||||
The `HttpServletRequest` is saved in the javadoc:org.springframework.security.web.savedrequest.RequestCache[].
|
||||
When the user successfully authenticates, the `RequestCache` is used to replay the original request.
|
||||
The <<requestcacheawarefilter,`RequestCacheAwareFilter`>> uses the `RequestCache` to get the saved `HttpServletRequest` after the user authenticates, while the `ExceptionTranslationFilter` uses the `RequestCache` to save the `HttpServletRequest` after it detects `AuthenticationException`, before redirecting the user to the login endpoint.
|
||||
|
||||
@@ -463,7 +463,7 @@ There are a number of reasons you may want to not store the user's unauthenticat
|
||||
You may want to offload that storage onto the user's browser or store it in a database.
|
||||
Or you may want to shut off this feature since you always want to redirect the user to the home page instead of the page they tried to visit before login.
|
||||
|
||||
To do that, you can use {security-api-url}org/springframework/security/web/savedrequest/NullRequestCache.html[the `NullRequestCache` implementation].
|
||||
To do that, you can use the javadoc:org.springframework.security.web.savedrequest.NullRequestCache[NullRequestCache] implementation.
|
||||
|
||||
.Prevent the Request From Being Saved
|
||||
[tabs]
|
||||
@@ -517,7 +517,7 @@ XML::
|
||||
[[requestcacheawarefilter]]
|
||||
=== RequestCacheAwareFilter
|
||||
|
||||
The {security-api-url}org/springframework/security/web/savedrequest/RequestCacheAwareFilter.html[`RequestCacheAwareFilter`] uses the <<requestcache,`RequestCache`>> to replay the original request.
|
||||
The javadoc:org.springframework.security.web.savedrequest.RequestCacheAwareFilter[] uses the <<requestcache,`RequestCache`>> to replay the original request.
|
||||
|
||||
[[servlet-logging]]
|
||||
== Logging
|
||||
|
||||
@@ -117,13 +117,13 @@ However, if you do, take a look at the JavaDoc for `SecurityContextHolder` to le
|
||||
[[servlet-authentication-securitycontext]]
|
||||
== SecurityContext
|
||||
|
||||
The {security-api-url}org/springframework/security/core/context/SecurityContext.html[`SecurityContext`] is obtained from the <<servlet-authentication-securitycontextholder>>.
|
||||
The javadoc:org.springframework.security.core.context.SecurityContext[] is obtained from the <<servlet-authentication-securitycontextholder>>.
|
||||
The `SecurityContext` contains an <<servlet-authentication-authentication>> object.
|
||||
|
||||
[[servlet-authentication-authentication]]
|
||||
== Authentication
|
||||
|
||||
The {security-api-url}org/springframework/security/core/Authentication.html[`Authentication`] interface serves two main purposes within Spring Security:
|
||||
The javadoc:org.springframework.security.core.Authentication[] interface serves two main purposes within Spring Security:
|
||||
|
||||
* An input to <<servlet-authentication-authenticationmanager,`AuthenticationManager`>> to provide the credentials a user has provided to authenticate.
|
||||
When used in this scenario, `isAuthenticated()` returns `false`.
|
||||
@@ -141,7 +141,7 @@ Two examples are roles and scopes.
|
||||
|
||||
[[servlet-authentication-granted-authority]]
|
||||
== GrantedAuthority
|
||||
{security-api-url}org/springframework/security/core/GrantedAuthority.html[`GrantedAuthority`] instances are high-level permissions that the user is granted.
|
||||
javadoc:org.springframework.security.core.GrantedAuthority[] instances are high-level permissions that the user is granted.
|
||||
Two examples are roles and scopes.
|
||||
|
||||
You can obtain `GrantedAuthority` instances from the <<servlet-authentication-authentication,`Authentication.getAuthorities()`>> method.
|
||||
@@ -160,7 +160,7 @@ Of course, Spring Security is expressly designed to handle this common requireme
|
||||
[[servlet-authentication-authenticationmanager]]
|
||||
== AuthenticationManager
|
||||
|
||||
{security-api-url}org/springframework/security/authentication/AuthenticationManager.html[`AuthenticationManager`] is the API that defines how Spring Security's Filters perform xref:features/authentication/index.adoc#authentication[authentication].
|
||||
javadoc:org.springframework.security.authentication.AuthenticationManager[] is the API that defines how Spring Security's Filters perform xref:features/authentication/index.adoc#authentication[authentication].
|
||||
The <<servlet-authentication-authentication,`Authentication`>> that is returned is then set on the <<servlet-authentication-securitycontextholder>> by the controller (that is, by xref:servlet/architecture.adoc#servlet-security-filters[Spring Security's `Filters` instances]) that invoked the `AuthenticationManager`.
|
||||
If you are not integrating with Spring Security's `Filters` instances, you can set the `SecurityContextHolder` directly and are not required to use an `AuthenticationManager`.
|
||||
|
||||
@@ -170,7 +170,7 @@ While the implementation of `AuthenticationManager` could be anything, the most
|
||||
[[servlet-authentication-providermanager]]
|
||||
== ProviderManager
|
||||
|
||||
{security-api-url}org/springframework/security/authentication/ProviderManager.html[`ProviderManager`] is the most commonly used implementation of <<servlet-authentication-authenticationmanager,`AuthenticationManager`>>.
|
||||
javadoc:org.springframework.security.authentication.ProviderManager[] is the most commonly used implementation of <<servlet-authentication-authenticationmanager,`AuthenticationManager`>>.
|
||||
`ProviderManager` delegates to a `List` of <<servlet-authentication-authenticationprovider,`AuthenticationProvider`>> instances.
|
||||
Each `AuthenticationProvider` has an opportunity to indicate that authentication should be successful, fail, or indicate it cannot make a decision and allow a downstream `AuthenticationProvider` to decide.
|
||||
If none of the configured `AuthenticationProvider` instances can authenticate, authentication fails with a `ProviderNotFoundException`, which is a special `AuthenticationException` that indicates that the `ProviderManager` was not configured to support the type of `Authentication` that was passed into it.
|
||||
@@ -200,19 +200,19 @@ If the `Authentication` contains a reference to an object in the cache (such as
|
||||
You need to take this into account if you use a cache.
|
||||
An obvious solution is to first make a copy of the object, either in the cache implementation or in the `AuthenticationProvider` that creates the returned `Authentication` object.
|
||||
Alternatively, you can disable the `eraseCredentialsAfterAuthentication` property on `ProviderManager`.
|
||||
See the Javadoc for the {security-api-url}org/springframework/security/authentication/ProviderManager.html[ProviderManager] class.
|
||||
See the Javadoc for the javadoc:org.springframework.security.authentication.ProviderManager[] class.
|
||||
|
||||
[[servlet-authentication-authenticationprovider]]
|
||||
== AuthenticationProvider
|
||||
|
||||
You can inject multiple {security-api-url}org/springframework/security/authentication/AuthenticationProvider.html[``AuthenticationProvider``s] instances into <<servlet-authentication-providermanager,`ProviderManager`>>.
|
||||
You can inject multiple javadoc:org.springframework.security.authentication.AuthenticationProvider[] instances into <<servlet-authentication-providermanager,`ProviderManager`>>.
|
||||
Each `AuthenticationProvider` performs a specific type of authentication.
|
||||
For example, xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] supports username/password-based authentication, while `JwtAuthenticationProvider` supports authenticating a JWT token.
|
||||
|
||||
[[servlet-authentication-authenticationentrypoint]]
|
||||
== Request Credentials with `AuthenticationEntryPoint`
|
||||
|
||||
{security-api-url}org/springframework/security/web/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`] is used to send an HTTP response that requests credentials from a client.
|
||||
javadoc:org.springframework.security.web.AuthenticationEntryPoint[] is used to send an HTTP response that requests credentials from a client.
|
||||
|
||||
Sometimes, a client proactively includes credentials (such as a username and password) to request a resource.
|
||||
In these cases, Spring Security does not need to provide an HTTP response that requests credentials from the client, since they are already included.
|
||||
@@ -229,7 +229,7 @@ The `AuthenticationEntryPoint` implementation might perform a xref:servlet/authe
|
||||
[[servlet-authentication-abstractprocessingfilter]]
|
||||
== AbstractAuthenticationProcessingFilter
|
||||
|
||||
{security-api-url}org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html[`AbstractAuthenticationProcessingFilter`] is used as a base `Filter` for authenticating a user's credentials.
|
||||
javadoc:org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter[] is used as a base `Filter` for authenticating a user's credentials.
|
||||
Before the credentials can be authenticated, Spring Security typically requests the credentials by using <<servlet-authentication-authenticationentrypoint,`AuthenticationEntryPoint`>>.
|
||||
|
||||
Next, the `AbstractAuthenticationProcessingFilter` can authenticate any authentication requests that are submitted to it.
|
||||
@@ -245,26 +245,26 @@ image:{icondir}/number_2.png[] Next, the <<servlet-authentication-authentication
|
||||
image:{icondir}/number_3.png[] If authentication fails, then __Failure__.
|
||||
|
||||
* The <<servlet-authentication-securitycontextholder>> is cleared out.
|
||||
* `RememberMeServices.loginFail` is invoked.
|
||||
* `RememberMeServices.loginFail` is invoked.ƒ
|
||||
If remember me is not configured, this is a no-op.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[`rememberme`] package.
|
||||
See the javadoc:org.springframework.security.web.authentication.rememberme.package-summary[rememberme] package.
|
||||
* `AuthenticationFailureHandler` is invoked.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] interface.
|
||||
See the javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] interface.
|
||||
|
||||
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
|
||||
|
||||
* `SessionAuthenticationStrategy` is notified of a new login.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface.
|
||||
See the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[] interface.
|
||||
* The <<servlet-authentication-authentication>> is set on the <<servlet-authentication-securitycontextholder>>.
|
||||
Later, if you need to save the `SecurityContext` so that it can be automatically set on future requests, `SecurityContextRepository#saveContext` must be explicitly invoked.
|
||||
See the {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[`SecurityContextHolderFilter`] class.
|
||||
See the javadoc:org.springframework.security.web.context.SecurityContextHolderFilter[] class.
|
||||
|
||||
* `RememberMeServices.loginSuccess` is invoked.
|
||||
If remember me is not configured, this is a no-op.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[`rememberme`] package.
|
||||
See the javadoc:org.springframework.security.web.authentication.rememberme.package-summary[rememberme] package.
|
||||
* `ApplicationEventPublisher` publishes an `InteractiveAuthenticationSuccessEvent`.
|
||||
* `AuthenticationSuccessHandler` is invoked.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/AuthenticationSuccessHandler.html[`AuthenticationSuccessHandler`] interface.
|
||||
See the javadoc:org.springframework.security.web.authentication.AuthenticationSuccessHandler[] interface.
|
||||
|
||||
|
||||
// daoauthenticationprovider (goes in username/password)
|
||||
|
||||
@@ -143,10 +143,10 @@ Java::
|
||||
@Bean
|
||||
public AuthenticationEventPublisher authenticationEventPublisher
|
||||
(ApplicationEventPublisher applicationEventPublisher) {
|
||||
AuthenticationEventPublisher authenticationEventPublisher =
|
||||
DefaultAuthenticationEventPublisher authenticationEventPublisher =
|
||||
new DefaultAuthenticationEventPublisher(applicationEventPublisher);
|
||||
authenticationEventPublisher.setDefaultAuthenticationFailureEvent
|
||||
(GenericAuthenticationFailureEvent.class);
|
||||
(AbstractAuthenticationFailureEvent.class);
|
||||
return authenticationEventPublisher;
|
||||
}
|
||||
----
|
||||
@@ -159,7 +159,7 @@ Kotlin::
|
||||
fun authenticationEventPublisher
|
||||
(applicationEventPublisher: ApplicationEventPublisher?): AuthenticationEventPublisher {
|
||||
val authenticationEventPublisher = DefaultAuthenticationEventPublisher(applicationEventPublisher)
|
||||
authenticationEventPublisher.setDefaultAuthenticationFailureEvent(GenericAuthenticationFailureEvent::class.java)
|
||||
authenticationEventPublisher.setDefaultAuthenticationFailureEvent(AbstractAuthenticationFailureEvent::class.java)
|
||||
return authenticationEventPublisher
|
||||
}
|
||||
----
|
||||
|
||||
@@ -58,7 +58,7 @@ This means that `DefaultJaasAuthenticationProvider` is not bound to any particul
|
||||
To make it easy to inject a `Configuration` into `DefaultJaasAuthenticationProvider`, a default in-memory implementation named `InMemoryConfiguration` is provided.
|
||||
The implementation constructor accepts a `Map` where each key represents a login configuration name, and the value represents an `Array` of `AppConfigurationEntry` instances.
|
||||
`InMemoryConfiguration` also supports a default `Array` of `AppConfigurationEntry` objects that is used if no mapping is found within the provided `Map`.
|
||||
For details, see the {security-api-url}org/springframework/security/authentication/jaas/memory/InMemoryConfiguration.html[Javadoc of `InMemoryConfiguration`].
|
||||
For details, see the Javadoc of javadoc:org.springframework.security.authentication.jaas.memory.InMemoryConfiguration[].
|
||||
|
||||
|
||||
[[jaas-djap-config]]
|
||||
|
||||
@@ -30,16 +30,16 @@ Please note that if xref:servlet/exploits/csrf.adoc[CSRF protection] is disabled
|
||||
In your application it is not necessary to use `GET /logout` to perform a logout.
|
||||
So long as xref:servlet/exploits/csrf.adoc[the needed CSRF token] is present in the request, your application can simply `POST /logout` to induce a logout.
|
||||
|
||||
If you request `POST /logout`, then it will perform the following default operations using a series of {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[``LogoutHandler``]s:
|
||||
If you request `POST /logout`, then it will perform the following default operations using a series of javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] instances:
|
||||
|
||||
- Invalidate the HTTP session ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`])
|
||||
- Clear the xref:servlet/authentication/session-management.adoc#use-securitycontextholderstrategy[`SecurityContextHolderStrategy`] ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`])
|
||||
- Clear the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`])
|
||||
- Invalidate the HTTP session (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[])
|
||||
- Clear the xref:servlet/authentication/session-management.adoc#use-securitycontextholderstrategy[`SecurityContextHolderStrategy`] (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[])
|
||||
- Clear the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[])
|
||||
- Clean up any xref:servlet/authentication/rememberme.adoc[RememberMe authentication] (`TokenRememberMeServices` / `PersistentTokenRememberMeServices`)
|
||||
- Clear out any saved xref:servlet/exploits/csrf.adoc[CSRF token] ({security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`])
|
||||
- xref:servlet/authentication/events.adoc[Fire] a `LogoutSuccessEvent` ({security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.html[`LogoutSuccessEventPublishingLogoutHandler`])
|
||||
- Clear out any saved xref:servlet/exploits/csrf.adoc[CSRF token] (javadoc:org.springframework.security.web.csrf.CsrfLogoutHandler[])
|
||||
- xref:servlet/authentication/events.adoc[Fire] a `LogoutSuccessEvent` (javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler[])
|
||||
|
||||
Once completed, then it will exercise its default {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] which redirects to `/login?logout`.
|
||||
Once completed, then it will exercise its default javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] which redirects to `/login?logout`.
|
||||
|
||||
[[customizing-logout-uris]]
|
||||
== Customizing Logout URIs
|
||||
@@ -197,15 +197,15 @@ http {
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Because {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[``LogoutHandler``]s are for the purposes of cleanup, they should not throw exceptions.
|
||||
Because javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] instances are for the purposes of cleanup, they should not throw exceptions.
|
||||
|
||||
[TIP]
|
||||
Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[`LogoutHandler`] is a functional interface, you can provide a custom one as a lambda.
|
||||
Since javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] is a functional interface, you can provide a custom one as a lambda.
|
||||
|
||||
Some logout handler configurations are common enough that they are exposed directly in the `logout` DSL and `<logout>` element.
|
||||
One example is configuring session invalidation and another is which additional cookies should be deleted.
|
||||
|
||||
For example, you can configure the {security-api-url}org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.html[`CookieClearingLogoutHandler`] as seen above.
|
||||
For example, you can configure the javadoc:org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler[] as seen above.
|
||||
|
||||
[[delete-cookies]]
|
||||
Or you can instead set the appropriate configuration value like so:
|
||||
@@ -242,7 +242,7 @@ Xml::
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Specifying that the `JSESSIONID` cookie is not necessary since {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] removes it by virtue of invalidating the session.
|
||||
Specifying that the `JSESSIONID` cookie is not necessary since javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] removes it by virtue of invalidating the session.
|
||||
|
||||
[[clear-all-site-data]]
|
||||
=== Using Clear-Site-Data to Log Out the User
|
||||
@@ -310,7 +310,7 @@ http {
|
||||
== Customizing Logout Success
|
||||
|
||||
While using `logoutSuccessUrl` will suffice for most cases, you may need to do something different from redirecting to a URL once logout is complete.
|
||||
{security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] is the Spring Security component for customizing logout success actions.
|
||||
javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] is the Spring Security component for customizing logout success actions.
|
||||
|
||||
For example, instead of redirecting, you may want to only return a status code.
|
||||
In this case, you can provide a success handler instance, like so:
|
||||
@@ -349,7 +349,7 @@ Xml::
|
||||
======
|
||||
|
||||
[TIP]
|
||||
Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] is a functional interface, you can provide a custom one as a lambda.
|
||||
Since javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] is a functional interface, you can provide a custom one as a lambda.
|
||||
|
||||
[[creating-custom-logout-endpoint]]
|
||||
== Creating a Custom Logout Endpoint
|
||||
@@ -387,7 +387,7 @@ fun performLogout(): String {
|
||||
----
|
||||
======
|
||||
|
||||
then you will need to have that endpoint invoke Spring Security's {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] to ensure a secure and complete logout.
|
||||
then you will need to have that endpoint invoke Spring Security's javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] to ensure a secure and complete logout.
|
||||
Something like the following is needed at a minimum:
|
||||
|
||||
.Custom Logout Endpoint
|
||||
@@ -422,12 +422,12 @@ fun performLogout(val authentication: Authentication, val request: HttpServletRe
|
||||
----
|
||||
======
|
||||
|
||||
Such will clear out the {security-api-url}/org/springframework/security/core/context/SecurityContextHolderStrategy.html[`SecurityContextHolderStrategy`] and {security-api-url}/org/springframework/security/web/context/SecurityContextRepository.html[`SecurityContextRepository`] as needed.
|
||||
Such will clear out the javadoc:org.springframework.security.core.context.SecurityContextHolderStrategy[] and javadoc:org.springframework.security.web.context.SecurityContextRepository[] as needed.
|
||||
|
||||
Also, you'll need to <<permit-logout-endpoints, explicitly permit the endpoint>>.
|
||||
|
||||
[WARNING]
|
||||
Failing to call {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] means that xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[the `SecurityContext`] could still be available on subsequent requests, meaning that the user is not actually logged out.
|
||||
Failing to call javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] means that xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[the `SecurityContext`] could still be available on subsequent requests, meaning that the user is not actually logged out.
|
||||
|
||||
[[testing-logout]]
|
||||
== Testing Logout
|
||||
|
||||
@@ -18,7 +18,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to
|
||||
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
|
||||
|
||||
image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__.
|
||||
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.html[`BasicAuthenticationEntryPoint`], which sends a WWW-Authenticate header.
|
||||
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of javadoc:org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint[], which sends a WWW-Authenticate header.
|
||||
The `RequestCache` is typically a `NullRequestCache` that does not save the request since the client is capable of replaying the requests it originally requested.
|
||||
|
||||
When a client receives the `WWW-Authenticate` header, it knows it should retry with a username and password.
|
||||
@@ -41,18 +41,18 @@ image:{icondir}/number_3.png[] If authentication fails, then __Failure__.
|
||||
. The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out.
|
||||
. `RememberMeServices.loginFail` is invoked.
|
||||
If remember me is not configured, this is a no-op.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc.
|
||||
See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc.
|
||||
. `AuthenticationEntryPoint` is invoked to trigger the WWW-Authenticate to be sent again.
|
||||
See the {security-api-url}org/springframework/security/web/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`] interface in the Javadoc.
|
||||
See the javadoc:org.springframework.security.web.AuthenticationEntryPoint[] interface in the Javadoc.
|
||||
|
||||
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
|
||||
|
||||
. The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
|
||||
. `RememberMeServices.loginSuccess` is invoked.
|
||||
If remember me is not configured, this is a no-op.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc.
|
||||
See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc.
|
||||
. The `BasicAuthenticationFilter` invokes `FilterChain.doFilter(request,response)` to continue with the rest of the application logic.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/www/BasicAuthenticationFilter.html[`BasicAuthenticationFilter`] Class in the Javadoc
|
||||
See the javadoc:org.springframework.security.web.authentication.www.BasicAuthenticationFilter[] Class in the Javadoc
|
||||
|
||||
By default, Spring Security's HTTP Basic Authentication support is enabled.
|
||||
However, as soon as any servlet based configuration is provided, HTTP Basic must be explicitly provided.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[[servlet-authentication-credentialscontainer]]
|
||||
= CredentialsContainer
|
||||
|
||||
{security-api-url}org/springframework/security/core/CredentialsContainer.html[The `CredentialsContainer`] interface indicates that the implementing object contains sensitive data, and is used internally by Spring Security to erase the authentication credentials after a successful authentication.
|
||||
This interface is implemented by most of Spring Security internal domain classes, like {security-api-url}org/springframework/security/core/userdetails/User.html[User] and {security-api-url}org/springframework/security/authentication/UsernamePasswordAuthenticationToken.html[UsernamePasswordAuthenticationToken].
|
||||
The javadoc:org.springframework.security.core.CredentialsContainer[] interface indicates that the implementing object contains sensitive data, and is used internally by Spring Security to erase the authentication credentials after a successful authentication.
|
||||
This interface is implemented by most of Spring Security internal domain classes, like javadoc:org.springframework.security.core.userdetails.User[] and javadoc:org.springframework.security.authentication.UsernamePasswordAuthenticationToken[].
|
||||
|
||||
The `ProviderManager` manager checks whether the returned `Authentication` implements this interface.
|
||||
If so, xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[it calls the `eraseCredentials` method] to remove the credentials from the object.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
= DaoAuthenticationProvider
|
||||
:figures: servlet/authentication/unpwd
|
||||
|
||||
{security-api-url}org/springframework/security/authentication/dao/DaoAuthenticationProvider.html[`DaoAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that uses a xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] and xref:servlet/authentication/passwords/password-encoder.adoc#servlet-authentication-password-storage[`PasswordEncoder`] to authenticate a username and password.
|
||||
javadoc:org.springframework.security.authentication.dao.DaoAuthenticationProvider[] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that uses a xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] and xref:servlet/authentication/passwords/password-encoder.adoc#servlet-authentication-password-storage[`PasswordEncoder`] to authenticate a username and password.
|
||||
|
||||
This section examines how `DaoAuthenticationProvider` works within Spring Security.
|
||||
The following figure explains the workings of the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from the xref:servlet/authentication/passwords/index.adoc#servlet-authentication-unpwd-input[Reading the Username & Password] section.
|
||||
|
||||
@@ -33,7 +33,7 @@ key: A private key to prevent modification of the nonce token
|
||||
----
|
||||
|
||||
You need to ensure that you xref:features/authentication/password-storage.adoc#authentication-password-storage-configuration[configure] insecure plain text xref:features/authentication/password-storage.adoc#authentication-password-storage[Password Storage] using `NoOpPasswordEncoder`.
|
||||
(See the {security-api-url}org/springframework/security/crypto/password/NoOpPasswordEncoder.html[`NoOpPasswordEncoder`] class in the Javadoc.)
|
||||
(See the javadoc:org.springframework.security.crypto.password.NoOpPasswordEncoder[] class in the Javadoc.)
|
||||
The following provides an example of configuring Digest Authentication with Java Configuration:
|
||||
|
||||
.Digest Authentication
|
||||
|
||||
@@ -19,7 +19,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to
|
||||
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
|
||||
|
||||
image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__ and sends a redirect to the login page with the configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`].
|
||||
In most cases, the `AuthenticationEntryPoint` is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`].
|
||||
In most cases, the `AuthenticationEntryPoint` is an instance of javadoc:org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint[].
|
||||
|
||||
image:{icondir}/number_4.png[] The browser requests the login page to which it was redirected.
|
||||
|
||||
@@ -45,19 +45,19 @@ image:{icondir}/number_3.png[] If authentication fails, then __Failure__.
|
||||
. The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out.
|
||||
. `RememberMeServices.loginFail` is invoked.
|
||||
If remember me is not configured, this is a no-op.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc.
|
||||
See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc.
|
||||
. `AuthenticationFailureHandler` is invoked.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] class in the Javadoc
|
||||
See the javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] class in the Javadoc
|
||||
|
||||
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
|
||||
|
||||
. `SessionAuthenticationStrategy` is notified of a new login.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface in the Javadoc.
|
||||
See the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[] interface in the Javadoc.
|
||||
. The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
|
||||
See the {security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[`SecurityContextPersistenceFilter`] class in the Javadoc.
|
||||
See the javadoc:org.springframework.security.web.context.SecurityContextPersistenceFilter[] class in the Javadoc.
|
||||
. `RememberMeServices.loginSuccess` is invoked.
|
||||
If remember me is not configured, this is a no-op.
|
||||
See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc.
|
||||
See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc.
|
||||
. `ApplicationEventPublisher` publishes an `InteractiveAuthenticationSuccessEvent`.
|
||||
. The `AuthenticationSuccessHandler` is invoked. Typically, this is a `SimpleUrlAuthenticationSuccessHandler`, which redirects to a request saved by xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] when we redirect to the login page.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
Spring Security's `InMemoryUserDetailsManager` implements xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[UserDetailsService] to provide support for username/password based authentication that is stored in memory.
|
||||
`InMemoryUserDetailsManager` provides management of `UserDetails` by implementing the `UserDetailsManager` interface.
|
||||
`UserDetails`-based authentication is used by Spring Security when it is configured to <<servlet-authentication-unpwd-input,accept a username and password>> for authentication.
|
||||
`UserDetails`-based authentication is used by Spring Security when it is configured to xref:servlet/authentication/passwords/index.adoc#servlet-authentication-unpwd-input[accept a username and password] for authentication.
|
||||
|
||||
In the following sample, we use xref:features/authentication/password-storage.adoc#authentication-password-storage-boot-cli[Spring Boot CLI] to encode a password value of `password` and get the encoded password of `+{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW+`:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[servlet-authentication-userdetailsservice]]
|
||||
= UserDetailsService
|
||||
|
||||
{security-api-url}org/springframework/security/core/userdetails/UserDetailsService.html[`UserDetailsService`] is used by xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] for retrieving a username, a password, and other attributes for authenticating with a username and password.
|
||||
javadoc:org.springframework.security.core.userdetails.UserDetailsService[] is used by xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] for retrieving a username, a password, and other attributes for authenticating with a username and password.
|
||||
Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory], xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC], and xref:servlet/authentication/passwords/caching.adoc#servlet-authentication-caching-user-details[caching] implementations of `UserDetailsService`.
|
||||
|
||||
You can define custom authentication by exposing a custom `UserDetailsService` as a bean.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[[servlet-authentication-userdetails]]
|
||||
= UserDetails
|
||||
|
||||
{security-api-url}org/springframework/security/core/userdetails/UserDetails.html[`UserDetails`] is returned by the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`].
|
||||
javadoc:org.springframework.security.core.userdetails.UserDetails[] is returned by the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`].
|
||||
The xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] validates the `UserDetails` and then returns an xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that has a principal that is the `UserDetails` returned by the configured `UserDetailsService`.
|
||||
|
||||
@@ -59,8 +59,8 @@ Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
|
||||
== SecurityContextRepository
|
||||
|
||||
// FIXME: api documentation
|
||||
In Spring Security the association of the user to future requests is made using {security-api-url}org/springframework/security/web/context/SecurityContextRepository.html[`SecurityContextRepository`].
|
||||
The default implementation of `SecurityContextRepository` is {security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[`DelegatingSecurityContextRepository`] which delegates to the following:
|
||||
In Spring Security the association of the user to future requests is made using javadoc:org.springframework.security.web.context.SecurityContextRepository[].
|
||||
The default implementation of `SecurityContextRepository` is javadoc:org.springframework.security.web.context.DelegatingSecurityContextRepository[] which delegates to the following:
|
||||
|
||||
* <<httpsecuritycontextrepository,`HttpSessionSecurityContextRepository`>>
|
||||
* <<requestattributesecuritycontextrepository,`RequestAttributeSecurityContextRepository`>>
|
||||
@@ -68,18 +68,18 @@ The default implementation of `SecurityContextRepository` is {security-api-url}o
|
||||
[[httpsecuritycontextrepository]]
|
||||
=== HttpSessionSecurityContextRepository
|
||||
|
||||
The {security-api-url}org/springframework/security/web/context/HttpSessionSecurityContextRepository.html[`HttpSessionSecurityContextRepository`] associates the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to the `HttpSession`.
|
||||
The javadoc:org.springframework.security.web.context.HttpSessionSecurityContextRepository[] associates the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to the `HttpSession`.
|
||||
Users can replace `HttpSessionSecurityContextRepository` with another implementation of `SecurityContextRepository` if they wish to associate the user with subsequent requests in another way or not at all.
|
||||
|
||||
[[nullsecuritycontextrepository]]
|
||||
=== NullSecurityContextRepository
|
||||
|
||||
If it is not desirable to associate the `SecurityContext` to an `HttpSession` (i.e. when authenticating with OAuth) the {security-api-url}org/springframework/security/web/context/NullSecurityContextRepository.html[`NullSecurityContextRepository`] is an implementation of `SecurityContextRepository` that does nothing.
|
||||
If it is not desirable to associate the `SecurityContext` to an `HttpSession` (i.e. when authenticating with OAuth) the javadoc:org.springframework.security.web.context.NullSecurityContextRepository[] is an implementation of `SecurityContextRepository` that does nothing.
|
||||
|
||||
[[requestattributesecuritycontextrepository]]
|
||||
=== RequestAttributeSecurityContextRepository
|
||||
|
||||
The {security-api-url}org/springframework/security/web/context/RequestAttributeSecurityContextRepository.html[`RequestAttributeSecurityContextRepository`] saves the `SecurityContext` as a request attribute to make sure the `SecurityContext` is available for a single request that occurs across dispatch types that may clear out the `SecurityContext`.
|
||||
The javadoc:org.springframework.security.web.context.RequestAttributeSecurityContextRepository[] saves the `SecurityContext` as a request attribute to make sure the `SecurityContext` is available for a single request that occurs across dispatch types that may clear out the `SecurityContext`.
|
||||
|
||||
For example, assume that a client makes a request, is authenticated, and then an error occurs.
|
||||
Depending on the servlet container implementation, the error means that any `SecurityContext` that was established is cleared out and then the error dispatch is made.
|
||||
@@ -118,7 +118,7 @@ XML::
|
||||
[[delegatingsecuritycontextrepository]]
|
||||
=== DelegatingSecurityContextRepository
|
||||
|
||||
The {security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[`DelegatingSecurityContextRepository`] saves the `SecurityContext` to multiple `SecurityContextRepository` delegates and allows retrieval from any of the delegates in a specified order.
|
||||
The javadoc:org.springframework.security.web.context.DelegatingSecurityContextRepository[] saves the `SecurityContext` to multiple `SecurityContextRepository` delegates and allows retrieval from any of the delegates in a specified order.
|
||||
|
||||
The most useful arrangement for this is configured with the following example, which allows the use of both xref:requestattributesecuritycontextrepository[`RequestAttributeSecurityContextRepository`] and xref:httpsecuritycontextrepository[`HttpSessionSecurityContextRepository`] simultaneously.
|
||||
|
||||
@@ -189,7 +189,7 @@ In Spring Security 6, the example shown above is the default configuration.
|
||||
[[securitycontextpersistencefilter]]
|
||||
== SecurityContextPersistenceFilter
|
||||
|
||||
The {security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[`SecurityContextPersistenceFilter`] is responsible for persisting the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`].
|
||||
The javadoc:org.springframework.security.web.context.SecurityContextPersistenceFilter[] is responsible for persisting the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`].
|
||||
|
||||
image::{figures}/securitycontextpersistencefilter.png[]
|
||||
|
||||
@@ -210,7 +210,7 @@ To avoid these problems, the `SecurityContextPersistenceFilter` wraps both the `
|
||||
[[securitycontextholderfilter]]
|
||||
== SecurityContextHolderFilter
|
||||
|
||||
The {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[`SecurityContextHolderFilter`] is responsible for loading the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`].
|
||||
The javadoc:org.springframework.security.web.context.SecurityContextHolderFilter[] is responsible for loading the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`].
|
||||
|
||||
image::{figures}/securitycontextholderfilter.png[]
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ void loginSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication successfulAuthentication);
|
||||
----
|
||||
|
||||
See the Javadoc for {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] for a fuller discussion on what the methods do, although note that, at this stage, `AbstractAuthenticationProcessingFilter` calls only the `loginFail()` and `loginSuccess()` methods.
|
||||
See the Javadoc for javadoc:org.springframework.security.web.authentication.RememberMeServices[] for a fuller discussion on what the methods do, although note that, at this stage, `AbstractAuthenticationProcessingFilter` calls only the `loginFail()` and `loginSuccess()` methods.
|
||||
The `autoLogin()` method is called by `RememberMeAuthenticationFilter` whenever the `SecurityContextHolder` does not contain an `Authentication`.
|
||||
This interface, therefore, provides the underlying remember-me implementation with sufficient notification of authentication-related events and delegates to the implementation whenever a candidate web request might contain a cookie and wish to be remembered.
|
||||
This design allows any number of remember-me implementation strategies.
|
||||
|
||||
@@ -49,7 +49,7 @@ The latter is also used when configuring an invalid session URL through the name
|
||||
[[moving-away-from-sessionmanagementfilter]]
|
||||
==== Moving Away From `SessionManagementFilter`
|
||||
|
||||
In Spring Security 5, the default configuration relies on `SessionManagementFilter` to detect if a user just authenticated and invoke {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[the `SessionAuthenticationStrategy`].
|
||||
In Spring Security 5, the default configuration relies on `SessionManagementFilter` to detect if a user just authenticated and invoke the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[SessionAuthenticationStrategy].
|
||||
The problem with this is that it means that in a typical setup, the `HttpSession` must be read for every request.
|
||||
|
||||
In Spring Security 6, the default is that authentication mechanisms themselves must invoke the `SessionAuthenticationStrategy`.
|
||||
@@ -63,10 +63,10 @@ In Spring Security 6, the `SessionManagementFilter` is not used by default, ther
|
||||
|Method |Replacement
|
||||
|
||||
|`sessionAuthenticationErrorUrl`
|
||||
|Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism
|
||||
|Configure an javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] in your authentication mechanism
|
||||
|
||||
|`sessionAuthenticationFailureHandler`
|
||||
|Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism
|
||||
|Configure an javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] in your authentication mechanism
|
||||
|
||||
|`sessionAuthenticationStrategy`
|
||||
|Configure an `SessionAuthenticationStrategy` in your authentication mechanism as <<moving-away-from-sessionmanagementfilter,discussed above>>
|
||||
@@ -589,8 +589,8 @@ If that is your case, you might want to <<clearing-session-cookie-on-logout,conf
|
||||
|
||||
=== Customizing the Invalid Session Strategy
|
||||
|
||||
The `invalidSessionUrl` is a convenience method for setting the `InvalidSessionStrategy` using the {security-api-url}/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.html[`SimpleRedirectInvalidSessionStrategy` implementation].
|
||||
If you want to customize the behavior, you can implement the {security-api-url}/org/springframework/security/web/session/InvalidSessionStrategy.html[`InvalidSessionStrategy`] interface and configure it using the `invalidSessionStrategy` method:
|
||||
The `invalidSessionUrl` is a convenience method for setting the `InvalidSessionStrategy` using the javadoc:org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy[`SimpleRedirectInvalidSessionStrategy` implementation].
|
||||
If you want to customize the behavior, you can implement the javadoc:org.springframework.security.web.session.InvalidSessionStrategy[] interface and configure it using the `invalidSessionStrategy` method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -898,7 +898,7 @@ public class SomeClass {
|
||||
== Forcing Eager Session Creation
|
||||
|
||||
At times, it can be valuable to eagerly create sessions.
|
||||
This can be done by using the {security-api-url}org/springframework/security/web/session/ForceEagerSessionCreationFilter.html[`ForceEagerSessionCreationFilter`] which can be configured using:
|
||||
This can be done by using the javadoc:org.springframework.security.web.session.ForceEagerSessionCreationFilter[] which can be configured using:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -145,7 +145,7 @@ Many sites allow certain limited access under remember-me authentication, but re
|
||||
|
||||
[[authz-authorization-managers]]
|
||||
==== AuthorizationManagers
|
||||
There are also helpful static factories in {security-api-url}org/springframework/security/authorization/AuthorizationManagers.html[`AuthorizationManagers`] for composing individual ``AuthorizationManager``s into more sophisticated expressions.
|
||||
There are also helpful static factories in javadoc:org.springframework.security.authorization.AuthorizationManagers[] for composing individual ``AuthorizationManager``s into more sophisticated expressions.
|
||||
|
||||
[[authz-custom-authorization-manager]]
|
||||
==== Custom Authorization Managers
|
||||
@@ -391,7 +391,7 @@ Many sites allow certain limited access under remember-me authentication but req
|
||||
|
||||
When we have used the `IS_AUTHENTICATED_ANONYMOUSLY` attribute to grant anonymous access, this attribute was being processed by the `AuthenticatedVoter`.
|
||||
For more information, see
|
||||
{security-api-url}org/springframework/security/access/vote/AuthenticatedVoter.html[`AuthenticatedVoter`].
|
||||
javadoc:org.springframework.security.access.vote.AuthenticatedVoter[].
|
||||
|
||||
|
||||
[[authz-custom-voter]]
|
||||
|
||||
@@ -192,7 +192,7 @@ public SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// ...
|
||||
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
@@ -209,7 +209,7 @@ fun web(http: HttpSecurity): SecurityFilterChain {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
@@ -403,7 +403,7 @@ Spring Security supports matching requests against a regular expression.
|
||||
This can come in handy if you want to apply more strict matching criteria than `**` on a subdirectory.
|
||||
|
||||
For example, consider a path that contains the username and the rule that all usernames must be alphanumeric.
|
||||
You can use {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[`RegexRequestMatcher`] to respect this rule, like so:
|
||||
You can use javadoc:org.springframework.security.web.util.matcher.RegexRequestMatcher[] to respect this rule, like so:
|
||||
|
||||
.Match with Regex
|
||||
[tabs]
|
||||
@@ -643,7 +643,7 @@ This need can arise in at least two different ways:
|
||||
[NOTE]
|
||||
This feature is not currently supported in XML
|
||||
|
||||
In Java configuration, you can create your own {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[`RequestMatcher`] and supply it to the DSL like so:
|
||||
In Java configuration, you can create your own javadoc:org.springframework.security.web.util.matcher.RequestMatcher[] and supply it to the DSL like so:
|
||||
|
||||
.Authorize by Dispatcher Type
|
||||
====
|
||||
@@ -672,7 +672,7 @@ http {
|
||||
====
|
||||
|
||||
[TIP]
|
||||
Because {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[`RequestMatcher`] is a functional interface, you can supply it as a lambda in the DSL.
|
||||
Because javadoc:org.springframework.security.web.util.matcher.RequestMatcher[] is a functional interface, you can supply it as a lambda in the DSL.
|
||||
However, if you want to extract values from the request, you will need to have a concrete class since that requires overriding a `default` method.
|
||||
|
||||
Once authorized, you can test it using xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
||||
@@ -922,15 +922,15 @@ Because the performance impact is now addressed, Spring Security recommends usin
|
||||
== Migrating from `authorizeRequests`
|
||||
|
||||
[NOTE]
|
||||
`AuthorizationFilter` supersedes {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[`FilterSecurityInterceptor`].
|
||||
`AuthorizationFilter` supersedes javadoc:org.springframework.security.web.access.intercept.FilterSecurityInterceptor[].
|
||||
To remain backward compatible, `FilterSecurityInterceptor` remains the default.
|
||||
This section discusses how `AuthorizationFilter` works and how to override the default configuration.
|
||||
|
||||
The {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[`AuthorizationFilter`] provides xref:servlet/authorization/index.adoc#servlet-authorization[authorization] for ``HttpServletRequest``s.
|
||||
The javadoc:org.springframework.security.web.access.intercept.AuthorizationFilter[] provides xref:servlet/authorization/index.adoc#servlet-authorization[authorization] for ``HttpServletRequest``s.
|
||||
It is inserted into the xref:servlet/architecture.adoc#servlet-filterchainproxy[FilterChainProxy] as one of the xref:servlet/architecture.adoc#servlet-security-filters[Security Filters].
|
||||
|
||||
You can override the default when you declare a `SecurityFilterChain`.
|
||||
Instead of using {security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html#authorizeRequests()[`authorizeRequests`], use `authorizeHttpRequests`, like so:
|
||||
Instead of using javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#authorizeRequests()[authorizeRequests], use `authorizeHttpRequests`, like so:
|
||||
|
||||
.Use authorizeHttpRequests
|
||||
[tabs]
|
||||
@@ -960,12 +960,12 @@ This simplifies reuse and customization.
|
||||
Instead of the authentication needing to be looked up for every request, it will only look it up in requests where an authorization decision requires authentication.
|
||||
3. Bean-based configuration support.
|
||||
|
||||
When `authorizeHttpRequests` is used instead of `authorizeRequests`, then {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[`AuthorizationFilter`] is used instead of {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[`FilterSecurityInterceptor`].
|
||||
When `authorizeHttpRequests` is used instead of `authorizeRequests`, then javadoc:org.springframework.security.web.access.intercept.AuthorizationFilter[] is used instead of javadoc:org.springframework.security.web.access.intercept.FilterSecurityInterceptor[].
|
||||
|
||||
=== Migrating Expressions
|
||||
|
||||
Where possible, it is recommended that you use type-safe authorization managers instead of SpEL.
|
||||
For Java configuration, {security-api-url}org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.html[`WebExpressionAuthorizationManager`] is available to help migrate legacy SpEL.
|
||||
For Java configuration, javadoc:org.springframework.security.web.access.expression.WebExpressionAuthorizationManager[] is available to help migrate legacy SpEL.
|
||||
|
||||
To use `WebExpressionAuthorizationManager`, you can construct one with the expression you are trying to migrate, like so:
|
||||
|
||||
@@ -1009,12 +1009,12 @@ Kotlin::
|
||||
|
||||
For complex instructions that include bean references as well as other expressions, it is recommended that you change those to implement `AuthorizationManager` and refer to them by calling `.access(AuthorizationManager)`.
|
||||
|
||||
If you are not able to do that, you can configure a {security-api-url}org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.html[`DefaultHttpSecurityExpressionHandler`] with a bean resolver and supply that to `WebExpressionAuthorizationManager#setExpressionhandler`.
|
||||
If you are not able to do that, you can configure a javadoc:org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler[] with a bean resolver and supply that to `WebExpressionAuthorizationManager#setExpressionhandler`.
|
||||
|
||||
[[security-matchers]]
|
||||
== Security Matchers
|
||||
|
||||
The {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[`RequestMatcher`] interface is used to determine if a request matches a given rule.
|
||||
The javadoc:org.springframework.security.web.util.matcher.RequestMatcher[] interface is used to determine if a request matches a given rule.
|
||||
We use `securityMatchers` to determine if xref:servlet/configuration/java.adoc#jc-httpsecurity[a given `HttpSecurity`] should be applied to a given request.
|
||||
The same way, we can use `requestMatchers` to determine the authorization rules that we should apply to a given request.
|
||||
Look at the following example:
|
||||
@@ -1074,7 +1074,7 @@ open class SecurityConfig {
|
||||
<3> Allow access to URLs that start with `/admin/` to users with the `ADMIN` role
|
||||
<4> Any other request that doesn't match the rules above, will require authentication
|
||||
|
||||
The `securityMatcher(s)` and `requestMatcher(s)` methods will decide which `RequestMatcher` implementation fits best for your application: If {spring-framework-reference-url}web.html#spring-web[Spring MVC] is in the classpath, then {security-api-url}org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.html[`MvcRequestMatcher`] will be used, otherwise, {security-api-url}org/springframework/security/web/servlet/util/matcher/AntPathRequestMatcher.html[`AntPathRequestMatcher`] will be used.
|
||||
The `securityMatcher(s)` and `requestMatcher(s)` methods will decide which `RequestMatcher` implementation fits best for your application: If {spring-framework-reference-url}web.html#spring-web[Spring MVC] is in the classpath, then javadoc:org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher[] will be used, otherwise, javadoc:org.springframework.security.web.util.matcher.AntPathRequestMatcher[] will be used.
|
||||
You can read more about the Spring MVC integration xref:servlet/integrations/mvc.adoc[here].
|
||||
|
||||
If you want to use a specific `RequestMatcher`, just pass an implementation to the `securityMatcher` and/or `requestMatcher` methods:
|
||||
|
||||
@@ -117,15 +117,15 @@ A given invocation to `MyCustomerService#readCustomer` may look something like t
|
||||
|
||||
image::{figures}/methodsecurity.png[]
|
||||
|
||||
1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
|
||||
2. The interceptor invokes {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager#check`]
|
||||
1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
|
||||
2. The interceptor invokes javadoc:org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager[`PreAuthorizeAuthorizationManager#check`]
|
||||
3. The authorization manager uses a `MethodSecurityExpressionHandler` to parse the annotation's <<authorization-expressions,SpEL expression>> and constructs a corresponding `EvaluationContext` from a `MethodSecurityExpressionRoot` containing xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[a `Supplier<Authentication>`] and `MethodInvocation`.
|
||||
4. The interceptor uses this context to evaluate the expression; specifically, it reads xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] from the `Supplier` and checks whether it has `permission:read` in its collection of xref:servlet/authorization/architecture.adoc#authz-authorities[authorities]
|
||||
5. If the evaluation passes, then Spring AOP proceeds to invoke the method.
|
||||
6. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`] which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
|
||||
7. After the method returns, Spring AOP invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthorizationManagerAfterMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PostAuthorize` pointcut>>, operating the same as above, but with {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`]
|
||||
6. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an javadoc:org.springframework.security.access.AccessDeniedException[] which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
|
||||
7. After the method returns, Spring AOP invokes an javadoc:org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor[] that matches <<annotation-method-pointcuts,the `@PostAuthorize` pointcut>>, operating the same as above, but with javadoc:org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager[]
|
||||
8. If the evaluation passes (in this case, the return value belongs to the logged-in user), processing continues normally
|
||||
9. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`], which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
|
||||
9. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an javadoc:org.springframework.security.access.AccessDeniedException[], which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
|
||||
|
||||
[NOTE]
|
||||
If the method is not being called in the context of an HTTP request, you will likely need to handle the `AccessDeniedException` yourself
|
||||
@@ -150,7 +150,7 @@ Instead, use SpEL's boolean support or its support for delegating to a separate
|
||||
|
||||
Each annotation has its own pointcut instance that looks for that annotation or its <<meta-annotations,meta-annotation>> counterparts across the entire object hierarchy, starting at <<class-or-interface-annotations,the method and its enclosing class>>.
|
||||
|
||||
You can see the specifics of this in {security-api-url}org/springframework/security/authorization/method/AuthorizationMethodPointcuts.html[`AuthorizationMethodPointcuts`].
|
||||
// FIXME: AuthorizationMethodPointcuts is package private and Javadoc is not published You can see the specifics of this in javadoc:org.springframework.security.authorization.method.AuthorizationMethodPointcuts[].
|
||||
|
||||
[[annotation-method-interceptors]]
|
||||
=== Each Annotation Has Its Own Method Interceptor
|
||||
@@ -161,12 +161,12 @@ For example, if needed, you can disable the Spring Security defaults and <<_enab
|
||||
|
||||
The method interceptors are as follows:
|
||||
|
||||
* For <<use-preauthorize,`@PreAuthorize`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager`]
|
||||
* For <<use-postauthorize,`@PostAuthorize`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#postAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`]
|
||||
* For <<use-prefilter,`@PreFilter`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.html[`PreFilterAuthorizationMethodInterceptor`]
|
||||
* For <<use-postfilter,`@PostFilter`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.html[`PostFilterAuthorizationMethodInterceptor`]
|
||||
* For <<use-secured,`@Secured`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#secured`], which in turn uses {security-api-url}org/springframework/security/authorization/method/SecuredAuthorizationManager.html[`SecuredAuthorizationManager`]
|
||||
* For JSR-250 annotations, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#jsr250`], which in turn uses {security-api-url}org/springframework/security/authorization/method/Jsr250AuthorizationManager.html[`Jsr250AuthorizationManager`]
|
||||
* For <<use-preauthorize,`@PreAuthorize`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses javadoc:org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager[]
|
||||
* For <<use-postauthorize,`@PostAuthorize`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor[`AuthorizationManagerAfterMethodInterceptor#postAuthorize`], which in turn uses javadoc:org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager[]
|
||||
* For <<use-prefilter,`@PreFilter`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor[]
|
||||
* For <<use-postfilter,`@PostFilter`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor[]
|
||||
* For <<use-secured,`@Secured`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#secured`], which in turn uses javadoc:org.springframework.security.authorization.method.SecuredAuthorizationManager[]
|
||||
* For JSR-250 annotations, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#jsr250`], which in turn uses javadoc:org.springframework.security.authorization.method.Jsr250AuthorizationManager[]
|
||||
|
||||
Generally speaking, you can consider the following listing as representative of what interceptors Spring Security publishes when you add `@EnableMethodSecurity`:
|
||||
|
||||
@@ -311,7 +311,7 @@ The primary way Spring Security enables method-level authorization support is th
|
||||
[[use-preauthorize]]
|
||||
=== Authorizing Method Invocation with `@PreAuthorize`
|
||||
|
||||
When <<activate-method-security,Method Security is active>>, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PreAuthorize.html[`@PreAuthorize`] annotation like so:
|
||||
When <<activate-method-security,Method Security is active>>, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PreAuthorize[format=annotation] annotation like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -399,7 +399,7 @@ While `@PreAuthorize` is quite helpful for declaring needed authorities, it can
|
||||
[[use-postauthorize]]
|
||||
=== Authorization Method Results with `@PostAuthorize`
|
||||
|
||||
When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PostAuthorize.html[`@PostAuthorize`] annotation like so:
|
||||
When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PostAuthorize[format=annotation] annotation like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -546,10 +546,7 @@ If not, Spring Security will throw an `AccessDeniedException` and return a 403 s
|
||||
[[use-prefilter]]
|
||||
=== Filtering Method Parameters with `@PreFilter`
|
||||
|
||||
[NOTE]
|
||||
`@PreFilter` is not yet supported for Kotlin-specific data types; for that reason, only Java snippets are shown
|
||||
|
||||
When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PreFilter.html[`@PreFilter`] annotation like so:
|
||||
When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PreFilter[format=annotation] annotation like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -566,6 +563,20 @@ public class BankService {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
open class BankService {
|
||||
@PreFilter("filterObject.owner == authentication.name")
|
||||
fun updateAccounts(vararg accounts: Account): Collection<Account> {
|
||||
// ... `accounts` will only contain the accounts owned by the logged-in user
|
||||
return updated
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This is meant to filter out any values from `accounts` where the expression `filterObject.owner == authentication.name` fails.
|
||||
@@ -591,6 +602,23 @@ void updateAccountsWhenOwnedThenReturns() {
|
||||
assertThat(updated).containsOnly(ownedBy);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Autowired
|
||||
lateinit var bankService: BankService
|
||||
|
||||
@WithMockUser(username="owner")
|
||||
@Test
|
||||
fun updateAccountsWhenOwnedThenReturns() {
|
||||
val ownedBy: Account = ...
|
||||
val notOwnedBy: Account = ...
|
||||
val updated: Collection<Account> = bankService.updateAccounts(ownedBy, notOwnedBy)
|
||||
assertThat(updated).containsOnly(ownedBy)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
@@ -618,6 +646,23 @@ public Collection<Account> updateAccounts(Map<String, Account> accounts)
|
||||
@PreFilter("filterObject.owner == authentication.name")
|
||||
public Collection<Account> updateAccounts(Stream<Account> accounts)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreFilter("filterObject.owner == authentication.name")
|
||||
fun updateAccounts(accounts: Array<Account>): Collection<Account>
|
||||
|
||||
@PreFilter("filterObject.owner == authentication.name")
|
||||
fun updateAccounts(accounts: Collection<Account>): Collection<Account>
|
||||
|
||||
@PreFilter("filterObject.value.owner == authentication.name")
|
||||
fun updateAccounts(accounts: Map<String, Account>): Collection<Account>
|
||||
|
||||
@PreFilter("filterObject.owner == authentication.name")
|
||||
fun updateAccounts(accounts: Stream<Account>): Collection<Account>
|
||||
----
|
||||
======
|
||||
|
||||
The result is that the above method will only have the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
|
||||
@@ -625,10 +670,7 @@ The result is that the above method will only have the `Account` instances where
|
||||
[[use-postfilter]]
|
||||
=== Filtering Method Results with `@PostFilter`
|
||||
|
||||
[NOTE]
|
||||
`@PostFilter` is not yet supported for Kotlin-specific data types; for that reason, only Java snippets are shown
|
||||
|
||||
When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PostFilter.html[`@PostFilter`] annotation like so:
|
||||
When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PostFilter[format=annotation] annotation like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -645,6 +687,20 @@ public class BankService {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
open class BankService {
|
||||
@PreFilter("filterObject.owner == authentication.name")
|
||||
fun readAccounts(vararg ids: String): Collection<Account> {
|
||||
// ... the return value will be filtered to only contain the accounts owned by the logged-in user
|
||||
return accounts
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This is meant to filter out any values from the return value where the expression `filterObject.owner == authentication.name` fails.
|
||||
@@ -669,6 +725,22 @@ void readAccountsWhenOwnedThenReturns() {
|
||||
assertThat(accounts.get(0).getOwner()).isEqualTo("owner");
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Autowired
|
||||
lateinit var bankService: BankService
|
||||
|
||||
@WithMockUser(username="owner")
|
||||
@Test
|
||||
fun readAccountsWhenOwnedThenReturns() {
|
||||
val accounts: Collection<Account> = bankService.updateAccounts("owner", "not-owner")
|
||||
assertThat(accounts).hasSize(1)
|
||||
assertThat(accounts[0].owner).isEqualTo("owner")
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
@@ -678,7 +750,15 @@ void readAccountsWhenOwnedThenReturns() {
|
||||
|
||||
For example, the above `readAccounts` declaration will function the same way as the following other three:
|
||||
|
||||
```java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PostFilter("filterObject.owner == authentication.name")
|
||||
public Collection<Account> readAccounts(String... ids)
|
||||
|
||||
@PostFilter("filterObject.owner == authentication.name")
|
||||
public Account[] readAccounts(String... ids)
|
||||
|
||||
@@ -687,7 +767,25 @@ public Map<String, Account> readAccounts(String... ids)
|
||||
|
||||
@PostFilter("filterObject.owner == authentication.name")
|
||||
public Stream<Account> readAccounts(String... ids)
|
||||
```
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PostFilter("filterObject.owner == authentication.name")
|
||||
fun readAccounts(vararg ids: String): Collection<Account>
|
||||
|
||||
@PostFilter("filterObject.owner == authentication.name")
|
||||
fun readAccounts(vararg ids: String): Array<Account>
|
||||
|
||||
@PostFilter("filterObject.owner == authentication.name")
|
||||
fun readAccounts(vararg ids: String): Map<String, Account>
|
||||
|
||||
@PostFilter("filterObject.owner == authentication.name")
|
||||
fun readAccounts(vararg ids: String): Stream<Account>
|
||||
----
|
||||
======
|
||||
|
||||
The result is that the above method will return the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
|
||||
|
||||
@@ -697,7 +795,7 @@ In-memory filtering can obviously be expensive, and so be considerate of whether
|
||||
[[use-secured]]
|
||||
=== Authorizing Method Invocation with `@Secured`
|
||||
|
||||
{security-api-url}org/springframework/security/access/annotation/Secured.html[`@Secured`] is a legacy option for authorizing invocations.
|
||||
javadoc:org.springframework.security.access.annotation.Secured[format=annotation] is a legacy option for authorizing invocations.
|
||||
<<use-preauthorize,`@PreAuthorize`>> supercedes it and is recommended instead.
|
||||
|
||||
To use the `@Secured` annotation, you should first change your Method Security declaration to enable it like so:
|
||||
@@ -1377,7 +1475,7 @@ You can place your interceptor in between Spring Security method interceptors us
|
||||
=== Customizing Expression Handling
|
||||
|
||||
Or, third, you can customize how each SpEL expression is handled.
|
||||
To do that, you can expose a custom {security-api-url}org.springframework.security.access.expression.method.MethodSecurityExpressionHandler.html[`MethodSecurityExpressionHandler`], like so:
|
||||
To do that, you can expose a custom javadoc:org.springframework.security.access.expression.method.MethodSecurityExpressionHandler[], like so:
|
||||
|
||||
.Custom MethodSecurityExpressionHandler
|
||||
[tabs]
|
||||
@@ -1706,7 +1804,7 @@ The intention of this expression is to require that the current `Authentication`
|
||||
+
|
||||
Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
|
||||
|
||||
* If xref:servlet/integrations/data.adoc[Spring Data's] `@Param` annotation is present on at least one parameter for the method, the value is used.
|
||||
2. If xref:servlet/integrations/data.adoc[Spring Data's] `@Param` annotation is present on at least one parameter for the method, the value is used.
|
||||
The following example uses the `@Param` annotation:
|
||||
+
|
||||
[tabs]
|
||||
@@ -1740,10 +1838,10 @@ The intention of this expression is to require that `name` be equal to `Authenti
|
||||
+
|
||||
Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
|
||||
|
||||
* If you compile your code with the `-parameters` argument, the standard JDK reflection API is used to discover the parameter names.
|
||||
3. If you compile your code with the `-parameters` argument, the standard JDK reflection API is used to discover the parameter names.
|
||||
This works on both classes and interfaces.
|
||||
|
||||
* Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols.
|
||||
4. Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols.
|
||||
This does not work for interfaces, since they do not have debug information about the parameter names.
|
||||
For interfaces, either annotations or the `-parameters` approach must be used.
|
||||
|
||||
@@ -2259,8 +2357,8 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu
|
||||
There are some scenarios where you may not wish to throw an `AuthorizationDeniedException` when a method is invoked without the required permissions.
|
||||
Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where authorization denied happened before invoking the method.
|
||||
|
||||
Spring Security provides support for handling authorization denied on method invocation by using the {security-api-url}org/springframework/security/authorization/method/HandleAuthorizationDenied.html[`@HandleAuthorizationDenied`].
|
||||
The handler works for denied authorizations that happened in the <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> as well as {security-api-url}org/springframework/security/authorization/AuthorizationDeniedException.html[`AuthorizationDeniedException`] thrown from the method invocation itself.
|
||||
Spring Security provides support for handling authorization denied on method invocation by using the javadoc:org.springframework.security.authorization.method.HandleAuthorizationDenied[format=annotation].
|
||||
The handler works for denied authorizations that happened in the <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> as well as javadoc:org.springframework.security.authorization.AuthorizationDeniedException[] thrown from the method invocation itself.
|
||||
|
||||
Let's consider the example from the <<authorize-object,previous section>>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@HandleAuthorizationDenied`:
|
||||
|
||||
@@ -2375,7 +2473,7 @@ fun getEmailWhenProxiedThenNullEmail() {
|
||||
There are some scenarios where you might want to return a secure result derived from the denied result.
|
||||
For example, if a user is not authorized to see email addresses, you might want to apply some masking on the original email address, i.e. _useremail@example.com_ would become _use\\******@example.com_.
|
||||
|
||||
For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the {security-api-url}org/springframework/security/authorization/method/MethodInvocationResult.html[`MethodInvocationResult`] as an argument.
|
||||
For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the javadoc:org.springframework.security.authorization.method.MethodInvocationResult[] as an argument.
|
||||
Let's continue with the previous example, but instead of returning `null`, we will return a masked value of the email:
|
||||
|
||||
[tabs]
|
||||
@@ -2720,7 +2818,7 @@ If you are using `@EnableGlobalMethodSecurity`, you should migrate to `@EnableMe
|
||||
[[servlet-replace-globalmethodsecurity-with-methodsecurity]]
|
||||
=== Replace xref:servlet/authorization/method-security.adoc#jc-enable-global-method-security[global method security] with xref:servlet/authorization/method-security.adoc#jc-enable-method-security[method security]
|
||||
|
||||
{security-api-url}org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.html[`@EnableGlobalMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[`<global-method-security>`] are deprecated in favor of {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[`<method-security>`], respectively.
|
||||
javadoc:org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity[format=annotation] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[`<global-method-security>`] are deprecated in favor of javadoc:org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[`<method-security>`], respectively.
|
||||
The new annotation and XML element activate Spring's xref:servlet/authorization/method-security.adoc#jc-enable-method-security[pre-post annotations] by default and use `AuthorizationManager` internally.
|
||||
|
||||
This means that the following two listings are functionally equivalent:
|
||||
|
||||
@@ -176,8 +176,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.httpBasic(withDefaults());
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
@@ -185,10 +185,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
The default configuration (shown in the preceding example):
|
||||
|
||||
* Ensures that any request to our application requires the user to be authenticated
|
||||
* Lets users authenticate with form based login
|
||||
* Lets users authenticate with form-based login
|
||||
* Lets users authenticate with HTTP Basic authentication
|
||||
|
||||
Note that this configuration is parallels the XML Namespace configuration:
|
||||
Note that this configuration parallels the XML namespace configuration:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
@@ -199,12 +199,16 @@ Note that this configuration is parallels the XML Namespace configuration:
|
||||
</http>
|
||||
----
|
||||
|
||||
== Multiple HttpSecurity Instances
|
||||
=== Multiple HttpSecurity Instances
|
||||
|
||||
To effectively manage security in an application where certain areas need different protection, we can employ multiple filter chains alongside the `securityMatcher` DSL method.
|
||||
This approach allows us to define distinct security configurations tailored to specific parts of the application, enhancing overall application security and control.
|
||||
|
||||
We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
|
||||
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
|
||||
The following example has a different configuration for URLs that start with `/api/`.
|
||||
The following example has a different configuration for URLs that begin with `/api/`:
|
||||
|
||||
[[multiple-httpsecurity-instances-java]]
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@@ -212,7 +216,6 @@ The following example has a different configuration for URLs that start with `/a
|
||||
public class MultiHttpSecurityConfig {
|
||||
@Bean <1>
|
||||
public UserDetailsService userDetailsService() throws Exception {
|
||||
// ensure the passwords are encoded properly
|
||||
UserBuilder users = User.withDefaultPasswordEncoder();
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(users.username("user").password("password").roles("USER").build());
|
||||
@@ -228,7 +231,7 @@ public class MultiHttpSecurityConfig {
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().hasRole("ADMIN")
|
||||
)
|
||||
.httpBasic(withDefaults());
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@@ -238,18 +241,244 @@ public class MultiHttpSecurityConfig {
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults());
|
||||
.formLogin(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Configure Authentication as usual.
|
||||
<2> Create an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first.
|
||||
<3> The `http.securityMatcher` states that this `HttpSecurity` is applicable only to URLs that start with `/api/`.
|
||||
<3> The `http.securityMatcher()` states that this `HttpSecurity` is applicable only to URLs that begin with `/api/`.
|
||||
<4> Create another instance of `SecurityFilterChain`.
|
||||
If the URL does not start with `/api/`, this configuration is used.
|
||||
If the URL does not begin with `/api/`, this configuration is used.
|
||||
This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
|
||||
|
||||
=== Choosing `securityMatcher` or `requestMatchers`
|
||||
|
||||
A common question is:
|
||||
|
||||
> What is the difference between the `http.securityMatcher()` method and `requestMatchers()` used for request authorization (i.e. inside of `http.authorizeHttpRequests()`)?
|
||||
|
||||
To answer this question, it helps to understand that each `HttpSecurity` instance used to build a `SecurityFilterChain` contains a `RequestMatcher` to match incoming requests.
|
||||
If a request does not match a `SecurityFilterChain` with higher priority (e.g. `@Order(1)`), the request can be tried against a filter chain with lower priority (e.g. no `@Order`).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The matching logic for multiple filter chains is performed by the xref:servlet/architecture.adoc#servlet-filterchainproxy[`FilterChainProxy`].
|
||||
====
|
||||
|
||||
The default `RequestMatcher` matches *any request* to ensure Spring Security protects *all requests by default*.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Specifying a `securityMatcher` overrides this default.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
If no filter chain matches a particular request, the request is *not protected* by Spring Security.
|
||||
====
|
||||
|
||||
The following example demonstrates a single filter chain that only protects requests that begin with `/secured/`:
|
||||
|
||||
[[choosing-security-matcher-request-matchers-java]]
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class PartialSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() throws Exception {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.securityMatcher("/secured/**") <1>
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/secured/user").hasRole("USER") <2>
|
||||
.requestMatchers("/secured/admin").hasRole("ADMIN") <3>
|
||||
.anyRequest().authenticated() <4>
|
||||
)
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.formLogin(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Requests that begin with `/secured/` will be protected but any other requests are not protected.
|
||||
<2> Requests to `/secured/user` require the `ROLE_USER` authority.
|
||||
<3> Requests to `/secured/admin` require the `ROLE_ADMIN` authority.
|
||||
<4> Any other requests (such as `/secured/other`) simply require an authenticated user.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
It is _recommended_ to provide a `SecurityFilterChain` that does not specify any `securityMatcher` to ensure the entire application is protected, as demonstrated in the <<multiple-httpsecurity-instances-java,earlier example>>.
|
||||
====
|
||||
|
||||
Notice that the `requestMatchers` method only applies to individual authorization rules.
|
||||
Each request listed there must also match the overall `securityMatcher` for this particular `HttpSecurity` instance used to create the `SecurityFilterChain`.
|
||||
Using `anyRequest()` in this example matches all other requests within this particular `SecurityFilterChain` (which must begin with `/secured/`).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
See xref:servlet/authorization/authorize-http-requests.adoc[Authorize HttpServletRequests] for more information on `requestMatchers`.
|
||||
====
|
||||
|
||||
=== `SecurityFilterChain` Endpoints
|
||||
|
||||
Several filters in the `SecurityFilterChain` directly provide endpoints, such as the `UsernamePasswordAuthenticationFilter` which is set up by `http.formLogin()` and provides the `POST /login` endpoint.
|
||||
In the <<choosing-security-matcher-request-matchers-java,above example>>, the `/login` endpoint is not matched by `http.securityMatcher("/secured/**")` and therefore that application would not have any `GET /login` or `POST /login` endpoint.
|
||||
Such requests would return `404 Not Found`.
|
||||
This is often surprising to users.
|
||||
|
||||
Specifying `http.securityMatcher()` affects what requests are matched by that `SecurityFilterChain`.
|
||||
However, it does not automatically affect endpoints provided by the filter chain.
|
||||
In such cases, you may need to customize the URL of any endpoints you would like the filter chain to provide.
|
||||
|
||||
The following example demonstrates a configuration that secures requests that begin with `/secured/` and denies all other requests, while also customizing endpoints provided by the `SecurityFilterChain`:
|
||||
|
||||
[[security-filter-chain-endpoints-java]]
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecuredSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() throws Exception {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.securityMatcher("/secured/**") <1>
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated() <2>
|
||||
)
|
||||
.formLogin(formLogin -> formLogin <3>
|
||||
.loginPage("/secured/login")
|
||||
.loginProcessingUrl("/secured/login")
|
||||
.permitAll()
|
||||
)
|
||||
.logout(logout -> logout <4>
|
||||
.logoutUrl("/secured/logout")
|
||||
.logoutSuccessUrl("/secured/login?logout")
|
||||
.permitAll()
|
||||
)
|
||||
.formLogin(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().denyAll() <5>
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Requests that begin with `/secured/` will be protected by this filter chain.
|
||||
<2> Requests that begin with `/secured/` require an authenticated user.
|
||||
<3> Customize form login to prefix URLs with `/secured/`.
|
||||
<4> Customize logout to prefix URLs with `/secured/`.
|
||||
<5> All other requests will be denied.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This example customizes the login and logout pages, which disables Spring Security's generated pages.
|
||||
You must xref:servlet/authentication/passwords/form.adoc#servlet-authentication-form-custom[provide your own] custom endpoints for `GET /secured/login` and `GET /secured/logout`.
|
||||
Note that Spring Security still provides `POST /secured/login` and `POST /secured/logout` endpoints for you.
|
||||
====
|
||||
|
||||
=== Real World Example
|
||||
|
||||
The following example demonstrates a slightly more real-world configuration putting all of these elements together:
|
||||
|
||||
[[real-world-example-java]]
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class BankingSecurityConfig {
|
||||
|
||||
@Bean <1>
|
||||
public UserDetailsService userDetailsService() {
|
||||
UserBuilder users = User.withDefaultPasswordEncoder();
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
|
||||
manager.createUser(users.username("user2").password("password").roles("USER").build());
|
||||
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1) <2>
|
||||
public SecurityFilterChain approvalsSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
String[] approvalsPaths = { "/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**" };
|
||||
http
|
||||
.securityMatcher(approvalsPaths)
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().hasRole("ADMIN")
|
||||
)
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2) <3>
|
||||
public SecurityFilterChain bankingSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
String[] bankingPaths = { "/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**" };
|
||||
String[] viewBalancePaths = { "/balances/**" };
|
||||
http
|
||||
.securityMatcher(bankingPaths)
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers(viewBalancePaths).hasRole("VIEW_BALANCE")
|
||||
.anyRequest().hasRole("USER")
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean <4>
|
||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers(allowedPaths).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(formLogin -> formLogin
|
||||
.loginPage("/user-login")
|
||||
.loginProcessingUrl("/user-login")
|
||||
)
|
||||
.logout(logout -> logout
|
||||
.logoutUrl("/user-logout")
|
||||
.logoutSuccessUrl("/?logout")
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Begin by configuring authentication settings.
|
||||
<2> Define a `SecurityFilterChain` instance with `@Order(1)`, which means that this filter chain will have the highest priority.
|
||||
This filter chain applies only to requests that begin with `/accounts/approvals/`, `/loans/approvals/` or `/credit-cards/approvals/`.
|
||||
Requests to this filter chain require the `ROLE_ADMIN` authority and allow HTTP Basic Authentication.
|
||||
<3> Next, create another `SecurityFilterChain` instance with `@Order(2)` which will be considered second.
|
||||
This filter chain applies only to requests that begin with `/accounts/`, `/loans/`, `/credit-cards/`, or `/balances/`.
|
||||
Notice that because this filter chain is second, any requests that include `/approvals/` will match the previous filter chain and will *not* be matched by this filter chain.
|
||||
Requests to this filter chain require the `ROLE_USER` authority.
|
||||
This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
|
||||
<4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
|
||||
This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
|
||||
Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
|
||||
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
|
||||
|
||||
[[jc-custom-dsls]]
|
||||
== Custom DSLs
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
[NOTE]
|
||||
Make sure to import the `org.springframework.security.config.annotation.web.invoke` function to enable the Kotlin DSL in your class, as the IDE will not always auto-import the method, causing compilation issues.
|
||||
|
||||
The default configuration (shown in the preceding listing):
|
||||
The default configuration (shown in the preceding example):
|
||||
|
||||
* Ensures that any request to our application requires the user to be authenticated
|
||||
* Lets users authenticate with form-based login
|
||||
@@ -55,12 +55,16 @@ Note that this configuration parallels the XML namespace configuration:
|
||||
</http>
|
||||
----
|
||||
|
||||
== Multiple HttpSecurity Instances
|
||||
=== Multiple HttpSecurity Instances
|
||||
|
||||
We can configure multiple `HttpSecurity` instances, just as we can have multiple `<http>` blocks.
|
||||
To effectively manage security in an application where certain areas need different protection, we can employ multiple filter chains alongside the `securityMatcher` DSL method.
|
||||
This approach allows us to define distinct security configurations tailored to specific parts of the application, enhancing overall application security and control.
|
||||
|
||||
We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
|
||||
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
|
||||
The following example has a different configuration for URLs that start with `/api/`:
|
||||
The following example has a different configuration for URLs that begin with `/api/`:
|
||||
|
||||
[[multiple-httpsecurity-instances-kotlin]]
|
||||
[source,kotlin]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
@@ -69,16 +73,16 @@ import org.springframework.security.config.annotation.web.invoke
|
||||
@EnableWebSecurity
|
||||
class MultiHttpSecurityConfig {
|
||||
@Bean <1>
|
||||
public fun userDetailsService(): UserDetailsService {
|
||||
val users: User.UserBuilder = User.withDefaultPasswordEncoder()
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
val users = User.withDefaultPasswordEncoder()
|
||||
val manager = InMemoryUserDetailsManager()
|
||||
manager.createUser(users.username("user").password("password").roles("USER").build())
|
||||
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build())
|
||||
return manager
|
||||
}
|
||||
|
||||
@Order(1) <2>
|
||||
@Bean
|
||||
@Order(1) <2>
|
||||
open fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
securityMatcher("/api/**") <3>
|
||||
@@ -102,10 +106,243 @@ class MultiHttpSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> Configure Authentication as usual.
|
||||
<2> Create an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first.
|
||||
<3> The `http.securityMatcher` states that this `HttpSecurity` is applicable only to URLs that start with `/api/`
|
||||
<3> The `http.securityMatcher()` states that this `HttpSecurity` is applicable only to URLs that begin with `/api/`.
|
||||
<4> Create another instance of `SecurityFilterChain`.
|
||||
If the URL does not start with `/api/`, this configuration is used.
|
||||
If the URL does not begin with `/api/`, this configuration is used.
|
||||
This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
|
||||
|
||||
=== Choosing `securityMatcher` or `requestMatchers`
|
||||
|
||||
A common question is:
|
||||
|
||||
> What is the difference between the `http.securityMatcher()` method and `requestMatchers()` used for request authorization (i.e. inside of `http.authorizeHttpRequests()`)?
|
||||
|
||||
To answer this question, it helps to understand that each `HttpSecurity` instance used to build a `SecurityFilterChain` contains a `RequestMatcher` to match incoming requests.
|
||||
If a request does not match a `SecurityFilterChain` with higher priority (e.g. `@Order(1)`), the request can be tried against a filter chain with lower priority (e.g. no `@Order`).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The matching logic for multiple filter chains is performed by the xref:servlet/architecture.adoc#servlet-filterchainproxy[`FilterChainProxy`].
|
||||
====
|
||||
|
||||
The default `RequestMatcher` matches *any request* to ensure Spring Security protects *all requests by default*.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Specifying a `securityMatcher` overrides this default.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
If no filter chain matches a particular request, the request is *not protected* by Spring Security.
|
||||
====
|
||||
|
||||
The following example demonstrates a single filter chain that only protects requests that begin with `/secured/`:
|
||||
|
||||
[[choosing-security-matcher-request-matchers-kotlin]]
|
||||
[source,kotlin]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class PartialSecurityConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
securityMatcher("/secured/**") <1>
|
||||
authorizeHttpRequests {
|
||||
authorize("/secured/user", hasRole("USER")) <2>
|
||||
authorize("/secured/admin", hasRole("ADMIN")) <3>
|
||||
authorize(anyRequest, authenticated) <4>
|
||||
}
|
||||
httpBasic { }
|
||||
formLogin { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Requests that begin with `/secured/` will be protected but any other requests are not protected.
|
||||
<2> Requests to `/secured/user` require the `ROLE_USER` authority.
|
||||
<3> Requests to `/secured/admin` require the `ROLE_ADMIN` authority.
|
||||
<4> Any other requests (such as `/secured/other`) simply require an authenticated user.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
It is _recommended_ to provide a `SecurityFilterChain` that does not specify any `securityMatcher` to ensure the entire application is protected, as demonstrated in the <<multiple-httpsecurity-instances-kotlin,earlier example>>.
|
||||
====
|
||||
|
||||
Notice that the `requestMatchers` method only applies to individual authorization rules.
|
||||
Each request listed there must also match the overall `securityMatcher` for this particular `HttpSecurity` instance used to create the `SecurityFilterChain`.
|
||||
Using `anyRequest()` in this example matches all other requests within this particular `SecurityFilterChain` (which must begin with `/secured/`).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
See xref:servlet/authorization/authorize-http-requests.adoc[Authorize HttpServletRequests] for more information on `requestMatchers`.
|
||||
====
|
||||
|
||||
=== `SecurityFilterChain` Endpoints
|
||||
|
||||
Several filters in the `SecurityFilterChain` directly provide endpoints, such as the `UsernamePasswordAuthenticationFilter` which is set up by `http.formLogin()` and provides the `POST /login` endpoint.
|
||||
In the <<choosing-security-matcher-request-matchers-kotlin,above example>>, the `/login` endpoint is not matched by `http.securityMatcher("/secured/**")` and therefore that application would not have any `GET /login` or `POST /login` endpoint.
|
||||
Such requests would return `404 Not Found`.
|
||||
This is often surprising to users.
|
||||
|
||||
Specifying `http.securityMatcher()` affects what requests are matched by that `SecurityFilterChain`.
|
||||
However, it does not automatically affect endpoints provided by the filter chain.
|
||||
In such cases, you may need to customize the URL of any endpoints you would like the filter chain to provide.
|
||||
|
||||
The following example demonstrates a configuration that secures requests that begin with `/secured/` and denies all other requests, while also customizing endpoints provided by the `SecurityFilterChain`:
|
||||
|
||||
[[security-filter-chain-endpoints-kotlin]]
|
||||
[source,kotlin]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class SecuredSecurityConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
securityMatcher("/secured/**") <1>
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated) <2>
|
||||
}
|
||||
formLogin { <3>
|
||||
loginPage = "/secured/login"
|
||||
loginProcessingUrl = "/secured/login"
|
||||
permitAll = true
|
||||
}
|
||||
logout { <4>
|
||||
logoutUrl = "/secured/logout"
|
||||
logoutSuccessUrl = "/secured/login?logout"
|
||||
permitAll = true
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun defaultFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, denyAll) <5>
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Requests that begin with `/secured/` will be protected by this filter chain.
|
||||
<2> Requests that begin with `/secured/` require an authenticated user.
|
||||
<3> Customize form login to prefix URLs with `/secured/`.
|
||||
<4> Customize logout to prefix URLs with `/secured/`.
|
||||
<5> All other requests will be denied.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This example customizes the login and logout pages, which disables Spring Security's generated pages.
|
||||
You must xref:servlet/authentication/passwords/form.adoc#servlet-authentication-form-custom[provide your own] custom endpoints for `GET /secured/login` and `GET /secured/logout`.
|
||||
Note that Spring Security still provides `POST /secured/login` and `POST /secured/logout` endpoints for you.
|
||||
====
|
||||
|
||||
=== Real World Example
|
||||
|
||||
The following example demonstrates a slightly more real-world configuration putting all of these elements together:
|
||||
|
||||
[[real-world-example-kotlin]]
|
||||
[source,kotlin]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class BankingSecurityConfig {
|
||||
@Bean <1>
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
val users = User.withDefaultPasswordEncoder()
|
||||
val manager = InMemoryUserDetailsManager()
|
||||
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build())
|
||||
manager.createUser(users.username("user2").password("password").roles("USER").build())
|
||||
manager.createUser(users.username("admin").password("password").roles("ADMIN").build())
|
||||
return manager
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1) <2>
|
||||
open fun approvalsSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val approvalsPaths = arrayOf("/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**")
|
||||
http {
|
||||
securityMatcher(approvalsPaths)
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, hasRole("ADMIN"))
|
||||
}
|
||||
httpBasic { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2) <3>
|
||||
open fun bankingSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val bankingPaths = arrayOf("/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**")
|
||||
val viewBalancePaths = arrayOf("/balances/**")
|
||||
http {
|
||||
securityMatcher(bankingPaths)
|
||||
authorizeHttpRequests {
|
||||
authorize(viewBalancePaths, hasRole("VIEW_BALANCE"))
|
||||
authorize(anyRequest, hasRole("USER"))
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean <4>
|
||||
open fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val allowedPaths = arrayOf("/", "/user-login", "/user-logout", "/notices", "/contact", "/register")
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(allowedPaths, permitAll)
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
formLogin {
|
||||
loginPage = "/user-login"
|
||||
loginProcessingUrl = "/user-login"
|
||||
}
|
||||
logout {
|
||||
logoutUrl = "/user-logout"
|
||||
logoutSuccessUrl = "/?logout"
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Begin by configuring authentication settings.
|
||||
<2> Define a `SecurityFilterChain` instance with `@Order(1)`, which means that this filter chain will have the highest priority.
|
||||
This filter chain applies only to requests that begin with `/accounts/approvals/`, `/loans/approvals/` or `/credit-cards/approvals/`.
|
||||
Requests to this filter chain require the `ROLE_ADMIN` authority and allow HTTP Basic Authentication.
|
||||
<3> Next, create another `SecurityFilterChain` instance with `@Order(2)` which will be considered second.
|
||||
This filter chain applies only to requests that begin with `/accounts/`, `/loans/`, `/credit-cards/`, or `/balances/`.
|
||||
Notice that because this filter chain is second, any requests that include `/approvals/` will match the previous filter chain and will *not* be matched by this filter chain.
|
||||
Requests to this filter chain require the `ROLE_USER` authority.
|
||||
This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
|
||||
<4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
|
||||
This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
|
||||
Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
|
||||
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
|
||||
|
||||
@@ -79,20 +79,20 @@ To learn more about CSRF protection for your application, consider the following
|
||||
[[csrf-components]]
|
||||
== Understanding CSRF Protection's Components
|
||||
|
||||
CSRF protection is provided by several components that are composed within the {security-api-url}org/springframework/security/web/csrf/CsrfFilter.html[`CsrfFilter`]:
|
||||
CSRF protection is provided by several components that are composed within the javadoc:org.springframework.security.web.csrf.CsrfFilter[]:
|
||||
|
||||
.`CsrfFilter` Components
|
||||
image::{figures}/csrf.png[]
|
||||
|
||||
CSRF protection is divided into two parts:
|
||||
|
||||
1. Make the {security-api-url}org/springframework/security/web/csrf/CsrfToken.html[`CsrfToken`] available to the application by delegating to the <<csrf-token-request-handler,`CsrfTokenRequestHandler`>>.
|
||||
1. Make the javadoc:org.springframework.security.web.csrf.CsrfToken[] available to the application by delegating to the <<csrf-token-request-handler,`CsrfTokenRequestHandler`>>.
|
||||
2. Determine if the request requires CSRF protection, load and validate the token, and <<csrf-access-denied-handler,handle `AccessDeniedException`>>.
|
||||
|
||||
.`CsrfFilter` Processing
|
||||
image::{figures}/csrf-processing.png[]
|
||||
|
||||
* image:{icondir}/number_1.png[] First, the {security-api-url}org/springframework/security/web/csrf/DeferredCsrfToken.html[`DeferredCsrfToken`] is loaded, which holds a reference to the <<csrf-token-repository,`CsrfTokenRepository`>> so that the persisted `CsrfToken` can be loaded later (in image:{icondir}/number_4.png[]).
|
||||
* image:{icondir}/number_1.png[] First, the javadoc:org.springframework.security.web.csrf.DeferredCsrfToken[] is loaded, which holds a reference to the <<csrf-token-repository,`CsrfTokenRepository`>> so that the persisted `CsrfToken` can be loaded later (in image:{icondir}/number_4.png[]).
|
||||
* image:{icondir}/number_2.png[] Second, a `Supplier<CsrfToken>` (created from `DeferredCsrfToken`) is given to the <<csrf-token-request-handler,`CsrfTokenRequestHandler`>>, which is responsible for populating a request attribute to make the `CsrfToken` available to the rest of the application.
|
||||
* image:{icondir}/number_3.png[] Next, the main CSRF protection processing begins and checks if the current request requires CSRF protection. If not required, the filter chain is continued and processing ends.
|
||||
* image:{icondir}/number_4.png[] If CSRF protection is required, the persisted `CsrfToken` is finally loaded from the `DeferredCsrfToken`.
|
||||
@@ -128,7 +128,7 @@ You can also specify <<csrf-token-repository-custom,your own implementation>> to
|
||||
[[csrf-token-repository-httpsession]]
|
||||
=== Using the `HttpSessionCsrfTokenRepository`
|
||||
|
||||
By default, Spring Security stores the expected CSRF token in the `HttpSession` by using {security-api-url}org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.html[`HttpSessionCsrfTokenRepository`], so no additional code is necessary.
|
||||
By default, Spring Security stores the expected CSRF token in the `HttpSession` by using javadoc:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository[], so no additional code is necessary.
|
||||
|
||||
The `HttpSessionCsrfTokenRepository` reads the token from an HTTP request header named `X-CSRF-TOKEN` or the request parameter `_csrf` by default.
|
||||
|
||||
@@ -197,7 +197,7 @@ XML::
|
||||
[[csrf-token-repository-cookie]]
|
||||
=== Using the `CookieCsrfTokenRepository`
|
||||
|
||||
You can persist the `CsrfToken` in a cookie to <<csrf-integration-javascript,support a JavaScript-based application>> using the {security-api-url}org/springframework/security/web/csrf/CookieCsrfTokenRepository.html[`CookieCsrfTokenRepository`].
|
||||
You can persist the `CsrfToken` in a cookie to <<csrf-integration-javascript,support a JavaScript-based application>> using the javadoc:org.springframework.security.web.csrf.CookieCsrfTokenRepository[].
|
||||
|
||||
The `CookieCsrfTokenRepository` writes to a cookie named `XSRF-TOKEN` and reads it from an HTTP request header named `X-XSRF-TOKEN` or the request parameter `_csrf` by default.
|
||||
These defaults come from Angular and its predecessor https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS].
|
||||
@@ -280,7 +280,7 @@ If you do not need the ability to read the cookie with JavaScript directly, we _
|
||||
[[csrf-token-repository-custom]]
|
||||
=== Customizing the `CsrfTokenRepository`
|
||||
|
||||
There can be cases where you want to implement a custom {security-api-url}org/springframework/security/web/csrf/CsrfTokenRepository.html[`CsrfTokenRepository`].
|
||||
There can be cases where you want to implement a custom javadoc:org.springframework.security.web.csrf.CsrfTokenRepository[].
|
||||
|
||||
Once you've implemented the `CsrfTokenRepository` interface, you can configure Spring Security to use it with the following configuration:
|
||||
|
||||
@@ -708,7 +708,7 @@ The following view technologies automatically include the actual CSRF token in a
|
||||
|
||||
* https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library]
|
||||
* https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymeleaf]
|
||||
* Any other view technology that integrates with {spring-framework-api-url}org/springframework/web/servlet/support/RequestDataValueProcessor.html[`RequestDataValueProcessor`] (via {security-api-url}org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[`CsrfRequestDataValueProcessor`])
|
||||
* Any other view technology that integrates with {spring-framework-api-url}org/springframework/web/servlet/support/RequestDataValueProcessor.html[`RequestDataValueProcessor`] (via javadoc:org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor[])
|
||||
* You can also include the token yourself via the xref:servlet/integrations/jsp-taglibs.adoc#taglibs-csrfinput[csrfInput] tag
|
||||
|
||||
If these options are not available, you can take advantage of the fact that the `CsrfToken` is exposed as an <<csrf-token-request-handler,`HttpServletRequest` attribute named `_csrf`>>.
|
||||
@@ -763,7 +763,7 @@ Spring Security defers loading a new CSRF token by default, and additional work
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token.
|
||||
Refreshing the token after authentication success and logout success is required because the javadoc:org.springframework.security.web.csrf.CsrfAuthenticationStrategy[] and javadoc:org.springframework.security.web.csrf.CsrfLogoutHandler[] will clear the previous token.
|
||||
The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token.
|
||||
====
|
||||
|
||||
@@ -788,14 +788,14 @@ public class SecurityConfig {
|
||||
.csrf((csrf) -> csrf
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // <1>
|
||||
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) // <2>
|
||||
)
|
||||
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // <3>
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
|
||||
private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
|
||||
final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
|
||||
private final CsrfTokenRequestHandler plain = new CsrfTokenRequestAttributeHandler();
|
||||
private final CsrfTokenRequestHandler xor = new XorCsrfTokenRequestAttributeHandler();
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
|
||||
@@ -803,40 +803,28 @@ final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler
|
||||
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
||||
* the CsrfToken when it is rendered in the response body.
|
||||
*/
|
||||
this.delegate.handle(request, response, csrfToken);
|
||||
this.xor.handle(request, response, csrfToken);
|
||||
/*
|
||||
* Render the token value to a cookie by causing the deferred token to be loaded.
|
||||
*/
|
||||
csrfToken.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
||||
String headerValue = request.getHeader(csrfToken.getHeaderName());
|
||||
/*
|
||||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||||
* the header value automatically, which was obtained via a cookie containing the
|
||||
* raw CsrfToken.
|
||||
*/
|
||||
if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
|
||||
return super.resolveCsrfTokenValue(request, csrfToken);
|
||||
}
|
||||
/*
|
||||
*
|
||||
* In all other cases (e.g. if the request contains a request parameter), use
|
||||
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
||||
* when a server-side rendered form includes the _csrf request parameter as a
|
||||
* hidden input.
|
||||
*/
|
||||
return this.delegate.resolveCsrfTokenValue(request, csrfToken);
|
||||
}
|
||||
}
|
||||
|
||||
final class CsrfCookieFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
|
||||
// Render the token value to a cookie by causing the deferred token to be loaded
|
||||
csrfToken.getToken();
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken);
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -856,35 +844,40 @@ class SecurityConfig {
|
||||
http {
|
||||
// ...
|
||||
csrf {
|
||||
csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() // <1>
|
||||
csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2>
|
||||
csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() // <1>
|
||||
csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2>
|
||||
}
|
||||
}
|
||||
http.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) // <3>
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
|
||||
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
||||
private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
|
||||
class SpaCsrfTokenRequestHandler : CsrfTokenRequestHandler {
|
||||
private val plain: CsrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
|
||||
private val xor: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
|
||||
|
||||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier<CsrfToken>) {
|
||||
/*
|
||||
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
||||
* the CsrfToken when it is rendered in the response body.
|
||||
*/
|
||||
delegate.handle(request, response, csrfToken)
|
||||
xor.handle(request, response, csrfToken)
|
||||
/*
|
||||
* Render the token value to a cookie by causing the deferred token to be loaded.
|
||||
*/
|
||||
csrfToken.get()
|
||||
}
|
||||
|
||||
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? {
|
||||
val headerValue = request.getHeader(csrfToken.headerName)
|
||||
/*
|
||||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||||
* the header value automatically, which was obtained via a cookie containing the
|
||||
* raw CsrfToken.
|
||||
*/
|
||||
return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) {
|
||||
super.resolveCsrfTokenValue(request, csrfToken)
|
||||
return if (StringUtils.hasText(headerValue)) {
|
||||
plain
|
||||
} else {
|
||||
/*
|
||||
* In all other cases (e.g. if the request contains a request parameter), use
|
||||
@@ -892,19 +885,8 @@ class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
||||
* when a server-side rendered form includes the _csrf request parameter as a
|
||||
* hidden input.
|
||||
*/
|
||||
delegate.resolveCsrfTokenValue(request, csrfToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CsrfCookieFilter : OncePerRequestFilter() {
|
||||
|
||||
@Throws(ServletException::class, IOException::class)
|
||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
||||
val csrfToken = request.getAttribute("_csrf") as CsrfToken
|
||||
// Render the token value to a cookie by causing the deferred token to be loaded
|
||||
csrfToken.token
|
||||
filterChain.doFilter(request, response)
|
||||
xor
|
||||
}.resolveCsrfTokenValue(request, csrfToken)
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -916,23 +898,20 @@ XML::
|
||||
<http>
|
||||
<!-- ... -->
|
||||
<csrf
|
||||
token-repository-ref="tokenRepository" <1>
|
||||
request-handler-ref="requestHandler"/> <2>
|
||||
<custom-filter ref="csrfCookieFilter" after="BASIC_AUTH_FILTER"/> <3>
|
||||
token-repository-ref="tokenRepository" <1>
|
||||
request-handler-ref="requestHandler"/> <2>
|
||||
</http>
|
||||
<b:bean id="tokenRepository"
|
||||
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
||||
p:cookieHttpOnly="false"/>
|
||||
<b:bean id="requestHandler"
|
||||
class="example.SpaCsrfTokenRequestHandler"/>
|
||||
<b:bean id="csrfCookieFilter"
|
||||
class="example.CsrfCookieFilter"/>
|
||||
----
|
||||
======
|
||||
|
||||
<1> Configure `CookieCsrfTokenRepository` with `HttpOnly` set to `false` so the cookie can be read by the JavaScript application.
|
||||
<2> Configure a custom `CsrfTokenRequestHandler` that resolves the CSRF token based on whether it is an HTTP request header (`X-XSRF-TOKEN`) or request parameter (`_csrf`).
|
||||
<3> Configure a custom `Filter` to load the `CsrfToken` on every request, which will return a new cookie if needed.
|
||||
This implementation also causes the deferred `CsrfToken` to be loaded on every request, which will return a new cookie if needed.
|
||||
|
||||
[[csrf-integration-javascript-mpa]]
|
||||
==== Multi-Page Applications
|
||||
@@ -1109,7 +1088,7 @@ This endpoint should be called to obtain a CSRF token when the application is la
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token.
|
||||
Refreshing the token after authentication success and logout success is required because the javadoc:org.springframework.security.web.csrf.CsrfAuthenticationStrategy[] and javadoc:org.springframework.security.web.csrf.CsrfLogoutHandler[] will clear the previous token.
|
||||
The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token.
|
||||
====
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ The strategy is implemented in the class `AntPathRequestMatcher`, which uses Spr
|
||||
|
||||
If you need a more powerful matching strategy, you can use regular expressions.
|
||||
The strategy implementation is then `RegexRequestMatcher`.
|
||||
See the {security-api-url}/org/springframework/security/web/util/matcher/RegexRequestMatcher.html[Javadoc for this class] for more information.
|
||||
See the javadoc:org.springframework.security.web.util.matcher.RegexRequestMatcher[] Javadoc for more information.
|
||||
|
||||
In practice, we recommend that you use method security at your service layer, to control access to your application, rather than rely entirely on the use of security constraints defined at the web-application level.
|
||||
URLs change, and it is difficult to take into account all the possible URLs that an application might support and how requests might be manipulated.
|
||||
|
||||
@@ -153,12 +153,12 @@ This means that we are running our `Runnable` with the same user that was used t
|
||||
See the {security-api-url}index.html[Javadoc] for additional integrations with both the Java concurrent APIs and the Spring Task abstractions.
|
||||
They are self-explanatory once you understand the previous code.
|
||||
|
||||
* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextCallable.html[`DelegatingSecurityContextCallable`]
|
||||
* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextExecutor.html[`DelegatingSecurityContextExecutor`]
|
||||
* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextExecutorService.html[`DelegatingSecurityContextExecutorService`]
|
||||
* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextRunnable.html[`DelegatingSecurityContextRunnable`]
|
||||
* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextScheduledExecutorService.html[`DelegatingSecurityContextScheduledExecutorService`]
|
||||
* {security-api-url}org/springframework/security/scheduling/DelegatingSecurityContextSchedulingTaskExecutor.html[`DelegatingSecurityContextSchedulingTaskExecutor`]
|
||||
* {security-api-url}org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutor.html[`DelegatingSecurityContextAsyncTaskExecutor`]
|
||||
* {security-api-url}org/springframework/security/task/DelegatingSecurityContextTaskExecutor.html[`DelegatingSecurityContextTaskExecutor`]
|
||||
* {security-api-url}org/springframework/security/scheduling/DelegatingSecurityContextTaskScheduler.html[`DelegatingSecurityContextTaskScheduler`]
|
||||
* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextCallable[]
|
||||
* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextExecutor[]
|
||||
* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextExecutorService[]
|
||||
* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextRunnable[]
|
||||
* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService[]
|
||||
* javadoc:org.springframework.security.scheduling.DelegatingSecurityContextSchedulingTaskExecutor[]
|
||||
* javadoc:org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor[]
|
||||
* javadoc:org.springframework.security.task.DelegatingSecurityContextTaskExecutor[]
|
||||
* javadoc:org.springframework.security.scheduling.DelegatingSecurityContextTaskScheduler[]
|
||||
|
||||
@@ -23,8 +23,8 @@ String json = mapper.writeValueAsString(context);
|
||||
====
|
||||
The following Spring Security modules provide Jackson support:
|
||||
|
||||
- spring-security-core ({security-api-url}org/springframework/security/jackson2/CoreJackson2Module.html[`CoreJackson2Module`])
|
||||
- spring-security-web ({security-api-url}org/springframework/security/web/jackson2/WebJackson2Module.html[`WebJackson2Module`], {security-api-url}org/springframework/security/web/jackson2/WebServletJackson2Module.html[`WebServletJackson2Module`], {security-api-url}org/springframework/security/web/server/jackson2/WebServerJackson2Module.html[`WebServerJackson2Module`])
|
||||
- <<oauth2client, spring-security-oauth2-client>> ({security-api-url}org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.html[`OAuth2ClientJackson2Module`])
|
||||
- spring-security-cas ({security-api-url}org/springframework/security/cas/jackson2/CasJackson2Module.html[`CasJackson2Module`])
|
||||
- spring-security-core (javadoc:org.springframework.security.jackson2.CoreJackson2Module[])
|
||||
- spring-security-web (javadoc:org.springframework.security.web.jackson2.WebJackson2Module[], javadoc:org.springframework.security.web.jackson2.WebServletJackson2Module[], javadoc:org.springframework.security.web.server.jackson2.WebServerJackson2Module[])
|
||||
- <<oauth2client, spring-security-oauth2-client>> (javadoc:org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module[])
|
||||
- spring-security-cas (javadoc:org.springframework.security.cas.jackson2.CasJackson2Module[])
|
||||
====
|
||||
|
||||
@@ -105,7 +105,7 @@ Xml::
|
||||
== Bearer Token Propagation
|
||||
|
||||
Now that your resource server has validated the token, it might be handy to pass it to downstream services.
|
||||
This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServletBearerExchangeFilterFunction.html[ServletBearerExchangeFilterFunction]`, which you can see in the following example:
|
||||
This is quite simple with javadoc:org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServletBearerExchangeFilterFunction[], which you can see in the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -134,7 +134,7 @@ fun rest(): WebClient {
|
||||
----
|
||||
======
|
||||
|
||||
When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential.
|
||||
When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any javadoc:org.springframework.security.oauth2.core.AbstractOAuth2Token[] credential.
|
||||
Then, it will propagate that token in the `Authorization` header.
|
||||
|
||||
For example:
|
||||
@@ -198,7 +198,7 @@ this.rest.get()
|
||||
In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain.
|
||||
|
||||
[NOTE]
|
||||
Unlike the {security-api-url}org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired.
|
||||
Unlike the javadoc:org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired.
|
||||
To obtain this level of support, please use the OAuth 2.0 Client filter.
|
||||
|
||||
=== `RestTemplate` support
|
||||
@@ -259,7 +259,7 @@ fun rest(): RestTemplate {
|
||||
|
||||
|
||||
[NOTE]
|
||||
Unlike the {security-api-url}org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.html[OAuth 2.0 Authorized Client Manager], this filter interceptor makes no attempt to renew the token, should it be expired.
|
||||
Unlike the javadoc:org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager[OAuth 2.0 Authorized Client Manager], this filter interceptor makes no attempt to renew the token, should it be expired.
|
||||
To obtain this level of support, please create an interceptor using the xref:servlet/oauth2/client/index.adoc#oauth2client[OAuth 2.0 Authorized Client Manager].
|
||||
|
||||
[[oauth2resourceserver-bearertoken-failure]]
|
||||
|
||||
@@ -30,7 +30,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to
|
||||
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`.
|
||||
|
||||
image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates _Start Authentication_.
|
||||
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationEntryPoint.html[`BearerTokenAuthenticationEntryPoint`], which sends a `WWW-Authenticate` header.
|
||||
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of javadoc:org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint[], which sends a `WWW-Authenticate` header.
|
||||
The `RequestCache` is typically a `NullRequestCache` that does not save the request, since the client is capable of replaying the requests it originally requested.
|
||||
|
||||
When a client receives the `WWW-Authenticate: Bearer` header, it knows it should retry with a bearer token.
|
||||
|
||||
@@ -86,7 +86,7 @@ From here, consider jumping to:
|
||||
|
||||
Next, let's see the architectural components that Spring Security uses to support https://tools.ietf.org/html/rfc7519[JWT] Authentication in servlet-based applications, like the one we just saw.
|
||||
|
||||
{security-api-url}org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.html[`JwtAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <<oauth2resourceserver-jwt-decoder,`JwtDecoder`>> and <<oauth2resourceserver-jwt-authorization-extraction,`JwtAuthenticationConverter`>> to authenticate a JWT.
|
||||
javadoc:org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider[] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <<oauth2resourceserver-jwt-decoder,`JwtDecoder`>> and <<oauth2resourceserver-jwt-authorization-extraction,`JwtAuthenticationConverter`>> to authenticate a JWT.
|
||||
|
||||
Let's take a look at how `JwtAuthenticationProvider` works within Spring Security.
|
||||
The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from <<oauth2resourceserver-authentication-bearertokenauthenticationfilter,Reading the Bearer Token>> works.
|
||||
@@ -293,7 +293,7 @@ fun jwtDecoder(): JwtDecoder {
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Calling `{security-api-url}org/springframework/security/oauth2/jwt/JwtDecoders.html#fromIssuerLocation-java.lang.String-[JwtDecoders#fromIssuerLocation]` is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri.
|
||||
Calling javadoc:org.springframework.security.oauth2.jwt.JwtDecoders#fromIssuerLocation-java.lang.String-[JwtDecoders#fromIssuerLocation] is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri.
|
||||
|
||||
If the application doesn't expose a `JwtDecoder` bean, then Spring Boot will expose the above default one.
|
||||
|
||||
@@ -879,7 +879,9 @@ public class DirectlyConfiguredJwkSetUri {
|
||||
.requestMatchers("/messages/**").access(hasScope("messages"))
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
.jwt(Customizer.withDefaults())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtValidator) {
|
||||
NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor);
|
||||
NimbusJwtDecoder decoder = new NimbusJwtDecoder(jwtProcessor);
|
||||
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>
|
||||
(JwtValidators.createDefault(), jwtValidator);
|
||||
decoder.setJwtValidator(validator);
|
||||
|
||||
@@ -68,7 +68,7 @@ Given an Opaque Token, Resource Server will
|
||||
2. Inspect the response for an `{ 'active' : true }` attribute
|
||||
3. Map each scope to an authority with the prefix `SCOPE_`
|
||||
|
||||
The resulting `Authentication#getPrincipal`, by default, is a Spring Security `{security-api-url}org/springframework/security/oauth2/core/OAuth2AuthenticatedPrincipal.html[OAuth2AuthenticatedPrincipal]` object, and `Authentication#getName` maps to the token's `sub` property, if one is present.
|
||||
The resulting `Authentication#getPrincipal`, by default, is a Spring Security javadoc:org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal[] object, and `Authentication#getName` maps to the token's `sub` property, if one is present.
|
||||
|
||||
From here, you may want to jump to:
|
||||
|
||||
@@ -82,7 +82,7 @@ From here, you may want to jump to:
|
||||
|
||||
Next, let's see the architectural components that Spring Security uses to support https://tools.ietf.org/html/rfc7662[opaque token] Authentication in servlet-based applications, like the one we just saw.
|
||||
|
||||
{security-api-url}org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.html[`OpaqueTokenAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <<oauth2resourceserver-opaque-introspector,`OpaqueTokenIntrospector`>> to authenticate an opaque token.
|
||||
javadoc:org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider[] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <<oauth2resourceserver-opaque-introspector,`OpaqueTokenIntrospector`>> to authenticate an opaque token.
|
||||
|
||||
Let's take a look at how `OpaqueTokenAuthenticationProvider` works within Spring Security.
|
||||
The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from <<oauth2resourceserver-authentication-bearertokenauthenticationfilter,Reading the Bearer Token>> works.
|
||||
@@ -204,7 +204,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
.opaqueToken(Customizer.withDefaults())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
@@ -564,7 +566,9 @@ public class MappedAuthorities {
|
||||
.requestMatchers("/messages/**").access(hasScope("messages"))
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
.opaqueToken(Customizer.withDefaults())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
As stated earlier, Spring Security's SAML 2.0 support produces a `<saml2:AuthnRequest>` to commence authentication with the asserting party.
|
||||
|
||||
Spring Security achieves this in part by registering the `Saml2WebSsoAuthenticationRequestFilter` in the filter chain.
|
||||
This filter by default responds to endpoint `+/saml2/authenticate/{registrationId}+`.
|
||||
This filter by default responds to the endpoints `+/saml2/authenticate/{registrationId}+` and `+/saml2/authenticate?registrationId={registrationId}+`.
|
||||
|
||||
For example, if you were deployed to `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to:
|
||||
|
||||
@@ -12,6 +12,42 @@ For example, if you were deployed to `https://rp.example.com` and you gave your
|
||||
|
||||
and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
|
||||
|
||||
== Configuring the `<saml2:AuthnRequest>` Endpoint
|
||||
|
||||
To configure the endpoint differently from the default, you can set the value in `saml2Login`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
.saml2Login((saml2) -> saml2
|
||||
.authenticationRequestUriQuery("/custom/auth/sso?peerEntityID={registrationId}")
|
||||
);
|
||||
return new CustomSaml2AuthenticationRequestRepository();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
saml2Login {
|
||||
authenticationRequestUriQuery = "/custom/auth/sso?peerEntityID={registrationId}"
|
||||
}
|
||||
}
|
||||
return CustomSaml2AuthenticationRequestRepository()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[servlet-saml2login-store-authn-request]]
|
||||
== Changing How the `<saml2:AuthnRequest>` Gets Stored
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to
|
||||
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`.
|
||||
|
||||
image:{icondir}/number_3.png[] Since the user lacks authorization, the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates _Start Authentication_.
|
||||
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`], which redirects to <<servlet-saml2login-sp-initiated-factory,the `<saml2:AuthnRequest>` generating endpoint>>, `Saml2WebSsoAuthenticationRequestFilter`.
|
||||
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of javadoc:org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint[], which redirects to <<servlet-saml2login-sp-initiated-factory,the `<saml2:AuthnRequest>` generating endpoint>>, `Saml2WebSsoAuthenticationRequestFilter`.
|
||||
Alternatively, if you have <<servlet-saml2login-relyingpartyregistrationrepository,configured more than one asserting party>>, it first redirects to a picker page.
|
||||
|
||||
image:{icondir}/number_4.png[] Next, the `Saml2WebSsoAuthenticationRequestFilter` creates, signs, serializes, and encodes a `<saml2:AuthnRequest>` using its configured <<servlet-saml2login-sp-initiated-factory,`Saml2AuthenticationRequestFactory`>>.
|
||||
@@ -418,7 +418,7 @@ class MyCustomSecurityConfiguration {
|
||||
The preceding example requires the role of `USER` for any URL that starts with `/messages/`.
|
||||
|
||||
[[servlet-saml2login-relyingpartyregistrationrepository]]
|
||||
The second `@Bean` Spring Boot creates is a {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html[`RelyingPartyRegistrationRepository`], which represents the asserting party and relying party metadata.
|
||||
The second `@Bean` Spring Boot creates is a javadoc:org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository[], which represents the asserting party and relying party metadata.
|
||||
This includes such things as the location of the SSO endpoint the relying party should use when requesting authentication from the asserting party.
|
||||
|
||||
You can override the default by publishing your own `RelyingPartyRegistrationRepository` bean.
|
||||
@@ -588,9 +588,60 @@ class MyCustomSecurityConfiguration {
|
||||
A relying party can be multi-tenant by registering more than one relying party in the `RelyingPartyRegistrationRepository`.
|
||||
====
|
||||
|
||||
[[servlet-saml2login-relyingpartyregistrationrepository-caching]]
|
||||
If you want your metadata to be refreshable on a periodic basis, you can wrap your repository in `CachingRelyingPartyRegistrationRepository` like so:
|
||||
|
||||
.Caching Relying Party Registration Repository
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class MyCustomSecurityConfiguration {
|
||||
@Bean
|
||||
public RelyingPartyRegistrationRepository registrations(CacheManager cacheManager) {
|
||||
Supplier<IterableRelyingPartyRegistrationRepository> delegate = () ->
|
||||
new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations
|
||||
.fromMetadataLocation("https://idp.example.org/ap/metadata")
|
||||
.registrationId("ap").build());
|
||||
CachingRelyingPartyRegistrationRepository registrations =
|
||||
new CachingRelyingPartyRegistrationRepository(delegate);
|
||||
registrations.setCache(cacheManager.getCache("my-cache-name"));
|
||||
return registrations;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class MyCustomSecurityConfiguration {
|
||||
@Bean
|
||||
fun registrations(cacheManager: CacheManager): RelyingPartyRegistrationRepository {
|
||||
val delegate = Supplier<IterableRelyingPartyRegistrationRepository> {
|
||||
InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations
|
||||
.fromMetadataLocation("https://idp.example.org/ap/metadata")
|
||||
.registrationId("ap").build())
|
||||
}
|
||||
val registrations = CachingRelyingPartyRegistrationRepository(delegate)
|
||||
registrations.setCache(cacheManager.getCache("my-cache-name"))
|
||||
return registrations
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In this way, the set of `RelyingPartyRegistration`s will refresh based on {spring-framework-reference-url}integration/cache/store-configuration.html[the cache's eviction schedule].
|
||||
|
||||
[[servlet-saml2login-relyingpartyregistration]]
|
||||
== RelyingPartyRegistration
|
||||
A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`]
|
||||
A javadoc:org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration[]
|
||||
instance represents a link between an relying party and an asserting party's metadata.
|
||||
|
||||
In a `RelyingPartyRegistration`, you can provide relying party metadata like its `Issuer` value, where it expects SAML Responses to be sent to, and any credentials that it owns for the purposes of signing or decrypting payloads.
|
||||
|
||||
@@ -189,28 +189,28 @@ Next, let's see the architectural components that Spring Security uses to suppor
|
||||
For RP-initiated logout:
|
||||
|
||||
image:{icondir}/number_1.png[] Spring Security executes its xref:servlet/authentication/logout.adoc#logout-architecture[logout flow], calling its ``LogoutHandler``s to invalidate the session and perform other cleanup.
|
||||
It then invokes the {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2RelyingPartyInitiatedLogoutSuccessHandler.html[`Saml2RelyingPartyInitiatedLogoutSuccessHandler`].
|
||||
It then invokes the javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler[].
|
||||
|
||||
image:{icondir}/number_2.png[] The logout success handler uses an instance of
|
||||
{security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestResolver.html[`Saml2LogoutRequestResolver`] to create, sign, and serialize a `<saml2:LogoutRequest>`.
|
||||
javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver[] to create, sign, and serialize a `<saml2:LogoutRequest>`.
|
||||
It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] that is associated with the current `Saml2AuthenticatedPrincipal`.
|
||||
Then, it redirect-POSTs the `<saml2:LogoutRequest>` to the asserting party SLO endpoint
|
||||
|
||||
The browser hands control over to the asserting party.
|
||||
If the asserting party redirects back (which it may not), then the application proceeds to step image:{icondir}/number_3.png[].
|
||||
|
||||
image:{icondir}/number_3.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.html[`Saml2LogoutResponseFilter`] deserializes, verifies, and processes the `<saml2:LogoutResponse>` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutResponseValidator.html[`Saml2LogoutResponseValidator`].
|
||||
image:{icondir}/number_3.png[] The javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter[] deserializes, verifies, and processes the `<saml2:LogoutResponse>` with its javadoc:org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator[].
|
||||
|
||||
image:{icondir}/number_4.png[] If valid, then it completes the local logout flow by redirecting to `/login?logout`, or whatever has been configured.
|
||||
If invalid, then it responds with a 400.
|
||||
|
||||
For AP-initiated logout:
|
||||
|
||||
image:{icondir}/number_1.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.html[`Saml2LogoutRequestFilter`] deserializes, verifies, and processes the `<saml2:LogoutRequest>` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutRequestValidator.html[`Saml2LogoutRequestValidator`].
|
||||
image:{icondir}/number_1.png[] The javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter[] deserializes, verifies, and processes the `<saml2:LogoutRequest>` with its javadoc:org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator[].
|
||||
|
||||
image:{icondir}/number_2.png[] If valid, then the filter calls the configured ``LogoutHandler``s, invalidating the session and performing other cleanup.
|
||||
|
||||
image:{icondir}/number_3.png[] It uses a {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.html[`Saml2LogoutResponseResolver`] to create, sign and serialize a `<saml2:LogoutResponse>`.
|
||||
image:{icondir}/number_3.png[] It uses a javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver[] to create, sign and serialize a `<saml2:LogoutResponse>`.
|
||||
It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] derived from the endpoint or from the contents of the `<saml2:LogoutRequest>`.
|
||||
Then, it redirect-POSTs the `<saml2:LogoutResponse>` to the asserting party SLO endpoint.
|
||||
|
||||
|
||||
@@ -964,7 +964,7 @@ mvc.get("/endpoint") {
|
||||
----
|
||||
======
|
||||
|
||||
You can also specify a complete `Jwt`, for which `{security-api-url}org/springframework/security/oauth2/jwt/Jwt.Builder.html[Jwt.Builder]` comes quite handy:
|
||||
You can also specify a complete `Jwt`, for which javadoc:org.springframework.security.oauth2.jwt.Jwt$Builder[] comes quite handy:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -44,7 +44,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [SecurityConfig::class])
|
||||
@WebAppConfiguration
|
||||
class CsrfShowcaseTests {
|
||||
@@ -52,7 +52,7 @@ class CsrfShowcaseTests {
|
||||
@Autowired
|
||||
private lateinit var context: WebApplicationContext
|
||||
|
||||
private var mvc: MockMvc? = null
|
||||
private lateinit var mvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
|
||||
@@ -1,307 +1,9 @@
|
||||
[[new]]
|
||||
= What's New in Spring Security 6.3
|
||||
= What's New in Spring Security 6.4
|
||||
|
||||
Spring Security 6.3 provides a number of new features.
|
||||
Spring Security 6.4 provides a number of new features.
|
||||
Below are the highlights of the release, or you can view https://github.com/spring-projects/spring-security/releases[the release notes] for a detailed listing of each feature and bug fix.
|
||||
|
||||
== Passive JDK Serialization Support
|
||||
- https://github.com/spring-projects/spring-security/issues/4186[gh-4186] - Support `RoleHierarchy` in `AclAuthorizationStrategyImpl`
|
||||
- https://github.com/spring-projects/spring-security/issues/15136[gh-15136] - Support `RoleHierarchy` Bean in `authorizeHttpRequests` Kotlin DSL
|
||||
|
||||
When it comes to its support for JDK-serialized security components, Spring Security has historically been quite aggressive, supporting each serialization version for only one Spring Security minor version.
|
||||
This meant that if you had JDK-serialized security components, then they would need to be evacuated before upgrading to the next Spring Security version since they would no longer be deserializable.
|
||||
|
||||
Now that Spring Security performs a minor release every six months, this became a much larger pain point.
|
||||
To address that, Spring Security now will https://spring.io/blog/2024/01/19/spring-security-6-3-adds-passive-jdk-serialization-deserialization-for[maintain passivity with JDK serialization], like it does with JSON serialization, making for more seamless upgrades.
|
||||
|
||||
== Authorization
|
||||
|
||||
An ongoing theme for the last several releases has been to refactor and improve Spring Security's authorization subsystem.
|
||||
Starting with replacing the `AccessDecisionManager` API with `AuthorizationManager` it's now come to the point where we are able to add several exciting new features.
|
||||
|
||||
=== Annotation Parameters - https://github.com/spring-projects/spring-security/issues/14480[#14480]
|
||||
|
||||
The first 6.3 feature is https://github.com/spring-projects/spring-security/issues/14480[support for annotation parameters].
|
||||
Consider Spring Security's support for xref:servlet/authorization/method-security.adoc#meta-annotations[meta-annotations] like this one:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_message:read')")
|
||||
public @interface HasMessageRead {}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_message:read')")
|
||||
annotation class HasMessageRead
|
||||
----
|
||||
======
|
||||
|
||||
Before this release, something like this is only helpful when it is used widely across the codebase.
|
||||
But now, xref:servlet/authorization/method-security.adoc#_templating_meta_annotation_expressions[you can add parameters] like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_{scope}')")
|
||||
public @interface HasScope {
|
||||
String scope();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_{scope}')")
|
||||
annotation class HasScope (val scope:String)
|
||||
----
|
||||
======
|
||||
|
||||
making it possible to do things like this:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@HasScope("message:read")
|
||||
public String method() { ... }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@HasScope("message:read")
|
||||
fun method(): String { ... }
|
||||
----
|
||||
======
|
||||
|
||||
and apply your SpEL expression in several more places.
|
||||
|
||||
=== Secure Return Values - https://github.com/spring-projects/spring-security/issues/14596[#14596], https://github.com/spring-projects/spring-security/issues/14597[#14597]
|
||||
|
||||
Since the early days of Spring Security, you've been able to xref:servlet/authorization/method-security.adoc#use-preauthorize[annotate Spring beans with `@PreAuthorize` and `@PostAuthorize`].
|
||||
But controllers, services, and repositories are not the only things you care to secure.
|
||||
For example, what about a domain object `Order` where only admins should be able to call the `Order#getPayment` method?
|
||||
|
||||
Now in 6.3, https://github.com/spring-projects/spring-security/issues/14597[you can annotate those methods], too.
|
||||
First, annotate the `getPayment` method like you would a Spring bean:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class Order {
|
||||
|
||||
@HasScope("payment:read")
|
||||
Payment getPayment() { ... }
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class Order {
|
||||
|
||||
@HasScope("payment:read")
|
||||
fun getPayment(): Payment { ... }
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
And then xref:servlet/authorization/method-security.adoc#authorize-object[annotate your Spring Data repository with `@AuthorizeReturnObject`] like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface OrderRepository implements CrudRepository<Order, String> {
|
||||
|
||||
@AuthorizeReturnObject
|
||||
Optional<Order> findOrderById(String id);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
|
||||
interface OrderRepository : CrudRepository<Order, String> {
|
||||
@AuthorizeReturnObject
|
||||
fun findOrderById(id: String?): Optional<Order?>?
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
At that point, Spring Security will protect any `Order` returned from `findOrderById` by way of https://github.com/spring-projects/spring-security/issues/14596[proxying the `Order` instance].
|
||||
|
||||
=== Error Handling - https://github.com/spring-projects/spring-security/issues/14598[#14598], https://github.com/spring-projects/spring-security/issues/14600[#14600], https://github.com/spring-projects/spring-security/issues/14601[#14601]
|
||||
|
||||
In this release, you can also https://github.com/spring-projects/spring-security/issues/14601[intercept and handle failure at the method level] with its last new method security annotation.
|
||||
|
||||
When you xref:servlet/authorization/method-security.adoc#fallback-values-authorization-denied[annotate a method with `@HandleAuthorizationDenied`] like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class Payment {
|
||||
@HandleAuthorizationDenied(handlerClass=Mask.class)
|
||||
@PreAuthorize("hasAuthority('card:read')")
|
||||
public String getCreditCardNumber() { ... }
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class Payment {
|
||||
@HandleAuthorizationDenied(handlerClass=Mask.class)
|
||||
@PreAuthorize("hasAuthority('card:read')")
|
||||
fun getCreditCardNumber(): String { ... }
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
and publish a `Mask` bean:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class Mask implements MethodAuthorizationDeniedHandler {
|
||||
@Override
|
||||
public Object handleDeniedInvocation(MethodInvocation invocation, AuthorizationResult result) {
|
||||
return "***";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class Mask : MethodAuthorizationDeniedHandler {
|
||||
fun handleDeniedInvocation(invocation: MethodInvocation?, result: AuthorizationResult?): Any = "***"
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
then any unauthorized call to `Payment#getCreditCardNumber` will return `\***` instead of the number.
|
||||
|
||||
You can see all these features at work together in https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/data[the latest Spring Security Data sample].
|
||||
|
||||
== Compromised Password Checking - https://github.com/spring-projects/spring-security/issues/7395[#7395]
|
||||
|
||||
If you are going to let users pick passwords, it's critical to ensure that such a password isn't already compromised.
|
||||
Spring Security 6.3 makes this as simple as xref:features/authentication/password-storage.adoc#authentication-compromised-password-check[publishing a `CompromisedPasswordChecker` bean]:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public CompromisedPasswordChecker compromisedPasswordChecker() {
|
||||
return new HaveIBeenPwnedRestApiPasswordChecker();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun compromisedPasswordChecker(): CompromisedPasswordChecker = HaveIBeenPwnedRestApiPasswordChecker()
|
||||
----
|
||||
======
|
||||
|
||||
== `spring-security-rsa` is now part of Spring Security - https://github.com/spring-projects/spring-security/issues/14202[#14202]
|
||||
|
||||
Since 2017, Spring Security has been undergoing a long-standing initiative to fold various Spring Security extensions into Spring Security proper.
|
||||
In 6.3, `spring-security-rsa` becomes the latest of these projects which will help the team maintain and add features to it, long-term.
|
||||
|
||||
`spring-security-rsa` provides a number of https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java[handy `BytesEncryptor`] https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java[implementations] as well as https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java[a simpler API for working with ``KeyStore``s].
|
||||
|
||||
|
||||
== OAuth 2.0 Token Exchange Grant - https://github.com/spring-projects/spring-security/issues/5199[#5199]
|
||||
|
||||
One of https://github.com/spring-projects/spring-security/issues/5199[the most highly-voted OAuth 2.0 features] in Spring Security is now in place in 6.3, which is the support for https://datatracker.ietf.org/doc/html/rfc8693#section-2[the OAuth 2.0 Token Exchange grant].
|
||||
|
||||
For xref:servlet/oauth2/client/authorization-grants.adoc#token-exchange-grant-access-token[any client configured for token exchange], you can activate it in Spring Security by adding a `TokenExchangeAuthorizedClientProvider` instance to your `OAuth2AuthorizedClientManager` like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2AuthorizedClientProvider tokenExchange() {
|
||||
return new TokenExchangeOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun tokenExchange(): OAuth2AuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
|
||||
----
|
||||
======
|
||||
|
||||
and then xref:servlet/oauth2/client/authorized-clients.adoc#oauth2Client-registered-authorized-client[use the `@RegisteredOAuth2AuthorizedClient` annotation] as per usual to retrieve the appropriate token with the expanded privileges your resource server needs.
|
||||
|
||||
== Additional Highlights
|
||||
|
||||
- https://github.com/spring-projects/spring-security/pull/14655[gh-14655] - Add `DelegatingAuthenticationConverter`
|
||||
- https://github.com/spring-projects/spring-security/issues/6192[gh-6192] - Add Concurrent Sessions Control on WebFlux (xref:reactive/authentication/concurrent-sessions-control.adoc[docs])
|
||||
- https://github.com/spring-projects/spring-security/pull/14193[gh-14193] - Added support for CAS Gateway Authentication
|
||||
- https://github.com/spring-projects/spring-security/issues/13259[gh-13259] - Customize when UserInfo is called
|
||||
- https://github.com/spring-projects/spring-security/pull/14168[gh-14168] - Introduce Customizable AuthorizationFailureHandler in OAuth2AuthorizationRequestRedirectFilter
|
||||
- https://github.com/spring-projects/spring-security/issues/14672[gh-14672] - Customize mapping the OidcUser from OidcUserRequest and OidcUserInfo
|
||||
- https://github.com/spring-projects/spring-security/issues/13763[gh-13763] - Simplify configuration of reactive OAuth2 Client component model
|
||||
- https://github.com/spring-projects/spring-security/issues/14758[gh-14758] - Update reactive OAuth2 docs landing page with examples (xref:reactive/oauth2/index.adoc[docs])
|
||||
- https://github.com/spring-projects/spring-security/issues/10538[gh-10538] - Support Certificate-Bound JWT Access Token Validation
|
||||
- https://github.com/spring-projects/spring-security/pull/14265[gh-14265] - Support Nested username in UserInfo response
|
||||
- https://github.com/spring-projects/spring-security/pull/14265[gh-14449] - Add `SecurityContext` argument resolver
|
||||
- https://github.com/spring-projects/spring-security/issues/11440[gh-11440] - Simplify Disabling `application/x-www-form-urlencoded` Encoding Client ID and Secret (xref:servlet/oauth2/client/client-authentication.adoc#_authenticate_using_client_secret_basic[servlet docs], xref:reactive/oauth2/client/client-authentication.adoc#_authenticate_using_client_secret_basic[reactive docs])
|
||||
|
||||
And for an exhaustive list, please see the release notes for https://github.com/spring-projects/spring-security/releases/tag/6.3.0-RC1[6.3.0-RC1], https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M3[6.3.0-M3], https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M2[6.3.0-M2], and https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M1[6.3.0-M1].
|
||||
|
||||
@@ -15,11 +15,22 @@ antora {
|
||||
]
|
||||
}
|
||||
|
||||
tasks.register("syncAntoraAttachments", Sync) {
|
||||
group = 'Documentation'
|
||||
description = 'Syncs the Antora attachments'
|
||||
from project.provider( { project.tasks.api.outputs } )
|
||||
into project.layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/assets/attachments/api/java')
|
||||
}
|
||||
|
||||
tasks.named("generateAntoraYml") {
|
||||
asciidocAttributes = project.provider( { generateAttributes() } )
|
||||
asciidocAttributes.putAll(providers.provider( { resolvedVersions(project.configurations.testRuntimeClasspath) }))
|
||||
}
|
||||
|
||||
tasks.register("generateAntoraResources") {
|
||||
dependsOn 'generateAntoraYml', 'syncAntoraAttachments'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation platform(project(':spring-security-dependencies'))
|
||||
testImplementation 'com.unboundid:unboundid-ldapsdk'
|
||||
@@ -44,7 +55,7 @@ def generateAttributes() {
|
||||
def securityApiUrl = "$securityDocsUrl/api/"
|
||||
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
|
||||
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
|
||||
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
|
||||
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/reference/$springFrameworkVersion/"
|
||||
def springBootReferenceUrl = "https://docs.spring.io/spring-boot/docs/$springBootVersion/reference/html/"
|
||||
def springBootApiUrl = "https://docs.spring.io/spring-boot/docs/$springBootVersion/api/"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user