1
0
mirror of synced 2026-08-05 09:47:05 +00:00

Compare commits

..

80 Commits

Author SHA1 Message Date
Eleftheria Stein bcaad3a51c Release 5.0.17.RELEASE 2020-06-03 18:12:04 -04:00
Josh Cummings 66f8a56b1b Polish setAllowedHostnames
Added JavaDoc to method, including @since attribute

Issue gh-4310
2020-06-03 08:48:46 -06:00
Eddú Meléndez 3e5b65f647 Add support for allowedHostnames in StrictHttpFirewall
Introduce a new method `setAllowedHostnames` which perform the validation
against untrusted hostnames.

Fixes gh-4310
2020-06-03 08:48:38 -06:00
Eleftheria Stein c62789c976 Update to okhttp 3.12.12
Fixes gh-8637
2020-06-02 22:05:05 -04:00
Eleftheria Stein b02cae0a0c Update to Jython 2.5.3
Fixes gh-8638
2020-06-02 22:04:02 -04:00
Eleftheria Stein af278b58b5 Update to mockwebserver 3.12.12
Fixes gh-8636
2020-06-02 22:03:22 -04:00
justmehyp ba81f6a06a Remove unused field 'digester' in Md4PasswordEncoder
`private Digester digester;`  defined in Md4PasswordEncoder is never used. So remove it.

Closes gh-8553
2020-05-21 11:21:58 -05:00
Maksim Vinogradov a39efaf883 Prevent StackOverflowError for AccessControlEntryImpl.hashCode
Getting StackOverflowError when invoke AclImpl.hashCode because of
cross-references between AclImpl and AccessControlEntryImpl

Remove from AccessControlEntryImpl.hashCode method invocation of
acl.hashCode

fixes gh-5401
2020-05-21 10:09:22 -05:00
Rob Winch 7d5e032e25 Revert "Create the CSRF token on the bounded elactic scheduler"
This reverts commit e21ef422e7.
2020-05-18 11:09:41 -05:00
cbornet e21ef422e7 Create the CSRF token on the bounded elactic scheduler
The CSRF token is created with a call to UUID.randomUUID which is blocking.
This change ensures this blocking call is done on the bounded elastic scheduler which supports blocking calls.

Fixes gh-8128
2020-05-18 11:07:41 -05:00
Artyom Tarynin 66f923dab7 Update AntPathRequestMatcher.java
Fixes gh-8512
2020-05-14 10:58:32 -04:00
Dávid Kovács e8daeacd89 Document NoOpPasswordEncoder will not be removed
This commit adds extension to deprecation notice.

Fixes gh-8506
2020-05-13 12:57:04 -05:00
Rob Winch 27f30b04cd Remove Broken Test
Issue gh-8518
2020-05-12 13:38:42 -05:00
Rob Winch baa238e339 Fix non-standard HTTP method for CsrfWebFilter
Closes gh-8452
2020-05-12 13:22:04 -05:00
Eleftheria Stein cf5bd52121 Next development version 2020-05-06 16:40:35 -04:00
Eleftheria Stein ef78626045 Release 5.0.16.RELEASE 2020-05-06 16:25:17 -04:00
Eleftheria Stein 7f8efb7680 Update to GAE 1.9.80
Closes gh-8479
2020-05-06 12:27:50 -04:00
Eleftheria Stein 2c4d13ddef Update to org.powermock 2.0.7
Closes gh-8478
2020-05-06 12:26:42 -04:00
Eleftheria Stein ed2a646a69 Update to Spring Framework 5.0.17.RELEASE
Closes gh-8477
2020-05-06 12:25:59 -04:00
Eleftheria Stein f8f4d960b5 Clean up Javadoc
Fixes gh-8480
2020-05-05 17:34:53 -04:00
Rob Winch 76a4df0461 Add ROLE_INFRASTRUCTURE to infrastructure beans
Closes gh-8407
2020-04-27 09:23:40 -05:00
Dávid Kovács 7eee6b102f ActiveDirectoryLdapAuthenticationProvider uses InternalAuthenticationServiceException
Closes gh-2884
2020-04-24 10:44:21 -05:00
Rob Winch d69288e665 Fix example in javadoc of FilterChainProxy
Closes gh-8344
2020-04-08 09:15:03 -05:00
Alan Czajkowski 62bc17ea3f BCryptPasswordEncoder rawPassword cannot be null
Closes gh-8317
2020-04-07 13:48:07 -05:00
hotire fd2798ca95 Fix typo in Javadoc of ServerHttpSecurity#hasAuthority
Closes gh-8336
2020-04-06 14:21:16 -05:00
Eleftheria Stein 173660c6ef Fix HttpSecurity Javadoc
Fixes gh-4404
2020-04-02 12:04:24 -04:00
Eleftheria Stein d6aa6a2246 Next Development Version 2020-04-01 16:29:44 -04:00
Eleftheria Stein 1526c981aa Release 5.0.15.RELEASE 2020-04-01 15:31:39 -04:00
Eleftheria Stein a9ddd363aa Fix typo in Getting Started docs
Fixes gh-5254
2020-04-01 15:14:23 -04:00
Eleftheria Stein 4edcac7651 Update to httpclient 4.5.12
Fixes gh-8304
2020-04-01 11:50:22 -04:00
Eleftheria Stein eecad9ee9a Update to hibernate-validator 6.0.19.Final
Fixes gh-8303
2020-04-01 11:49:42 -04:00
Eleftheria Stein 0191e1f11e Update to reactive-streams 1.0.3
Fixes gh-8302
2020-04-01 11:49:06 -04:00
Eleftheria Stein d73f0c5fb9 Update to hibernate-core 5.2.18.Final
Fixes gh-8301
2020-04-01 11:48:44 -04:00
Eleftheria Stein 8d07698d94 Update to groovy 2.4.19
Fixes gh-8300
2020-04-01 11:48:03 -04:00
Eleftheria Stein c9c9532447 Update to unboundid-ldapsdk 4.0.14
Fixes gh-8299
2020-04-01 11:47:24 -04:00
Eleftheria Stein d2d9ce9e6b Update to okhttp 3.12.10
Fixes gh-8298
2020-04-01 11:46:46 -04:00
Eleftheria Stein df6e58ca1d Update to mockwebserver 3.12.10
Fixes gh-8297
2020-04-01 11:46:12 -04:00
Eleftheria Stein ffa1bc8083 Update to org.powermock 2.0.6
Fixes gh-8296
2020-04-01 11:45:29 -04:00
Eleftheria Stein c91e456ae7 Update to GAE 1.9.79
Fixes gh-8295
2020-04-01 11:44:43 -04:00
Rob Winch 45d81ffc49 Fix HttpServlet3RequestFactory Logout Handlers
Previously there was a problem with Servlet API logout integration
when Servlet API was configured before log out.

This ensures that logout handlers is a reference to the logout handlers
vs copying the logout handlers. This ensures that the ordering does not
matter.

Closes gh-4760
2020-03-30 22:19:58 -05:00
Josh Cummings 3a787e2c4f Add Missing Import
Restored an import that was lost during a backport

Issue gh-4183
2020-03-27 15:07:57 -06:00
Josh Cummings b50e511745 SwitchUserFilter Defaults to POST
Fixes gh-4183
2020-03-27 14:43:10 -06:00
Eleftheria Stein e3a11c98bd Update Encryptors documentation
Fixes gh-8208
2020-03-27 11:23:55 -04:00
Erik van Paassen d4ceabd7ac Fix typo in Javadoc of HttpSecurity#csrf()
`HttpSecurity#csrf()` obviously returns a `CsrfConfigurer`, while the Javadoc states that it returns the `ServletApiConfigurer`.
2020-03-17 13:38:04 -06:00
Markus Engelbrecht 475a53233d Fix typo 'properites' in documentation
Fixes gh-8095
2020-03-11 11:05:06 -06:00
AmitB d245b0466a Fix typo in AntPathRequestMatcher contructor comment
Closes gh-8042
2020-03-02 07:22:31 -06:00
LeeHainie d1162c39ce Remove unwanted code
Remove unwanted code
2020-02-20 12:31:21 -07:00
Joe Grandja 888aa15e20 Add release-notes-sections.yml 2020-02-05 15:21:20 -05:00
Joe Grandja b92f105f35 Next Development Version 2020-02-05 08:40:27 -05:00
Joe Grandja 8febecd43f Release 5.0.14.RELEASE 2020-02-05 07:27:13 -05:00
Joe Grandja 425b7783b5 Update to org.slf4j 1.7.30
Fixes gh-7934
2020-02-04 20:34:15 -05:00
Joe Grandja b7ee7136b2 Update to org.powermock 2.0.5
Fixes gh-7933
2020-02-04 20:30:32 -05:00
Joe Grandja 0d331b08b9 Update to hibernate-validator 6.0.18.Final
Fixes gh-7932
2020-02-04 20:26:20 -05:00
Joe Grandja c47d72fd12 Update to org.bouncycastle:bcprov-jdk15on 1.64
Fixes gh-7931
2020-02-04 20:23:01 -05:00
Joe Grandja 248c956962 Update to org.bouncycastle:bcpkix-jdk15on 1.64
Fixes gh-7930
2020-02-04 20:21:09 -05:00
Joe Grandja 530def3af0 Update to org.aspectj 1.9.5
Fixes gh-7929
2020-02-04 20:18:20 -05:00
Joe Grandja e6049fce26 Update to httpclient 4.5.11
Fixes gh-7928
2020-02-04 20:16:00 -05:00
Joe Grandja 0105e95ba2 Update to com.squareup.okhttp3 3.12.8
Fixes gh-7927
2020-02-04 20:13:30 -05:00
Joe Grandja ded544de14 Update to Jackson 2.9.10
Fixes gh-7926
2020-02-04 20:06:52 -05:00
Joe Grandja 7d845b6872 Update to Spring Framework 5.0.16
Fixes gh-7924
2020-02-04 17:26:18 -05:00
Eleftheria Stein 97818ebb22 Load LDIF file from classpath in unboundId mode
Fixes: gh-7833
2020-01-21 17:49:58 +01:00
Filip Hanik f2ee151d63 Build using openjdk8
Fixes gh-7169

[closes #7169]
2020-01-13 10:34:08 -07:00
Rob Winch 8da27b5f41 CompositeServerHttpHeadersWriter Executes Sequentially
Fixes gh-7731
2019-12-12 14:44:57 -06:00
Jeffrey Morlan 466b8a1d82 Fix race condition in SessionRegistryImpl
Adding/removing sessions from principals wasn't atomic. If one thread
removed the last session from a principal while another thread added a
new one, the addition could be lost.

Fixes gh-3189
2019-08-06 13:47:45 -05:00
Rob Winch 0532b6e665 Next Development Version 2019-08-05 12:15:43 -05:00
Rob Winch c94a79c3a9 Release 5.0.13.RELEASE 2019-08-05 12:15:04 -05:00
Rob Winch 09343eb240 Update to Bouncy Castle 1.62
Fixes gh-7194
2019-08-05 11:55:45 -05:00
Rob Winch 90c509d3c5 Update to httpclient 4.5.9
Fixes gh-7193
2019-08-05 11:55:45 -05:00
Rob Winch 3b79bff44d Update to Unboundid 4.0.11
Fixes gh-7192
2019-08-05 11:55:45 -05:00
Rob Winch 3d8819447f Update to AspectJ 1.9.4
Fixes gh-7191
2019-08-05 11:55:45 -05:00
Rob Winch 863e45ca6c Update to Spring Boot 2.0.9.RELEASE
Fixes gh-7190
2019-08-05 11:55:45 -05:00
Rob Winch b520d36abb Update to Powermock 2.0.2
Fixes gh-7189
2019-08-05 11:55:45 -05:00
Rob Winch b03fdd8e25 Update to cglib 3.2.12
Fixes gh-7195
2019-08-05 11:55:39 -05:00
Rob Winch 0f5e37172d Update to Spring Framework 5.0.15.RELEASE
Fixes gh-7188
2019-08-05 11:52:34 -05:00
Rob Winch 7498248910 Update to GAE 1.9.76
Fixes gh-7187
2019-08-05 11:52:19 -05:00
Josh Cummings f3a90df1ef Release Scripts
Added a script for polling Maven Central to notify when release is
uploaded.
2019-06-19 13:40:15 -06:00
Josh Cummings c3b4cabb9f Reinstate docs and schema deployments
Fixes: gh-6929
2019-05-31 21:52:18 -06:00
Josh Cummings 8e3678c79d Temporarily remove docs and schema deployments
Issue: gh-6929
2019-05-31 14:04:30 -06:00
Josh Cummings facc7299e3 Adding JAVA_HOME for JDK 8
Fixes: gh-6928
2019-05-31 14:03:54 -06:00
Joe Grandja b94e4c0197 Next Development Version 2019-04-02 12:47:48 -04:00
43 changed files with 558 additions and 158 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
language: java language: java
jdk: jdk:
- oraclejdk8 - openjdk8
os: os:
- linux - linux
Vendored
+18 -8
View File
@@ -14,7 +14,9 @@ try {
node { node {
checkout scm checkout scm
try { try {
sh "./gradlew clean check --refresh-dependencies --no-daemon --stacktrace" withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew clean check --refresh-dependencies --no-daemon --stacktrace"
}
} catch(Exception e) { } catch(Exception e) {
currentBuild.result = 'FAILED: check' currentBuild.result = 'FAILED: check'
throw e throw e
@@ -30,10 +32,12 @@ try {
checkout scm checkout scm
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) { withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
try { try {
if ("master" == env.BRANCH_NAME) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" if ("master" == env.BRANCH_NAME) {
} else { sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" } else {
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
}
} }
} catch(Exception e) { } catch(Exception e) {
currentBuild.result = 'FAILED: sonar' currentBuild.result = 'FAILED: sonar'
@@ -53,7 +57,9 @@ try {
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) { withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) { withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) { withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
}
} }
} }
} }
@@ -66,7 +72,9 @@ try {
node { node {
checkout scm checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
} }
} }
} }
@@ -76,7 +84,9 @@ try {
node { node {
checkout scm checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -131,10 +131,9 @@ public class AccessControlEntryImpl implements AccessControlEntry,
@Override @Override
public int hashCode() { public int hashCode() {
int result = this.acl.hashCode(); int result = this.permission.hashCode();
result = 31 * result + this.permission.hashCode();
result = 31 * result + (this.id != null ? this.id.hashCode() : 0); result = 31 * result + (this.id != null ? this.id.hashCode() : 0);
result = 31 * result + this.sid.hashCode(); result = 31 * result + (this.sid.hashCode());
result = 31 * result + (this.auditFailure ? 1 : 0); result = 31 * result + (this.auditFailure ? 1 : 0);
result = 31 * result + (this.auditSuccess ? 1 : 0); result = 31 * result + (this.auditSuccess ? 1 : 0);
result = 31 * result + (this.granting ? 1 : 0); result = 31 * result + (this.granting ? 1 : 0);
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -560,6 +560,25 @@ public class AclImplTests {
childAcl.setParent(changeParentAcl); childAcl.setParent(changeParentAcl);
} }
@Test
public void hashCodeWithoutStackOverFlow() throws Exception {
//given
Sid sid = new PrincipalSid("pSid");
ObjectIdentity oid = new ObjectIdentityImpl("type", 1);
AclAuthorizationStrategy authStrategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("role"));
PermissionGrantingStrategy grantingStrategy = new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
AclImpl acl = new AclImpl(oid, 1L, authStrategy, grantingStrategy, null, null, false, sid);
AccessControlEntryImpl ace = new AccessControlEntryImpl(1L, acl, sid, BasePermission.READ, true, true, true);
Field fieldAces = FieldUtils.getField(AclImpl.class, "aces");
fieldAces.setAccessible(true);
List<AccessControlEntryImpl> aces = (List<AccessControlEntryImpl>) fieldAces.get(acl);
aces.add(ace);
//when - then none StackOverFlowError been raised
ace.hashCode();
}
// ~ Inner Classes // ~ Inner Classes
// ================================================================================================== // ==================================================================================================
@@ -47,7 +47,7 @@ public class ServiceAuthenticationDetailsSource implements
// =================================================================================================== // ===================================================================================================
/** /**
* Creates an implementation that uses the specified ServiceProperites and the default * Creates an implementation that uses the specified ServiceProperties and the default
* CAS artifactParameterName. * CAS artifactParameterName.
* *
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl. * @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
@@ -16,8 +16,10 @@
package org.springframework.security.config.annotation.configuration; package org.springframework.security.config.annotation.configuration;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -34,9 +36,11 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
* @since 3.2 * @since 3.2
*/ */
@Configuration @Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ObjectPostProcessorConfiguration { public class ObjectPostProcessorConfiguration {
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ObjectPostProcessor<Object> objectPostProcessor( public ObjectPostProcessor<Object> objectPostProcessor(
AutowireCapableBeanFactory beanFactory) { AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory); return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
@@ -28,8 +28,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware; import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
@@ -80,6 +82,7 @@ import org.springframework.util.Assert;
* @see EnableGlobalMethodSecurity * @see EnableGlobalMethodSecurity
*/ */
@Configuration @Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class GlobalMethodSecurityConfiguration public class GlobalMethodSecurityConfiguration
implements ImportAware, SmartInitializingSingleton { implements ImportAware, SmartInitializingSingleton {
private static final Log logger = LogFactory private static final Log logger = LogFactory
@@ -15,14 +15,18 @@
*/ */
package org.springframework.security.config.annotation.method.configuration; package org.springframework.security.config.annotation.method.configuration;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource; import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
@Configuration @Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class Jsr250MetadataSourceConfiguration { class Jsr250MetadataSourceConfiguration {
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource() { public Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource() {
return new Jsr250MethodSecurityMetadataSource(); return new Jsr250MethodSecurityMetadataSource();
} }
@@ -49,6 +49,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
} }
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public DelegatingMethodSecurityMetadataSource methodMetadataSource() { public DelegatingMethodSecurityMetadataSource methodMetadataSource() {
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory( ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
new DefaultMethodSecurityExpressionHandler()); new DefaultMethodSecurityExpressionHandler());
@@ -69,6 +70,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
} }
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() { public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler(); return new DefaultMethodSecurityExpressionHandler();
} }
@@ -584,6 +584,7 @@ public final class HttpSecurity extends
/** /**
* Allows restricting access based upon the {@link HttpServletRequest} using * Allows restricting access based upon the {@link HttpServletRequest} using
* {@link RequestMatcher} implementations (i.e. via URL patterns).
* *
* <h2>Example Configurations</h2> * <h2>Example Configurations</h2>
* *
@@ -721,7 +722,7 @@ public final class HttpSecurity extends
* } * }
* </pre> * </pre>
* *
* @return the {@link ServletApiConfigurer} for further customizations * @return the {@link CsrfConfigurer} for further customizations
* @throws Exception * @throws Exception
*/ */
public CsrfConfigurer<HttpSecurity> csrf() throws Exception { public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
@@ -168,7 +168,7 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
/** /**
* Specifies the {@link AuthenticationSuccessHandler} to be used. The default is * Specifies the {@link AuthenticationSuccessHandler} to be used. The default is
* {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properites * {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properties
* set. * set.
* *
* @param successHandler the {@link AuthenticationSuccessHandler}. * @param successHandler the {@link AuthenticationSuccessHandler}.
@@ -675,7 +675,7 @@ public class ServerHttpSecurity {
/** /**
* Require a specific authority. * Require a specific authority.
* @param authority the authority to require (i.e. "USER" woudl require authority of "USER"). * @param authority the authority to require (i.e. "USER" would require authority of "USER").
* @return the {@link AuthorizeExchangeSpec} to configure * @return the {@link AuthorizeExchangeSpec} to configure
*/ */
public AuthorizeExchangeSpec hasAuthority(String authority) { public AuthorizeExchangeSpec hasAuthority(String authority) {
@@ -17,7 +17,6 @@ package org.springframework.security.access.expression;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.PermissionEvaluator;
@@ -158,7 +157,6 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
private Set<String> getAuthoritySet() { private Set<String> getAuthoritySet() {
if (roles == null) { if (roles == null) {
roles = new HashSet<>();
Collection<? extends GrantedAuthority> userAuthorities = authentication Collection<? extends GrantedAuthority> userAuthorities = authentication
.getAuthorities(); .getAuthorities();
@@ -129,7 +129,7 @@ import org.springframework.util.Assert;
* &lt;/property&gt; * &lt;/property&gt;
* </pre> * </pre>
* *
* A configuration note: The JaasAuthenticationProvider uses the security properites * A configuration note: The JaasAuthenticationProvider uses the security properties
* "login.config.url.X" to configure jaas. If you would like to customize the way Jaas * "login.config.url.X" to configure jaas. If you would like to customize the way Jaas
* gets configured, create a subclass of this and override the * gets configured, create a subclass of this and override the
* {@link #configureJaas(Resource)} method. * {@link #configureJaas(Resource)} method.
@@ -132,13 +132,18 @@ public class SessionRegistryImpl implements SessionRegistry,
sessionIds.put(sessionId, sessionIds.put(sessionId,
new SessionInformation(principal, sessionId, new Date())); new SessionInformation(principal, sessionId, new Date()));
Set<String> sessionsUsedByPrincipal = principals.computeIfAbsent(principal, key -> new CopyOnWriteArraySet<>()); principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
sessionsUsedByPrincipal.add(sessionId); if (sessionsUsedByPrincipal == null) {
sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
}
sessionsUsedByPrincipal.add(sessionId);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Sessions used by '" + principal + "' : " logger.trace("Sessions used by '" + principal + "' : "
+ sessionsUsedByPrincipal); + sessionsUsedByPrincipal);
} }
return sessionsUsedByPrincipal;
});
} }
public void removeSessionInformation(String sessionId) { public void removeSessionInformation(String sessionId) {
@@ -157,32 +162,29 @@ public class SessionRegistryImpl implements SessionRegistry,
sessionIds.remove(sessionId); sessionIds.remove(sessionId);
Set<String> sessionsUsedByPrincipal = principals.get(info.getPrincipal()); principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {
if (sessionsUsedByPrincipal == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Removing session " + sessionId
+ " from principal's set of registered sessions");
}
sessionsUsedByPrincipal.remove(sessionId);
if (sessionsUsedByPrincipal.isEmpty()) {
// No need to keep object in principals Map anymore
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Removing principal " + info.getPrincipal() logger.debug("Removing session " + sessionId
+ " from registry"); + " from principal's set of registered sessions");
} }
principals.remove(info.getPrincipal());
}
if (logger.isTraceEnabled()) { sessionsUsedByPrincipal.remove(sessionId);
logger.trace("Sessions used by '" + info.getPrincipal() + "' : "
+ sessionsUsedByPrincipal); if (sessionsUsedByPrincipal.isEmpty()) {
} // No need to keep object in principals Map anymore
if (logger.isDebugEnabled()) {
logger.debug("Removing principal " + info.getPrincipal()
+ " from registry");
}
sessionsUsedByPrincipal = null;
}
if (logger.isTraceEnabled()) {
logger.trace("Sessions used by '" + info.getPrincipal() + "' : "
+ sessionsUsedByPrincipal);
}
return sessionsUsedByPrincipal;
});
} }
} }
@@ -31,6 +31,7 @@ DigestAuthenticationFilter.usernameNotFound=Username {0} not found
JdbcDaoImpl.noAuthority=User {0} has no GrantedAuthority JdbcDaoImpl.noAuthority=User {0} has no GrantedAuthority
JdbcDaoImpl.notFound=User {0} not found JdbcDaoImpl.notFound=User {0} not found
LdapAuthenticationProvider.badCredentials=Bad credentials LdapAuthenticationProvider.badCredentials=Bad credentials
LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed
LdapAuthenticationProvider.credentialsExpired=User credentials have expired LdapAuthenticationProvider.credentialsExpired=User credentials have expired
LdapAuthenticationProvider.disabled=User is disabled LdapAuthenticationProvider.disabled=User is disabled
LdapAuthenticationProvider.expired=User account has expired LdapAuthenticationProvider.expired=User account has expired
@@ -65,6 +65,10 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
} }
public String encode(CharSequence rawPassword) { public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
String salt; String salt;
if (strength > 0) { if (strength > 0) {
if (random != null) { if (random != null) {
@@ -81,6 +85,10 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
} }
public boolean matches(CharSequence rawPassword, String encodedPassword) { public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
if (encodedPassword == null || encodedPassword.length() == 0) { if (encodedPassword == null || encodedPassword.length() == 0) {
logger.warn("Empty encoded password"); logger.warn("Empty encoded password");
return false; return false;
@@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2016 the original author or authors. * Copyright 2011-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -32,16 +32,13 @@ public class Encryptors {
* (Password-Based Key Derivation Function #2). Salts the password to prevent * (Password-Based Key Derivation Function #2). Salts the password to prevent
* dictionary attacks against the key. The provided salt is expected to be * dictionary attacks against the key. The provided salt is expected to be
* hex-encoded; it should be random and at least 8 bytes in length. Also applies a * hex-encoded; it should be random and at least 8 bytes in length. Also applies a
* random 16 byte initialization vector to ensure each encrypted message will be * random 16-byte initialization vector to ensure each encrypted message will be
* unique. Requires Java 6. * unique. Requires Java 6.
* *
* @param password the password used to generate the encryptor's secret key; should * @param password the password used to generate the encryptor's secret key; should
* not be shared * not be shared
* @param salt a hex-encoded, random, site-global salt value to use to generate the * @param salt a hex-encoded, random, site-global salt value to use to generate the
* key * key
*
* @see #standard(CharSequence, CharSequence) which uses the slightly weaker CBC mode
* (instead of GCM)
*/ */
public static BytesEncryptor stronger(CharSequence password, CharSequence salt) { public static BytesEncryptor stronger(CharSequence password, CharSequence salt) {
return new AesBytesEncryptor(password.toString(), salt, return new AesBytesEncryptor(password.toString(), salt,
@@ -53,13 +50,21 @@ public class Encryptors {
* Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation * Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation
* Function #2). Salts the password to prevent dictionary attacks against the key. The * Function #2). Salts the password to prevent dictionary attacks against the key. The
* provided salt is expected to be hex-encoded; it should be random and at least 8 * provided salt is expected to be hex-encoded; it should be random and at least 8
* bytes in length. Also applies a random 16 byte initialization vector to ensure each * bytes in length. Also applies a random 16-byte initialization vector to ensure each
* encrypted message will be unique. Requires Java 6. * encrypted message will be unique. Requires Java 6.
* NOTE: This mode is not
* <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">authenticated</a>
* and does not provide any guarantees about the authenticity of the data.
* For a more secure alternative, users should prefer
* {@link #stronger(CharSequence, CharSequence)}.
* *
* @param password the password used to generate the encryptor's secret key; should * @param password the password used to generate the encryptor's secret key; should
* not be shared * not be shared
* @param salt a hex-encoded, random, site-global salt value to use to generate the * @param salt a hex-encoded, random, site-global salt value to use to generate the
* key * key
*
* @see #stronger(CharSequence, CharSequence), which uses the significatly more secure
* GCM (instead of CBC)
*/ */
public static BytesEncryptor standard(CharSequence password, CharSequence salt) { public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
return new AesBytesEncryptor(password.toString(), salt, return new AesBytesEncryptor(password.toString(), salt,
@@ -100,7 +105,10 @@ public class Encryptors {
* not be shared * not be shared
* @param salt a hex-encoded, random, site-global salt value to use to generate the * @param salt a hex-encoded, random, site-global salt value to use to generate the
* secret key * secret key
* @deprecated This encryptor is not secure. Instead, look to your data store for a
* mechanism to query encrypted data.
*/ */
@Deprecated
public static TextEncryptor queryableText(CharSequence password, CharSequence salt) { public static TextEncryptor queryableText(CharSequence password, CharSequence salt) {
return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(), return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(),
salt)); salt));
@@ -83,8 +83,6 @@ public class Md4PasswordEncoder implements PasswordEncoder {
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator(); private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
private boolean encodeHashAsBase64; private boolean encodeHashAsBase64;
private Digester digester;
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) { public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
this.encodeHashAsBase64 = encodeHashAsBase64; this.encodeHashAsBase64 = encodeHashAsBase64;
@@ -26,7 +26,8 @@ package org.springframework.security.crypto.password;
* @deprecated This PasswordEncoder is not secure. Instead use an * @deprecated This PasswordEncoder is not secure. Instead use an
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or * adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports * SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
* password upgrades. * password upgrades. There are no plans to remove this support. It is deprecated to indicate that
* this is a legacy implementation and using it is considered insecure.
*/ */
@Deprecated @Deprecated
public final class NoOpPasswordEncoder implements PasswordEncoder { public final class NoOpPasswordEncoder implements PasswordEncoder {
@@ -92,4 +92,15 @@ public class BCryptPasswordEncoderTests {
assertThat(encoder.matches("password", "012345678901234567890123456789")).isFalse(); assertThat(encoder.matches("password", "012345678901234567890123456789")).isFalse();
} }
@Test(expected = IllegalArgumentException.class)
public void encodeNullRawPassword() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
encoder.encode(null);
}
@Test(expected = IllegalArgumentException.class)
public void matchNullRawPassword() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
encoder.matches(null, "does-not-matter");
}
} }
+12 -4
View File
@@ -24,7 +24,7 @@ Finally, welcome to the Spring Security <<community,community>>.
[[getting-started]] [[getting-started]]
== Getting Started == Getting Started
The later parts of this guide provide an in-depth discussion of the framework architecture and implementation classes, which you need to understand if you want to do any serious customization. In this part, we'll introduce Spring Security 4.0, give a brief overview of the project's history and take a slightly gentler look at how to get started using the framework. In particular, we'll look at namespace configuration which provides a much simpler way of securing your application compared to the traditional Spring bean approach where you have to wire up all the implementation classes individually. The later parts of this guide provide an in-depth discussion of the framework architecture and implementation classes, which you need to understand if you want to do any serious customization. In this part, we'll introduce Spring Security 5.0, give a brief overview of the project's history and take a slightly gentler look at how to get started using the framework. In particular, we'll look at namespace configuration which provides a much simpler way of securing your application compared to the traditional Spring bean approach where you have to wire up all the implementation classes individually.
We'll also take a look at the sample applications that are available. It's worth trying to run these and experimenting with them a bit even before you read the later sections - you can dip back into them as your understanding of the framework increases. Please also check out the https://spring.io/spring-security[project website] as it has useful information on building the project, plus links to articles, videos and tutorials. We'll also take a look at the sample applications that are available. It's worth trying to run these and experimenting with them a bit even before you read the later sections - you can dip back into them as your understanding of the framework increases. Please also check out the https://spring.io/spring-security[project website] as it has useful information on building the project, plus links to articles, videos and tutorials.
@@ -7980,14 +7980,17 @@ The Encryptors class provides factory methods for constructing symmetric encrypt
[[spring-security-crypto-encryption-bytes]] [[spring-security-crypto-encryption-bytes]]
==== BytesEncryptor ==== BytesEncryptor
Use the Encryptors.standard factory method to construct a "standard" BytesEncryptor: Use the `Encryptors.stronger` factory method to construct a BytesEncryptor:
[source,java] [source,java]
---- ----
Encryptors.standard("password", "salt"); Encryptors.stronger("password", "salt");
---- ----
The "standard" encryption method is 256-bit AES using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2). This method requires Java 6. The password used to generate the SecretKey should be kept in a secure place and not be shared. The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised. A 16-byte random initialization vector is also applied so each encrypted message is unique. The "stronger" encryption method creates an encryptor using 256 bit AES encryption with
Galois Counter Mode (GCM).
It derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
This method requires Java 6. The password used to generate the SecretKey should be kept in a secure place and not be shared. The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised. A 16-byte random initialization vector is also applied so each encrypted message is unique.
The provided salt should be in hex-encoded String form, be random, and be at least 8 bytes in length. Such a salt may be generated using a KeyGenerator: The provided salt should be in hex-encoded String form, be random, and be at least 8 bytes in length. Such a salt may be generated using a KeyGenerator:
@@ -7996,6 +7999,11 @@ The provided salt should be in hex-encoded String form, be random, and be at lea
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
---- ----
Users may also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode.
This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any
guarantees about the authenticity of the data.
For a more secure alternative, users should prefer `Encryptors.stronger`.
[[spring-security-crypto-encryption-text]] [[spring-security-crypto-encryption-text]]
==== TextEncryptor ==== TextEncryptor
Use the Encryptors.text factory method to construct a standard TextEncryptor: Use the Encryptors.text factory method to construct a standard TextEncryptor:
+3 -3
View File
@@ -1,3 +1,3 @@
gaeVersion=1.9.68 gaeVersion=1.9.80
springBootVersion=2.0.8.RELEASE springBootVersion=2.0.9.RELEASE
version=5.0.12.RELEASE version=5.0.17.RELEASE
+37 -37
View File
@@ -1,22 +1,22 @@
dependencyManagement { dependencyManagement {
imports { imports {
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR17' mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR17'
mavenBom 'org.springframework:spring-framework-bom:5.0.13.RELEASE' mavenBom 'org.springframework:spring-framework-bom:5.0.17.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR14' mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR14'
} }
dependencies { dependencies {
dependency 'cglib:cglib-nodep:3.2.10' dependency 'cglib:cglib-nodep:3.2.12'
dependency 'com.squareup.okhttp3:mockwebserver:3.12.2' dependency 'com.squareup.okhttp3:mockwebserver:3.12.12'
dependency 'opensymphony:sitemesh:2.4.2' dependency 'opensymphony:sitemesh:2.4.2'
dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.gebish:geb-spock:0.10.0'
dependency 'org.jasig.cas:cas-server-webapp:4.2.7' dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
dependency 'org.powermock:powermock-api-mockito2:2.0.0' dependency 'org.powermock:powermock-api-mockito2:2.0.7'
dependency 'org.powermock:powermock-api-support:2.0.0' dependency 'org.powermock:powermock-api-support:2.0.7'
dependency 'org.powermock:powermock-core:2.0.0' dependency 'org.powermock:powermock-core:2.0.7'
dependency 'org.powermock:powermock-module-junit4-common:2.0.0' dependency 'org.powermock:powermock-module-junit4-common:2.0.7'
dependency 'org.powermock:powermock-module-junit4:2.0.0' dependency 'org.powermock:powermock-module-junit4:2.0.7'
dependency 'org.powermock:powermock-reflect:2.0.0' dependency 'org.powermock:powermock-reflect:2.0.7'
dependency 'org.python:jython:2.5.0' dependency 'org.python:jython:2.5.3'
dependency 'org.spockframework:spock-core:1.0-groovy-2.4' dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
dependency 'org.spockframework:spock-spring:1.0-groovy-2.4' dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
} }
@@ -28,27 +28,27 @@ dependencyManagement {
dependency 'asm:asm:3.1' dependency 'asm:asm:3.1'
dependency 'ch.qos.logback:logback-classic:1.2.3' dependency 'ch.qos.logback:logback-classic:1.2.3'
dependency 'ch.qos.logback:logback-core:1.2.3' dependency 'ch.qos.logback:logback-core:1.2.3'
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.8' dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.10'
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.8' dependency 'com.fasterxml.jackson.core:jackson-core:2.9.10'
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.8' dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.10.2'
dependency 'com.fasterxml:classmate:1.3.4' dependency 'com.fasterxml:classmate:1.3.4'
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1' dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.64' dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.80'
dependency 'com.google.appengine:appengine-api-labs:1.9.64' dependency 'com.google.appengine:appengine-api-labs:1.9.80'
dependency 'com.google.appengine:appengine-api-stubs:1.9.64' dependency 'com.google.appengine:appengine-api-stubs:1.9.80'
dependency 'com.google.appengine:appengine-testing:1.9.64' dependency 'com.google.appengine:appengine-testing:1.9.80'
dependency 'com.google.appengine:appengine:1.9.63' dependency 'com.google.appengine:appengine:1.9.80'
dependency 'com.google.code.gson:gson:2.8.2' dependency 'com.google.code.gson:gson:2.8.2'
dependency 'com.google.guava:guava:20.0' dependency 'com.google.guava:guava:20.0'
dependency 'com.google.inject:guice:3.0' dependency 'com.google.inject:guice:3.0'
dependency 'com.nimbusds:lang-tag:1.4.3' dependency 'com.nimbusds:lang-tag:1.4.3'
dependency 'com.nimbusds:nimbus-jose-jwt:5.14' dependency 'com.nimbusds:nimbus-jose-jwt:5.14'
dependency 'com.nimbusds:oauth2-oidc-sdk:5.64.4' dependency 'com.nimbusds:oauth2-oidc-sdk:5.64.4'
dependency 'com.squareup.okhttp3:okhttp:3.12.2' dependency 'com.squareup.okhttp3:okhttp:3.12.12'
dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.squareup.okio:okio:1.13.0'
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
dependency 'com.sun.xml.bind:jaxb-impl:2.3.0.1' dependency 'com.sun.xml.bind:jaxb-impl:2.3.0.1'
dependency 'com.unboundid:unboundid-ldapsdk:4.0.10' dependency 'com.unboundid:unboundid-ldapsdk:4.0.14'
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1' dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
dependency 'commons-cli:commons-cli:1.4' dependency 'commons-cli:commons-cli:1.4'
dependency 'commons-codec:commons-codec:1.12' dependency 'commons-codec:commons-codec:1.12'
@@ -115,7 +115,7 @@ dependencyManagement {
dependency 'org.apache.directory.shared:shared-cursor:0.9.15' dependency 'org.apache.directory.shared:shared-cursor:0.9.15'
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15' dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
dependency 'org.apache.directory.shared:shared-ldap:0.9.15' dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
dependency 'org.apache.httpcomponents:httpclient:4.5.7' dependency 'org.apache.httpcomponents:httpclient:4.5.12'
dependency 'org.apache.httpcomponents:httpcore:4.4.8' dependency 'org.apache.httpcomponents:httpcore:4.4.8'
dependency 'org.apache.httpcomponents:httpmime:4.5.3' dependency 'org.apache.httpcomponents:httpmime:4.5.3'
dependency 'org.apache.mina:mina-core:2.0.0-M6' dependency 'org.apache.mina:mina-core:2.0.0-M6'
@@ -128,16 +128,16 @@ dependencyManagement {
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.0.44' dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.0.44'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23'
dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23' dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23'
dependency 'org.aspectj:aspectjrt:1.9.2' dependency 'org.aspectj:aspectjrt:1.9.5'
dependency 'org.aspectj:aspectjtools:1.9.2' dependency 'org.aspectj:aspectjtools:1.9.5'
dependency 'org.aspectj:aspectjweaver:1.9.2' dependency 'org.aspectj:aspectjweaver:1.9.5'
dependency 'org.assertj:assertj-core:3.11.1' dependency 'org.assertj:assertj-core:3.11.1'
dependency 'org.attoparser:attoparser:2.0.4.RELEASE' dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
dependency 'org.bouncycastle:bcpkix-jdk15on:1.61' dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
dependency 'org.bouncycastle:bcprov-jdk15on:1.61' dependency 'org.bouncycastle:bcprov-jdk15on:1.64'
dependency 'org.codehaus.groovy:groovy-all:2.4.14' dependency 'org.codehaus.groovy:groovy-all:2.4.19'
dependency 'org.codehaus.groovy:groovy-json:2.4.14' dependency 'org.codehaus.groovy:groovy-json:2.4.19'
dependency 'org.codehaus.groovy:groovy:2.4.14' dependency 'org.codehaus.groovy:groovy:2.4.19'
dependency 'org.eclipse.jdt:ecj:3.12.3' dependency 'org.eclipse.jdt:ecj:3.12.3'
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.7.v20170914' dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.7.v20170914'
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.7.v20170914' dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.7.v20170914'
@@ -158,9 +158,9 @@ dependencyManagement {
dependency 'org.hamcrest:hamcrest-core:1.3' dependency 'org.hamcrest:hamcrest-core:1.3'
dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final'
dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
dependency 'org.hibernate:hibernate-core:5.2.17.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final'
dependency 'org.hibernate:hibernate-entitymanager:5.2.18.Final' dependency 'org.hibernate:hibernate-entitymanager:5.2.18.Final'
dependency 'org.hibernate:hibernate-validator:6.0.16.Final' dependency 'org.hibernate:hibernate-validator:6.0.19.Final'
dependency 'org.hsqldb:hsqldb:2.4.1' dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.jasig.cas.client:cas-client-core:3.5.1' dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
dependency 'org.javassist:javassist:3.22.0-CR2' dependency 'org.javassist:javassist:3.22.0-CR2'
@@ -171,17 +171,17 @@ dependencyManagement {
dependency 'org.objenesis:objenesis:2.6' dependency 'org.objenesis:objenesis:2.6'
dependency 'org.openid4java:openid4java-nodeps:0.9.6' dependency 'org.openid4java:openid4java-nodeps:0.9.6'
dependency 'org.ow2.asm:asm:6.0' dependency 'org.ow2.asm:asm:6.0'
dependency 'org.reactivestreams:reactive-streams:1.0.1' dependency 'org.reactivestreams:reactive-streams:1.0.3'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.3' dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.3'
dependency 'org.seleniumhq.selenium:selenium-api:3.141.59' dependency 'org.seleniumhq.selenium:selenium-api:3.141.59'
dependency 'org.seleniumhq.selenium:selenium-java:3.141.59' dependency 'org.seleniumhq.selenium:selenium-java:3.141.59'
dependency 'org.seleniumhq.selenium:selenium-support:3.141.59' dependency 'org.seleniumhq.selenium:selenium-support:3.141.59'
dependency 'org.skyscreamer:jsonassert:1.5.0' dependency 'org.skyscreamer:jsonassert:1.5.0'
dependency 'org.slf4j:jcl-over-slf4j:1.7.26' dependency 'org.slf4j:jcl-over-slf4j:1.7.30'
dependency 'org.slf4j:jul-to-slf4j:1.7.26' dependency 'org.slf4j:jul-to-slf4j:1.7.30'
dependency 'org.slf4j:log4j-over-slf4j:1.7.26' dependency 'org.slf4j:log4j-over-slf4j:1.7.30'
dependency 'org.slf4j:slf4j-api:1.7.26' dependency 'org.slf4j:slf4j-api:1.7.30'
dependency 'org.slf4j:slf4j-nop:1.7.26' dependency 'org.slf4j:slf4j-nop:1.7.30'
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE' dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE'
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE' dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'
@@ -0,0 +1,144 @@
/*
* Copyright 2002-2020 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.ldap.server;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.ContextSource;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
/**
* Tests for {@link UnboundIdContainer}, specifically relating to LDIF file detection.
*
* @author Eleftheria Stein
*/
public class UnboundIdContainerLdifTests {
AnnotationConfigApplicationContext appCtx;
@After
public void closeAppContext() {
if (appCtx != null) {
appCtx.close();
appCtx = null;
}
}
@Test
public void unboundIdContainerWhenCustomLdifNameThenLdifLoaded() {
appCtx = new AnnotationConfigApplicationContext(CustomLdifConfig.class);
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx
.getBean(ContextSource.class);
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
assertThat(template.compare("uid=bob,ou=people", "uid", "bob")).isTrue();
}
@Configuration
static class CustomLdifConfig {
private UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
@Bean
UnboundIdContainer ldapContainer() {
this.container.setPort(0);
return this.container;
}
@Bean
ContextSource contextSource(UnboundIdContainer container) {
return new DefaultSpringSecurityContextSource("ldap://127.0.0.1:"
+ container.getPort() + "/dc=springframework,dc=org");
}
@PreDestroy
void shutdown() {
this.container.stop();
}
}
@Test
public void unboundIdContainerWhenWildcardLdifNameThenLdifLoaded() {
appCtx = new AnnotationConfigApplicationContext(WildcardLdifConfig.class);
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx
.getBean(ContextSource.class);
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
assertThat(template.compare("uid=bob,ou=people", "uid", "bob")).isTrue();
}
@Configuration
static class WildcardLdifConfig {
private UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org",
"classpath*:test-server.ldif");
@Bean
UnboundIdContainer ldapContainer() {
this.container.setPort(0);
return this.container;
}
@Bean
ContextSource contextSource(UnboundIdContainer container) {
return new DefaultSpringSecurityContextSource("ldap://127.0.0.1:"
+ container.getPort() + "/dc=springframework,dc=org");
}
@PreDestroy
void shutdown() {
this.container.stop();
}
}
@Test
public void unboundIdContainerWhenMalformedLdifThenException() {
try {
appCtx = new AnnotationConfigApplicationContext(MalformedLdifConfig.class);
failBecauseExceptionWasNotThrown(IllegalStateException.class);
} catch (Exception e) {
assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
assertThat(e.getMessage()).contains("Unable to load LDIF classpath:test-server-malformed.txt");
}
}
@Configuration
static class MalformedLdifConfig {
private UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org",
"classpath:test-server-malformed.txt");
@Bean
UnboundIdContainer ldapContainer() {
this.container.setPort(0);
return this.container;
}
@PreDestroy
void shutdown() {
this.container.stop();
}
}
}
@@ -0,0 +1,9 @@
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
dn ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroups
@@ -16,6 +16,7 @@
package org.springframework.security.ldap.authentication.ad; package org.springframework.security.ldap.authentication.ad;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.DefaultDirObjectFactory; import org.springframework.ldap.core.support.DefaultDirObjectFactory;
@@ -24,6 +25,7 @@ import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@@ -141,12 +143,15 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
UsernamePasswordAuthenticationToken auth) { UsernamePasswordAuthenticationToken auth) {
String username = auth.getName(); String username = auth.getName();
String password = (String) auth.getCredentials(); String password = (String) auth.getCredentials();
DirContext ctx = null;
DirContext ctx = bindAsUser(username, password);
try { try {
ctx = bindAsUser(username, password);
return searchForUser(ctx, username); return searchForUser(ctx, username);
} }
catch (CommunicationException e) {
throw badLdapConnection(e);
}
catch (NamingException e) { catch (NamingException e) {
logger.error("Failed to locate directory entry for authenticated user: " logger.error("Failed to locate directory entry for authenticated user: "
+ username, e); + username, e);
@@ -208,8 +213,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
|| (e instanceof OperationNotSupportedException)) { || (e instanceof OperationNotSupportedException)) {
handleBindException(bindPrincipal, e); handleBindException(bindPrincipal, e);
throw badCredentials(e); throw badCredentials(e);
} } else {
else {
throw LdapUtils.convertLdapException(e); throw LdapUtils.convertLdapException(e);
} }
} }
@@ -311,6 +315,12 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
return (BadCredentialsException) badCredentials().initCause(cause); return (BadCredentialsException) badCredentials().initCause(cause);
} }
private InternalAuthenticationServiceException badLdapConnection(Throwable cause) {
return new InternalAuthenticationServiceException(messages.getMessage(
"LdapAuthenticationProvider.badLdapConnection",
"Connection to LDAP server failed."), cause);
}
private DirContextOperations searchForUser(DirContext context, String username) private DirContextOperations searchForUser(DirContext context, String username)
throws NamingException { throws NamingException {
SearchControls searchControls = new SearchControls(); SearchControls searchControls = new SearchControls();
@@ -325,6 +335,9 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
searchControls, searchRoot, searchFilter, searchControls, searchRoot, searchFilter,
new Object[] { bindPrincipal, username }); new Object[] { bindPrincipal, username });
} }
catch (CommunicationException ldapCommunicationException) {
throw badLdapConnection(ldapCommunicationException);
}
catch (IncorrectResultSizeDataAccessException incorrectResults) { catch (IncorrectResultSizeDataAccessException incorrectResults) {
// Search should never return multiple results if properly configured - just // Search should never return multiple results if properly configured - just
// rethrow // rethrow
@@ -114,10 +114,10 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
private void importLdif(InMemoryDirectoryServer directoryServer) { private void importLdif(InMemoryDirectoryServer directoryServer) {
if (StringUtils.hasText(this.ldif)) { if (StringUtils.hasText(this.ldif)) {
Resource resource = this.context.getResource(this.ldif);
try { try {
if (resource.exists()) { Resource[] resources = this.context.getResources(this.ldif);
try (InputStream inputStream = resource.getInputStream()) { if (resources.length > 0 && resources[0].exists()) {
try (InputStream inputStream = resources[0].getInputStream()) {
directoryServer.importFromLDIF(false, new LDIFReader(inputStream)); directoryServer.importFromLDIF(false, new LDIFReader(inputStream));
} }
} }
@@ -32,6 +32,7 @@ import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@@ -58,6 +59,9 @@ import static org.springframework.security.ldap.authentication.ad.ActiveDirector
* @author Rob Winch * @author Rob Winch
*/ */
public class ActiveDirectoryLdapAuthenticationProviderTests { public class ActiveDirectoryLdapAuthenticationProviderTests {
public static final String EXISTING_LDAP_PROVIDER = "ldap://192.168.1.200/";
public static final String NON_EXISTING_LDAP_PROVIDER = "ldap://192.168.1.201/";
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@@ -378,17 +382,29 @@ public class ActiveDirectoryLdapAuthenticationProviderTests {
} }
@Test(expected = org.springframework.ldap.CommunicationException.class) @Test(expected = org.springframework.ldap.CommunicationException.class)
public void nonAuthenticationExceptionIsConvertedToSpringLdapException() public void nonAuthenticationExceptionIsConvertedToSpringLdapException() throws Throwable {
throws Exception { try {
provider.contextFactory = createContextFactoryThrowing(new CommunicationException( provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
msg)); msg));
provider.authenticate(joe); provider.authenticate(joe);
} catch (InternalAuthenticationServiceException e) {
// Since GH-8418 ldap communication exception is wrapped into InternalAuthenticationServiceException.
// This test is about the wrapped exception, so we throw it.
throw e.getCause();
}
}
@Test(expected = org.springframework.security.authentication.InternalAuthenticationServiceException.class )
public void connectionExceptionIsWrappedInInternalException() throws Exception {
ActiveDirectoryLdapAuthenticationProvider noneReachableProvider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", NON_EXISTING_LDAP_PROVIDER, "dc=ad,dc=eu,dc=mydomain");
noneReachableProvider.doAuthentication(joe);
} }
@Test @Test
public void rootDnProvidedSeparatelyFromDomainAlsoWorks() throws Exception { public void rootDnProvidedSeparatelyFromDomainAlsoWorks() throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider( ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/", "dc=ad,dc=eu,dc=mydomain"); "mydomain.eu", EXISTING_LDAP_PROVIDER, "dc=ad,dc=eu,dc=mydomain");
checkAuthentication("dc=ad,dc=eu,dc=mydomain", provider); checkAuthentication("dc=ad,dc=eu,dc=mydomain", provider);
} }
@@ -0,0 +1,14 @@
releasenotes:
sections:
- title: "New Features"
emoji: ":star:"
labels: ["enhancement"]
- title: "Bug Fixes"
emoji: ":beetle:"
labels: ["bug", "regression"]
- title: "Dependency Upgrades"
emoji: ":hammer:"
labels: ["dependency-upgrade"]
- title: "Non-passive"
emoji: ":rewind:"
labels: ["breaks-passivity"]
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
VERSION=$1
until http -h --check-status --ignore-stdin https://repo1.maven.org/maven2/org/springframework/security/spring-security-core/$VERSION/; do sleep 10; clear; done; spd-say "It is now uploaded"
@@ -43,7 +43,7 @@ final class WithMockUserSecurityContextFactory implements
.username() : withUser.value(); .username() : withUser.value();
if (username == null) { if (username == null) {
throw new IllegalArgumentException(withUser throw new IllegalArgumentException(withUser
+ " cannot have null username on both username and value properites"); + " cannot have null username on both username and value properties");
} }
List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
@@ -60,7 +60,7 @@ import java.util.*;
* requests which match the pattern. An example configuration might look like this: * requests which match the pattern. An example configuration might look like this:
* *
* <pre> * <pre>
* &lt;bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy"&gt; * &lt;bean id="myfilterChainProxy" class="org.springframework.security.web.FilterChainProxy"&gt;
* &lt;constructor-arg&gt; * &lt;constructor-arg&gt;
* &lt;util:list&gt; * &lt;util:list&gt;
* &lt;security:filter-chain pattern="/do/not/filter*" filters="none"/&gt; * &lt;security:filter-chain pattern="/do/not/filter*" filters="none"/&gt;
@@ -228,10 +228,15 @@ class DummyRequest extends HttpServletRequestWrapper {
public void setQueryString(String queryString) { public void setQueryString(String queryString) {
this.queryString = queryString; this.queryString = queryString;
} }
@Override
public String getServerName() {
return null;
}
} }
final class UnsupportedOperationExceptionInvocationHandler implements InvocationHandler { final class UnsupportedOperationExceptionInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
throw new UnsupportedOperationException(method + " is not supported"); throw new UnsupportedOperationException(method + " is not supported");
} }
} }
@@ -563,6 +563,6 @@ public class SwitchUserFilter extends GenericFilterBean
} }
private static RequestMatcher createMatcher(String pattern) { private static RequestMatcher createMatcher(String pattern) {
return new AntPathRequestMatcher(pattern, null, true, new UrlPathHelper()); return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper());
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,14 +16,15 @@
package org.springframework.security.web.firewall; package org.springframework.security.web.firewall;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* <p> * <p>
@@ -59,10 +60,15 @@ import java.util.Set;
* Rejects URLs that contain a URL encoded percent. See * Rejects URLs that contain a URL encoded percent. See
* {@link #setAllowUrlEncodedPercent(boolean)} * {@link #setAllowUrlEncodedPercent(boolean)}
* </li> * </li>
* <li>
* Rejects hosts that are not allowed. See
* {@link #setAllowedHostnames(Predicate)}
* </li>
* </ul> * </ul>
* *
* @see DefaultHttpFirewall * @see DefaultHttpFirewall
* @author Rob Winch * @author Rob Winch
* @author Eddú Meléndez
* @since 5.0.1 * @since 5.0.1
*/ */
public class StrictHttpFirewall implements HttpFirewall { public class StrictHttpFirewall implements HttpFirewall {
@@ -82,6 +88,8 @@ public class StrictHttpFirewall implements HttpFirewall {
private Set<String> decodedUrlBlacklist = new HashSet<String>(); private Set<String> decodedUrlBlacklist = new HashSet<String>();
private Predicate<String> allowedHostnames = hostname -> true;
public StrictHttpFirewall() { public StrictHttpFirewall() {
urlBlacklistsAddAll(FORBIDDEN_SEMICOLON); urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH); urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
@@ -230,6 +238,21 @@ public class StrictHttpFirewall implements HttpFirewall {
} }
} }
/**
* <p>
* Determines which hostnames should be allowed. The default is to allow any hostname.
* </p>
*
* @param allowedHostnames the predicate for testing hostnames
* @since 5.0.17
*/
public void setAllowedHostnames(Predicate<String> allowedHostnames) {
if (allowedHostnames == null) {
throw new IllegalArgumentException("allowedHostnames cannot be null");
}
this.allowedHostnames = allowedHostnames;
}
private void urlBlacklistsAddAll(Collection<String> values) { private void urlBlacklistsAddAll(Collection<String> values) {
this.encodedUrlBlacklist.addAll(values); this.encodedUrlBlacklist.addAll(values);
this.decodedUrlBlacklist.addAll(values); this.decodedUrlBlacklist.addAll(values);
@@ -243,6 +266,7 @@ public class StrictHttpFirewall implements HttpFirewall {
@Override @Override
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
rejectedBlacklistedUrls(request); rejectedBlacklistedUrls(request);
rejectedUntrustedHosts(request);
if (!isNormalized(request)) { if (!isNormalized(request)) {
throw new RequestRejectedException("The request was rejected because the URL was not normalized."); throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
@@ -272,6 +296,13 @@ public class StrictHttpFirewall implements HttpFirewall {
} }
} }
private void rejectedUntrustedHosts(HttpServletRequest request) {
String serverName = request.getServerName();
if (serverName != null && !this.allowedHostnames.test(serverName)) {
throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted.");
}
}
@Override @Override
public HttpServletResponse getFirewalledResponse(HttpServletResponse response) { public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
return new FirewalledResponse(response); return new FirewalledResponse(response);
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -54,6 +54,7 @@ import java.util.Set;
* </p> * </p>
* *
* @author Rob Winch * @author Rob Winch
* @author Parikshit Dutta
* @since 5.0 * @since 5.0
*/ */
public class CsrfWebFilter implements WebFilter { public class CsrfWebFilter implements WebFilter {
@@ -133,7 +134,7 @@ public class CsrfWebFilter implements WebFilter {
@Override @Override
public Mono<MatchResult> matches(ServerWebExchange exchange) { public Mono<MatchResult> matches(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest()) return Mono.just(exchange.getRequest())
.map(r -> r.getMethod()) .flatMap(r -> Mono.justOrEmpty(r.getMethod()))
.filter(m -> ALLOWED_METHODS.contains(m)) .filter(m -> ALLOWED_METHODS.contains(m))
.flatMap(m -> MatchResult.notMatch()) .flatMap(m -> MatchResult.notMatch())
.switchIfEmpty(MatchResult.match()); .switchIfEmpty(MatchResult.match());
@@ -15,14 +15,12 @@
*/ */
package org.springframework.security.web.server.header; package org.springframework.security.web.server.header;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/** /**
* Combines multiple {@link ServerHttpHeadersWriter} instances into a single instance. * Combines multiple {@link ServerHttpHeadersWriter} instances into a single instance.
@@ -43,8 +41,9 @@ public class CompositeServerHttpHeadersWriter implements ServerHttpHeadersWriter
@Override @Override
public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) { public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
Stream<Mono<Void>> results = writers.stream().map( writer -> writer.writeHttpHeaders(exchange)); return Flux.fromIterable(this.writers)
return Mono.when(results.collect(Collectors.toList())); .concatMap(w -> w.writeHttpHeaders(exchange))
.then();
} }
} }
@@ -42,7 +42,6 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -82,7 +81,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
private LogoutHandler logoutHandler; private List<LogoutHandler> logoutHandlers;
HttpServlet3RequestFactory(String rolePrefix) { HttpServlet3RequestFactory(String rolePrefix) {
this.rolePrefix = rolePrefix; this.rolePrefix = rolePrefix;
@@ -146,7 +145,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
* {@link HttpServletRequest#logout()}. * {@link HttpServletRequest#logout()}.
*/ */
public void setLogoutHandlers(List<LogoutHandler> logoutHandlers) { public void setLogoutHandlers(List<LogoutHandler> logoutHandlers) {
this.logoutHandler = CollectionUtils.isEmpty(logoutHandlers) ? null : new CompositeLogoutHandler(logoutHandlers); this.logoutHandlers = logoutHandlers;
} }
/** /**
@@ -246,8 +245,8 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
@Override @Override
public void logout() throws ServletException { public void logout() throws ServletException {
LogoutHandler handler = HttpServlet3RequestFactory.this.logoutHandler; List<LogoutHandler> handlers = HttpServlet3RequestFactory.this.logoutHandlers;
if (handler == null) { if (CollectionUtils.isEmpty(handlers)) {
HttpServlet3RequestFactory.this.logger.debug( HttpServlet3RequestFactory.this.logger.debug(
"logoutHandlers is null, so allowing original HttpServletRequest to handle logout"); "logoutHandlers is null, so allowing original HttpServletRequest to handle logout");
super.logout(); super.logout();
@@ -255,7 +254,9 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
} }
Authentication authentication = SecurityContextHolder.getContext() Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication(); .getAuthentication();
handler.logout(this, this.response, authentication); for (LogoutHandler handler : handlers) {
handler.logout(this, this.response, authentication);
}
} }
private boolean isAuthenticated() { private boolean isAuthenticated() {
@@ -67,7 +67,7 @@ public final class AntPathRequestMatcher
/** /**
* Creates a matcher with the specific pattern which will match all HTTP methods in a * Creates a matcher with the specific pattern which will match all HTTP methods in a
* case insensitive manner. * case sensitive manner.
* *
* @param pattern the ant pattern to use for matching * @param pattern the ant pattern to use for matching
*/ */
@@ -76,7 +76,7 @@ public final class AntPathRequestMatcher
} }
/** /**
* Creates a matcher with the supplied pattern and HTTP method in a case insensitive * Creates a matcher with the supplied pattern and HTTP method in a case sensitive
* manner. * manner.
* *
* @param pattern the ant pattern to use for matching * @param pattern the ant pattern to use for matching
@@ -107,7 +107,7 @@ public final class AntPathRequestMatcher
* *
* @param pattern the ant pattern to use for matching * @param pattern the ant pattern to use for matching
* @param httpMethod the HTTP method. The {@code matches} method will return false if * @param httpMethod the HTTP method. The {@code matches} method will return false if
* the incoming request doesn't doesn't have the same method. * the incoming request doesn't have the same method.
* @param caseSensitive true if the matcher should consider case, else false * @param caseSensitive true if the matcher should consider case, else false
* @param urlPathHelper if non-null, will be used for extracting the path from the HttpServletRequest * @param urlPathHelper if non-null, will be used for extracting the path from the HttpServletRequest
*/ */
@@ -16,11 +16,17 @@
package org.springframework.security.web.authentication.switchuser; package org.springframework.security.web.authentication.switchuser;
import static org.assertj.core.api.Assertions.*; import java.util.ArrayList;
import static org.mockito.Mockito.*; import java.util.Collection;
import java.util.List;
import javax.servlet.FilterChain;
import org.junit.*; import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.AccountExpiredException;
@@ -42,8 +48,10 @@ import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import javax.servlet.FilterChain; import static org.assertj.core.api.Assertions.assertThat;
import java.util.*; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/** /**
* Tests * Tests
@@ -75,6 +83,7 @@ public class SwitchUserFilterTests {
request.setScheme("http"); request.setScheme("http");
request.setServerName("localhost"); request.setServerName("localhost");
request.setRequestURI("/login/impersonate"); request.setRequestURI("/login/impersonate");
request.setMethod("POST");
return request; return request;
} }
@@ -125,6 +134,20 @@ public class SwitchUserFilterTests {
assertThat(filter.requiresExitUser(request)).isFalse(); assertThat(filter.requiresExitUser(request)).isFalse();
} }
@Test
// gh-4183
public void requiresExitUserWhenGetThenDoesNotMatch() {
SwitchUserFilter filter = new SwitchUserFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setScheme("http");
request.setServerName("localhost");
request.setRequestURI("/login/impersonate");
request.setMethod("GET");
assertThat(filter.requiresExitUser(request)).isFalse();
}
@Test @Test
public void requiresExitUserWhenMatcherThenWorks() { public void requiresExitUserWhenMatcherThenWorks() {
SwitchUserFilter filter = new SwitchUserFilter(); SwitchUserFilter filter = new SwitchUserFilter();
@@ -159,6 +182,20 @@ public class SwitchUserFilterTests {
assertThat(filter.requiresSwitchUser(request)).isFalse(); assertThat(filter.requiresSwitchUser(request)).isFalse();
} }
@Test
// gh-4183
public void requiresSwitchUserWhenGetThenDoesNotMatch() {
SwitchUserFilter filter = new SwitchUserFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setScheme("http");
request.setServerName("localhost");
request.setRequestURI("/login/impersonate");
request.setMethod("GET");
assertThat(filter.requiresSwitchUser(request)).isFalse();
}
@Test @Test
public void requiresSwitchUserWhenMatcherThenWorks() { public void requiresSwitchUserWhenMatcherThenWorks() {
SwitchUserFilter filter = new SwitchUserFilter(); SwitchUserFilter filter = new SwitchUserFilter();
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.fail;
/** /**
* @author Rob Winch * @author Rob Winch
* @author Eddú Meléndez
*/ */
public class StrictHttpFirewallTests { public class StrictHttpFirewallTests {
public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.", public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.",
@@ -373,4 +374,30 @@ public class StrictHttpFirewallTests {
this.firewall.getFirewalledRequest(request); this.firewall.getFirewalledRequest(request);
} }
@Test
public void getFirewalledRequestWhenTrustedDomainThenNoException() {
String host = "example.org";
this.request.addHeader("Host", host);
this.firewall.setAllowedHostnames(hostname -> hostname.equals("example.org"));
try {
this.firewall.getFirewalledRequest(this.request);
} catch (RequestRejectedException fail) {
fail("Host " + host + " was rejected");
}
}
@Test
public void getFirewalledRequestWhenUntrustedDomainThenException() {
String host = "example.org";
this.request.addHeader("Host", host);
this.firewall.setAllowedHostnames(hostname -> hostname.equals("myexample.org"));
try {
this.firewall.getFirewalledRequest(this.request);
fail("Host " + host + " was accepted");
} catch (RequestRejectedException expected) {
}
}
} }
@@ -15,11 +15,6 @@
*/ */
package org.springframework.security.web.server.header; package org.springframework.security.web.server.header;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -28,10 +23,19 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/** /**
* *
* @author Rob Winch * @author Rob Winch
@@ -55,7 +59,6 @@ public class CompositeServerHttpHeadersWriterTests {
@Test @Test
public void writeHttpHeadersWhenErrorNoErrorThenError() { public void writeHttpHeadersWhenErrorNoErrorThenError() {
when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException())); when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException()));
when(writer2.writeHttpHeaders(exchange)).thenReturn(Mono.empty());
Mono<Void> result = writer.writeHttpHeaders(exchange); Mono<Void> result = writer.writeHttpHeaders(exchange);
@@ -64,13 +67,11 @@ public class CompositeServerHttpHeadersWriterTests {
.verify(); .verify();
verify(writer1).writeHttpHeaders(exchange); verify(writer1).writeHttpHeaders(exchange);
verify(writer2).writeHttpHeaders(exchange);
} }
@Test @Test
public void writeHttpHeadersWhenErrorErrorThenError() { public void writeHttpHeadersWhenErrorErrorThenError() {
when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException())); when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException()));
when(writer2.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException()));
Mono<Void> result = writer.writeHttpHeaders(exchange); Mono<Void> result = writer.writeHttpHeaders(exchange);
@@ -79,7 +80,6 @@ public class CompositeServerHttpHeadersWriterTests {
.verify(); .verify();
verify(writer1).writeHttpHeaders(exchange); verify(writer1).writeHttpHeaders(exchange);
verify(writer2).writeHttpHeaders(exchange);
} }
@Test @Test
@@ -96,4 +96,26 @@ public class CompositeServerHttpHeadersWriterTests {
verify(writer1).writeHttpHeaders(exchange); verify(writer1).writeHttpHeaders(exchange);
verify(writer2).writeHttpHeaders(exchange); verify(writer2).writeHttpHeaders(exchange);
} }
@Test
public void writeHttpHeadersSequential() throws Exception {
AtomicBoolean slowDone = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(1);
ServerHttpHeadersWriter slow = exchange ->
Mono.delay(Duration.ofMillis(100))
.doOnSuccess(__ -> slowDone.set(true))
.then();
ServerHttpHeadersWriter second = exchange ->
Mono.fromRunnable(() -> {
latch.countDown();
assertThat(slowDone.get())
.describedAs("ServerLogoutHandler should be executed sequentially")
.isTrue();
});
CompositeServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter(slow, second);
writer.writeHttpHeaders(this.exchange).block();
assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue();
}
} }