Compare commits
107 Commits
5.0.12.RELEASE
...
5.0.x
| Author | SHA1 | Date | |
|---|---|---|---|
| 21e092c141 | |||
| c7d9b4e7ec | |||
| 764685dd58 | |||
| 05349139a4 | |||
| 33aba4fd64 | |||
| 7641dc0f3b | |||
| 66b52c5d5f | |||
| d86b1492a2 | |||
| 50c1c5e082 | |||
| 2a9b41df61 | |||
| 430407ea45 | |||
| 7358eb2b48 | |||
| 50faf5b3f2 | |||
| 1fd4e940f4 | |||
| 8a8b389907 | |||
| d51dd0450d | |||
| 5c0b080eb6 | |||
| b5b3ef979f | |||
| d5c264792a | |||
| f280d6e90e | |||
| 2303ac3d6f | |||
| ef442def43 | |||
| 502d28f1b9 | |||
| aa5a42cfbc | |||
| 5802954130 | |||
| b4e51e7740 | |||
| e0169eaea8 | |||
| bcaad3a51c | |||
| 66f8a56b1b | |||
| 3e5b65f647 | |||
| c62789c976 | |||
| b02cae0a0c | |||
| af278b58b5 | |||
| ba81f6a06a | |||
| a39efaf883 | |||
| 7d5e032e25 | |||
| e21ef422e7 | |||
| 66f923dab7 | |||
| e8daeacd89 | |||
| 27f30b04cd | |||
| baa238e339 | |||
| cf5bd52121 | |||
| ef78626045 | |||
| 7f8efb7680 | |||
| 2c4d13ddef | |||
| ed2a646a69 | |||
| f8f4d960b5 | |||
| 76a4df0461 | |||
| 7eee6b102f | |||
| d69288e665 | |||
| 62bc17ea3f | |||
| fd2798ca95 | |||
| 173660c6ef | |||
| d6aa6a2246 | |||
| 1526c981aa | |||
| a9ddd363aa | |||
| 4edcac7651 | |||
| eecad9ee9a | |||
| 0191e1f11e | |||
| d73f0c5fb9 | |||
| 8d07698d94 | |||
| c9c9532447 | |||
| d2d9ce9e6b | |||
| df6e58ca1d | |||
| ffa1bc8083 | |||
| c91e456ae7 | |||
| 45d81ffc49 | |||
| 3a787e2c4f | |||
| b50e511745 | |||
| e3a11c98bd | |||
| d4ceabd7ac | |||
| 475a53233d | |||
| d245b0466a | |||
| d1162c39ce | |||
| 888aa15e20 | |||
| b92f105f35 | |||
| 8febecd43f | |||
| 425b7783b5 | |||
| b7ee7136b2 | |||
| 0d331b08b9 | |||
| c47d72fd12 | |||
| 248c956962 | |||
| 530def3af0 | |||
| e6049fce26 | |||
| 0105e95ba2 | |||
| ded544de14 | |||
| 7d845b6872 | |||
| 97818ebb22 | |||
| f2ee151d63 | |||
| 8da27b5f41 | |||
| 466b8a1d82 | |||
| 0532b6e665 | |||
| c94a79c3a9 | |||
| 09343eb240 | |||
| 90c509d3c5 | |||
| 3b79bff44d | |||
| 3d8819447f | |||
| 863e45ca6c | |||
| b520d36abb | |||
| b03fdd8e25 | |||
| 0f5e37172d | |||
| 7498248910 | |||
| f3a90df1ef | |||
| c3b4cabb9f | |||
| 8e3678c79d | |||
| facc7299e3 | |||
| b94e4c0197 |
-16
@@ -1,16 +0,0 @@
|
||||
language: java
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
script: ./gradlew build --refresh-dependencies --no-daemon --continue
|
||||
Vendored
-108
@@ -1,108 +0,0 @@
|
||||
def projectProperties = [
|
||||
[$class: 'BuildDiscarderProperty',
|
||||
strategy: [$class: 'LogRotator', numToKeepStr: '5']],
|
||||
pipelineTriggers([cron('@daily')])
|
||||
]
|
||||
properties(projectProperties)
|
||||
|
||||
def SUCCESS = hudson.model.Result.SUCCESS.toString()
|
||||
currentBuild.result = SUCCESS
|
||||
|
||||
try {
|
||||
parallel check: {
|
||||
stage('Check') {
|
||||
node {
|
||||
checkout scm
|
||||
try {
|
||||
sh "./gradlew clean check --refresh-dependencies --no-daemon --stacktrace"
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: check'
|
||||
throw e
|
||||
} finally {
|
||||
junit '**/build/*-results/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sonar: {
|
||||
stage('Sonar') {
|
||||
node {
|
||||
checkout scm
|
||||
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
|
||||
try {
|
||||
if ("master" == env.BRANCH_NAME) {
|
||||
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -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) {
|
||||
currentBuild.result = 'FAILED: sonar'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentBuild.result == 'SUCCESS') {
|
||||
parallel artifacts: {
|
||||
stage('Deploy Artifacts') {
|
||||
node {
|
||||
checkout scm
|
||||
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
|
||||
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
|
||||
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')]) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
docs: {
|
||||
stage('Deploy Docs') {
|
||||
node {
|
||||
checkout scm
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
schema: {
|
||||
stage('Deploy Schema') {
|
||||
node {
|
||||
checkout scm
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
def buildStatus = currentBuild.result
|
||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
||||
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
|
||||
|
||||
if(buildNotSuccess || lastBuildNotSuccess) {
|
||||
|
||||
stage('Notifiy') {
|
||||
node {
|
||||
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
|
||||
|
||||
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
|
||||
def details = """The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"""
|
||||
|
||||
emailext (
|
||||
subject: subject,
|
||||
body: details,
|
||||
recipientProviders: RECIPIENTS,
|
||||
to: "$SPRING_SECURITY_TEAM_EMAILS"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,6 +1,10 @@
|
||||
image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
|
||||
|
||||
image:https://travis-ci.org/spring-projects/spring-security.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-security"]
|
||||
[NOTE]
|
||||
======
|
||||
This branch of Spring Security has reached its https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions#supported-versions[End of Life], meaning that there are no further maintenance releases or security patches planned.
|
||||
Please migrate to a supported branch as soon as possible.
|
||||
======
|
||||
|
||||
= Spring Security
|
||||
|
||||
|
||||
+3
-4
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -131,10 +131,9 @@ public class AccessControlEntryImpl implements AccessControlEntry,
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.acl.hashCode();
|
||||
result = 31 * result + this.permission.hashCode();
|
||||
int result = this.permission.hashCode();
|
||||
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.auditSuccess ? 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -560,6 +560,25 @@ public class AclImplTests {
|
||||
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
|
||||
// ==================================================================================================
|
||||
|
||||
|
||||
+1
-1
@@ -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.
|
||||
*
|
||||
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
|
||||
|
||||
+4
@@ -16,8 +16,10 @@
|
||||
package org.springframework.security.config.annotation.configuration;
|
||||
|
||||
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.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
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
|
||||
*/
|
||||
@Configuration
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public class ObjectPostProcessorConfiguration {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public ObjectPostProcessor<Object> objectPostProcessor(
|
||||
AutowireCapableBeanFactory beanFactory) {
|
||||
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
|
||||
|
||||
+3
@@ -28,8 +28,10 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AdviceMode;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
@@ -80,6 +82,7 @@ import org.springframework.util.Assert;
|
||||
* @see EnableGlobalMethodSecurity
|
||||
*/
|
||||
@Configuration
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public class GlobalMethodSecurityConfiguration
|
||||
implements ImportAware, SmartInitializingSingleton {
|
||||
private static final Log logger = LogFactory
|
||||
|
||||
+4
@@ -15,14 +15,18 @@
|
||||
*/
|
||||
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.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
|
||||
|
||||
@Configuration
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
class Jsr250MetadataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource() {
|
||||
return new Jsr250MethodSecurityMetadataSource();
|
||||
}
|
||||
|
||||
+2
@@ -49,6 +49,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public DelegatingMethodSecurityMetadataSource methodMetadataSource() {
|
||||
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
|
||||
new DefaultMethodSecurityExpressionHandler());
|
||||
@@ -69,6 +70,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
|
||||
return new DefaultMethodSecurityExpressionHandler();
|
||||
}
|
||||
|
||||
+2
-1
@@ -584,6 +584,7 @@ public final class HttpSecurity extends
|
||||
|
||||
/**
|
||||
* Allows restricting access based upon the {@link HttpServletRequest} using
|
||||
* {@link RequestMatcher} implementations (i.e. via URL patterns).
|
||||
*
|
||||
* <h2>Example Configurations</h2>
|
||||
*
|
||||
@@ -721,7 +722,7 @@ public final class HttpSecurity extends
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return the {@link ServletApiConfigurer} for further customizations
|
||||
* @return the {@link CsrfConfigurer} for further customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
|
||||
|
||||
+8
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* 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.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.SecurityBuilder;
|
||||
@@ -74,6 +75,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
* @see WebSecurityConfiguration
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Evgeniy Cheban
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class WebSecurity extends
|
||||
@@ -383,6 +385,11 @@ public final class WebSecurity extends
|
||||
throws BeansException {
|
||||
this.defaultWebSecurityExpressionHandler
|
||||
.setApplicationContext(applicationContext);
|
||||
|
||||
try {
|
||||
this.defaultWebSecurityExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class));
|
||||
} catch (NoSuchBeanDefinitionException e) {}
|
||||
|
||||
try {
|
||||
this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
|
||||
PermissionEvaluator.class));
|
||||
|
||||
+11
@@ -332,6 +332,13 @@ public abstract class WebSecurityConfigurerAdapter implements
|
||||
/**
|
||||
* Override this method to configure {@link WebSecurity}. For example, if you wish to
|
||||
* ignore certain requests.
|
||||
*
|
||||
* Endpoints specified in this method will be ignored by Spring Security, meaning it
|
||||
* will not protect them from CSRF, XSS, Clickjacking, and so on.
|
||||
*
|
||||
* Instead, if you want to protect endpoints against common vulnerabilities, then see
|
||||
* {@link #configure(HttpSecurity)} and the {@link HttpSecurity#authorizeRequests}
|
||||
* configuration method.
|
||||
*/
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
}
|
||||
@@ -345,6 +352,10 @@ public abstract class WebSecurityConfigurerAdapter implements
|
||||
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
|
||||
* </pre>
|
||||
*
|
||||
* Any endpoint that requires defense against common vulnerabilities can be specified here, including public ones.
|
||||
* See {@link HttpSecurity#authorizeRequests} and the `permitAll()` authorization rule
|
||||
* for more details on public endpoints.
|
||||
*
|
||||
* @param http the {@link HttpSecurity} to modify
|
||||
* @throws Exception if an error occurs
|
||||
*/
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
|
||||
|
||||
/**
|
||||
* Specifies the {@link AuthenticationSuccessHandler} to be used. The default is
|
||||
* {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properites
|
||||
* {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properties
|
||||
* set.
|
||||
*
|
||||
* @param successHandler the {@link AuthenticationSuccessHandler}.
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
||||
*
|
||||
* <p>
|
||||
* Allows customizing the {@link XXssProtectionHeaderWriter} which adds the <a href=
|
||||
* "https://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx"
|
||||
* "https://web.archive.org/web/20160201174302/https://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx"
|
||||
* >X-XSS-Protection header</a>
|
||||
* </p>
|
||||
*
|
||||
|
||||
+1
-1
@@ -675,7 +675,7 @@ public class ServerHttpSecurity {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public AuthorizeExchangeSpec hasAuthority(String authority) {
|
||||
|
||||
+29
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* 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.
|
||||
@@ -45,6 +45,8 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
@@ -68,6 +70,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Joe Grandja
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
public class WebSecurityConfigurationTests {
|
||||
@Rule
|
||||
@@ -270,6 +273,31 @@ public class WebSecurityConfigurationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityExpressionHandlerWhenRoleHierarchyBeanThenRoleHierarchyUsed() {
|
||||
this.spring.register(WebSecurityExpressionHandlerRoleHierarchyBeanConfig.class).autowire();
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused", "ROLE_ADMIN");
|
||||
FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest("GET", ""),
|
||||
new MockHttpServletResponse(), new MockFilterChain());
|
||||
|
||||
AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class);
|
||||
EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation);
|
||||
Expression expression = handler.getExpressionParser()
|
||||
.parseExpression("hasRole('ROLE_USER')");
|
||||
boolean granted = expression.getValue(evaluationContext, Boolean.class);
|
||||
assertThat(granted).isTrue();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebSecurityExpressionHandlerRoleHierarchyBeanConfig extends WebSecurityConfigurerAdapter {
|
||||
@Bean
|
||||
RoleHierarchy roleHierarchy() {
|
||||
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
|
||||
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
|
||||
return roleHierarchy;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
|
||||
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
|
||||
|
||||
-2
@@ -17,7 +17,6 @@ package org.springframework.security.access.expression;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
@@ -158,7 +157,6 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
|
||||
|
||||
private Set<String> getAuthoritySet() {
|
||||
if (roles == null) {
|
||||
roles = new HashSet<>();
|
||||
Collection<? extends GrantedAuthority> userAuthorities = authentication
|
||||
.getAuthorities();
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
((CredentialsContainer) result).eraseCredentials();
|
||||
}
|
||||
|
||||
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
|
||||
// If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent
|
||||
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
|
||||
if (parentResult == null) {
|
||||
eventPublisher.publishAuthenticationSuccess(result);
|
||||
@@ -235,7 +235,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
"No AuthenticationProvider found for {0}"));
|
||||
}
|
||||
|
||||
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
|
||||
// If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent
|
||||
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
|
||||
if (parentException == null) {
|
||||
prepareException(lastException, authentication);
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ import org.springframework.util.Assert;
|
||||
* </property>
|
||||
* </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
|
||||
* gets configured, create a subclass of this and override the
|
||||
* {@link #configureJaas(Resource)} method.
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.SpringVersion;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
@@ -108,8 +109,9 @@ public class SpringSecurityCoreVersion {
|
||||
*/
|
||||
private static String getSpringVersion() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(SpringSecurityCoreVersion.class.getClassLoader().getResourceAsStream("META-INF/spring-security.versions"));
|
||||
try (InputStream is = SpringSecurityCoreVersion.class.getClassLoader()
|
||||
.getResourceAsStream("META-INF/spring-security.versions")) {
|
||||
properties.load(is);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+31
-29
@@ -132,13 +132,18 @@ public class SessionRegistryImpl implements SessionRegistry,
|
||||
sessionIds.put(sessionId,
|
||||
new SessionInformation(principal, sessionId, new Date()));
|
||||
|
||||
Set<String> sessionsUsedByPrincipal = principals.computeIfAbsent(principal, key -> new CopyOnWriteArraySet<>());
|
||||
sessionsUsedByPrincipal.add(sessionId);
|
||||
principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
|
||||
if (sessionsUsedByPrincipal == null) {
|
||||
sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
|
||||
}
|
||||
sessionsUsedByPrincipal.add(sessionId);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sessions used by '" + principal + "' : "
|
||||
+ sessionsUsedByPrincipal);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sessions used by '" + principal + "' : "
|
||||
+ sessionsUsedByPrincipal);
|
||||
}
|
||||
return sessionsUsedByPrincipal;
|
||||
});
|
||||
}
|
||||
|
||||
public void removeSessionInformation(String sessionId) {
|
||||
@@ -157,32 +162,29 @@ public class SessionRegistryImpl implements SessionRegistry,
|
||||
|
||||
sessionIds.remove(sessionId);
|
||||
|
||||
Set<String> sessionsUsedByPrincipal = principals.get(info.getPrincipal());
|
||||
|
||||
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
|
||||
principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Removing principal " + info.getPrincipal()
|
||||
+ " from registry");
|
||||
logger.debug("Removing session " + sessionId
|
||||
+ " from principal's set of registered sessions");
|
||||
}
|
||||
principals.remove(info.getPrincipal());
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sessions used by '" + info.getPrincipal() + "' : "
|
||||
+ sessionsUsedByPrincipal);
|
||||
}
|
||||
sessionsUsedByPrincipal.remove(sessionId);
|
||||
|
||||
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.notFound=User {0} not found
|
||||
LdapAuthenticationProvider.badCredentials=Bad credentials
|
||||
LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed
|
||||
LdapAuthenticationProvider.credentialsExpired=User credentials have expired
|
||||
LdapAuthenticationProvider.disabled=User is disabled
|
||||
LdapAuthenticationProvider.expired=User account has expired
|
||||
|
||||
+8
@@ -65,6 +65,10 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
}
|
||||
|
||||
public String encode(CharSequence rawPassword) {
|
||||
if (rawPassword == null) {
|
||||
throw new IllegalArgumentException("rawPassword cannot be null");
|
||||
}
|
||||
|
||||
String salt;
|
||||
if (strength > 0) {
|
||||
if (random != null) {
|
||||
@@ -81,6 +85,10 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
}
|
||||
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
if (rawPassword == null) {
|
||||
throw new IllegalArgumentException("rawPassword cannot be null");
|
||||
}
|
||||
|
||||
if (encodedPassword == null || encodedPassword.length() == 0) {
|
||||
logger.warn("Empty encoded password");
|
||||
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");
|
||||
* 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
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* @param password the password used to generate the encryptor's secret key; should
|
||||
* not be shared
|
||||
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
||||
* key
|
||||
*
|
||||
* @see #standard(CharSequence, CharSequence) which uses the slightly weaker CBC mode
|
||||
* (instead of GCM)
|
||||
*/
|
||||
public static BytesEncryptor stronger(CharSequence password, CharSequence 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
|
||||
* 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
|
||||
* 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.
|
||||
* 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
|
||||
* not be shared
|
||||
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
||||
* key
|
||||
*
|
||||
* @see #stronger(CharSequence, CharSequence), which uses the significatly more secure
|
||||
* GCM (instead of CBC)
|
||||
*/
|
||||
public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
|
||||
return new AesBytesEncryptor(password.toString(), salt,
|
||||
@@ -100,7 +105,10 @@ public class Encryptors {
|
||||
* not be shared
|
||||
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
||||
* 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) {
|
||||
return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(),
|
||||
salt));
|
||||
|
||||
-2
@@ -83,8 +83,6 @@ public class Md4PasswordEncoder implements PasswordEncoder {
|
||||
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
|
||||
private boolean encodeHashAsBase64;
|
||||
|
||||
private Digester digester;
|
||||
|
||||
|
||||
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
|
||||
this.encodeHashAsBase64 = encodeHashAsBase64;
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@ package org.springframework.security.crypto.password;
|
||||
* @deprecated This PasswordEncoder is not secure. Instead use an
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* 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
|
||||
public final class NoOpPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
+11
@@ -92,4 +92,15 @@ public class BCryptPasswordEncoderTests {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ asciidoctor {
|
||||
'gh-samples-url': "$ghUrl/samples"
|
||||
}
|
||||
|
||||
remotes {
|
||||
docs {
|
||||
retryCount = 5 // retry 5 times (default is 0)
|
||||
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
|
||||
}
|
||||
}
|
||||
|
||||
docsZip {
|
||||
from(project(':spring-security-docs-guides').asciidoctor) {
|
||||
into 'guides'
|
||||
|
||||
@@ -24,7 +24,7 @@ Finally, welcome to the Spring Security <<community,community>>.
|
||||
|
||||
[[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.
|
||||
|
||||
@@ -7980,14 +7980,17 @@ The Encryptors class provides factory methods for constructing symmetric encrypt
|
||||
|
||||
[[spring-security-crypto-encryption-bytes]]
|
||||
==== BytesEncryptor
|
||||
Use the Encryptors.standard factory method to construct a "standard" BytesEncryptor:
|
||||
Use the `Encryptors.stronger` factory method to construct a BytesEncryptor:
|
||||
|
||||
[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:
|
||||
|
||||
@@ -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
|
||||
----
|
||||
|
||||
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]]
|
||||
==== TextEncryptor
|
||||
Use the Encryptors.text factory method to construct a standard TextEncryptor:
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
gaeVersion=1.9.68
|
||||
springBootVersion=2.0.8.RELEASE
|
||||
version=5.0.12.RELEASE
|
||||
gaeVersion=1.9.82
|
||||
springBootVersion=2.0.9.RELEASE
|
||||
version=5.0.20.BUILD-SNAPSHOT
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
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.19.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR14'
|
||||
}
|
||||
dependencies {
|
||||
dependency 'cglib:cglib-nodep:3.2.10'
|
||||
dependency 'com.squareup.okhttp3:mockwebserver:3.12.2'
|
||||
dependency 'cglib:cglib-nodep:3.2.12'
|
||||
dependency 'com.squareup.okhttp3:mockwebserver:3.12.12'
|
||||
dependency 'opensymphony:sitemesh:2.4.2'
|
||||
dependency 'org.gebish:geb-spock:0.10.0'
|
||||
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
|
||||
dependency 'org.powermock:powermock-api-mockito2:2.0.0'
|
||||
dependency 'org.powermock:powermock-api-support:2.0.0'
|
||||
dependency 'org.powermock:powermock-core:2.0.0'
|
||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.0'
|
||||
dependency 'org.powermock:powermock-module-junit4:2.0.0'
|
||||
dependency 'org.powermock:powermock-reflect:2.0.0'
|
||||
dependency 'org.python:jython:2.5.0'
|
||||
dependency 'org.powermock:powermock-api-mockito2:2.0.7'
|
||||
dependency 'org.powermock:powermock-api-support:2.0.7'
|
||||
dependency 'org.powermock:powermock-core:2.0.7'
|
||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.7'
|
||||
dependency 'org.powermock:powermock-module-junit4:2.0.7'
|
||||
dependency 'org.powermock:powermock-reflect:2.0.7'
|
||||
dependency 'org.python:jython:2.5.3'
|
||||
dependency 'org.spockframework:spock-core: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 'ch.qos.logback:logback-classic: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-core:2.9.8'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.10'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.10'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.10.6'
|
||||
dependency 'com.fasterxml:classmate:1.3.4'
|
||||
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-labs:1.9.64'
|
||||
dependency 'com.google.appengine:appengine-api-stubs:1.9.64'
|
||||
dependency 'com.google.appengine:appengine-testing:1.9.64'
|
||||
dependency 'com.google.appengine:appengine:1.9.63'
|
||||
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.82'
|
||||
dependency 'com.google.appengine:appengine-api-labs:1.9.82'
|
||||
dependency 'com.google.appengine:appengine-api-stubs:1.9.82'
|
||||
dependency 'com.google.appengine:appengine-testing:1.9.82'
|
||||
dependency 'com.google.appengine:appengine:1.9.82'
|
||||
dependency 'com.google.code.gson:gson:2.8.2'
|
||||
dependency 'com.google.guava:guava:20.0'
|
||||
dependency 'com.google.inject:guice:3.0'
|
||||
dependency 'com.nimbusds:lang-tag:1.4.3'
|
||||
dependency 'com.nimbusds:nimbus-jose-jwt:5.14'
|
||||
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.sun.xml.bind:jaxb-core: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 'commons-cli:commons-cli:1.4'
|
||||
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-ldap-constants: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:httpmime:4.5.3'
|
||||
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-websocket:8.5.23'
|
||||
dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23'
|
||||
dependency 'org.aspectj:aspectjrt:1.9.2'
|
||||
dependency 'org.aspectj:aspectjtools:1.9.2'
|
||||
dependency 'org.aspectj:aspectjweaver:1.9.2'
|
||||
dependency 'org.aspectj:aspectjrt:1.9.6'
|
||||
dependency 'org.aspectj:aspectjtools:1.9.6'
|
||||
dependency 'org.aspectj:aspectjweaver:1.9.6'
|
||||
dependency 'org.assertj:assertj-core:3.11.1'
|
||||
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
|
||||
dependency 'org.bouncycastle:bcpkix-jdk15on:1.61'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.61'
|
||||
dependency 'org.codehaus.groovy:groovy-all:2.4.14'
|
||||
dependency 'org.codehaus.groovy:groovy-json:2.4.14'
|
||||
dependency 'org.codehaus.groovy:groovy:2.4.14'
|
||||
dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.64'
|
||||
dependency 'org.codehaus.groovy:groovy-all:2.4.20'
|
||||
dependency 'org.codehaus.groovy:groovy-json:2.4.20'
|
||||
dependency 'org.codehaus.groovy:groovy:2.4.20'
|
||||
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-client:9.4.7.v20170914'
|
||||
@@ -158,9 +158,9 @@ dependencyManagement {
|
||||
dependency 'org.hamcrest:hamcrest-core:1.3'
|
||||
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: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-validator:6.0.16.Final'
|
||||
dependency 'org.hibernate:hibernate-validator:6.0.21.Final'
|
||||
dependency 'org.hsqldb:hsqldb:2.4.1'
|
||||
dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
|
||||
dependency 'org.javassist:javassist:3.22.0-CR2'
|
||||
@@ -171,19 +171,19 @@ dependencyManagement {
|
||||
dependency 'org.objenesis:objenesis:2.6'
|
||||
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
|
||||
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:selenium-api:3.141.59'
|
||||
dependency 'org.seleniumhq.selenium:selenium-java:3.141.59'
|
||||
dependency 'org.seleniumhq.selenium:selenium-support:3.141.59'
|
||||
dependency 'org.skyscreamer:jsonassert:1.5.0'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.26'
|
||||
dependency 'org.slf4j:jul-to-slf4j:1.7.26'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.26'
|
||||
dependency 'org.slf4j:slf4j-api:1.7.26'
|
||||
dependency 'org.slf4j:slf4j-nop:1.7.26'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.30'
|
||||
dependency 'org.slf4j:jul-to-slf4j:1.7.30'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.30'
|
||||
dependency 'org.slf4j:slf4j-api:1.7.30'
|
||||
dependency 'org.slf4j:slf4j-nop:1.7.30'
|
||||
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.3.RELEASE'
|
||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'
|
||||
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
|
||||
dependency 'org.w3c.css:sac:1.3'
|
||||
|
||||
+144
@@ -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
|
||||
+17
-4
@@ -16,6 +16,7 @@
|
||||
package org.springframework.security.ldap.authentication.ad;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.ldap.CommunicationException;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
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.CredentialsExpiredException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -141,12 +143,15 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
|
||||
UsernamePasswordAuthenticationToken auth) {
|
||||
String username = auth.getName();
|
||||
String password = (String) auth.getCredentials();
|
||||
|
||||
DirContext ctx = bindAsUser(username, password);
|
||||
DirContext ctx = null;
|
||||
|
||||
try {
|
||||
ctx = bindAsUser(username, password);
|
||||
return searchForUser(ctx, username);
|
||||
}
|
||||
catch (CommunicationException e) {
|
||||
throw badLdapConnection(e);
|
||||
}
|
||||
catch (NamingException e) {
|
||||
logger.error("Failed to locate directory entry for authenticated user: "
|
||||
+ username, e);
|
||||
@@ -208,8 +213,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
|
||||
|| (e instanceof OperationNotSupportedException)) {
|
||||
handleBindException(bindPrincipal, e);
|
||||
throw badCredentials(e);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw LdapUtils.convertLdapException(e);
|
||||
}
|
||||
}
|
||||
@@ -311,6 +315,12 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
|
||||
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)
|
||||
throws NamingException {
|
||||
SearchControls searchControls = new SearchControls();
|
||||
@@ -325,6 +335,9 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
|
||||
searchControls, searchRoot, searchFilter,
|
||||
new Object[] { bindPrincipal, username });
|
||||
}
|
||||
catch (CommunicationException ldapCommunicationException) {
|
||||
throw badLdapConnection(ldapCommunicationException);
|
||||
}
|
||||
catch (IncorrectResultSizeDataAccessException incorrectResults) {
|
||||
// Search should never return multiple results if properly configured - just
|
||||
// rethrow
|
||||
|
||||
@@ -114,10 +114,10 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
|
||||
|
||||
private void importLdif(InMemoryDirectoryServer directoryServer) {
|
||||
if (StringUtils.hasText(this.ldif)) {
|
||||
Resource resource = this.context.getResource(this.ldif);
|
||||
try {
|
||||
if (resource.exists()) {
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
Resource[] resources = this.context.getResources(this.ldif);
|
||||
if (resources.length > 0 && resources[0].exists()) {
|
||||
try (InputStream inputStream = resources[0].getInputStream()) {
|
||||
directoryServer.importFromLDIF(false, new LDIFReader(inputStream));
|
||||
}
|
||||
}
|
||||
|
||||
+22
-6
@@ -32,6 +32,7 @@ import org.springframework.security.authentication.AccountExpiredException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -58,6 +59,9 @@ import static org.springframework.security.ldap.authentication.ad.ActiveDirector
|
||||
* @author Rob Winch
|
||||
*/
|
||||
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
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@@ -378,17 +382,29 @@ public class ActiveDirectoryLdapAuthenticationProviderTests {
|
||||
}
|
||||
|
||||
@Test(expected = org.springframework.ldap.CommunicationException.class)
|
||||
public void nonAuthenticationExceptionIsConvertedToSpringLdapException()
|
||||
throws Exception {
|
||||
provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
|
||||
msg));
|
||||
provider.authenticate(joe);
|
||||
public void nonAuthenticationExceptionIsConvertedToSpringLdapException() throws Throwable {
|
||||
try {
|
||||
provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
|
||||
msg));
|
||||
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
|
||||
public void rootDnProvidedSeparatelyFromDomainAlsoWorks() throws Exception {
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
Executable
+4
@@ -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"
|
||||
+1
-1
@@ -43,7 +43,7 @@ final class WithMockUserSecurityContextFactory implements
|
||||
.username() : withUser.value();
|
||||
if (username == null) {
|
||||
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<>();
|
||||
|
||||
@@ -60,7 +60,7 @@ import java.util.*;
|
||||
* requests which match the pattern. An example configuration might look like this:
|
||||
*
|
||||
* <pre>
|
||||
* <bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
|
||||
* <bean id="myfilterChainProxy" class="org.springframework.security.web.FilterChainProxy">
|
||||
* <constructor-arg>
|
||||
* <util:list>
|
||||
* <security:filter-chain pattern="/do/not/filter*" filters="none"/>
|
||||
|
||||
@@ -228,10 +228,15 @@ class DummyRequest extends HttpServletRequestWrapper {
|
||||
public void setQueryString(String queryString) {
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerName() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final class UnsupportedOperationExceptionInvocationHandler implements InvocationHandler {
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
throw new UnsupportedOperationException(method + " is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -563,6 +563,6 @@ public class SwitchUserFilter extends GenericFilterBean
|
||||
}
|
||||
|
||||
private static RequestMatcher createMatcher(String pattern) {
|
||||
return new AntPathRequestMatcher(pattern, null, true, new UrlPathHelper());
|
||||
return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper());
|
||||
}
|
||||
}
|
||||
|
||||
+34
-3
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
package org.springframework.security.web.firewall;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -59,10 +60,15 @@ import java.util.Set;
|
||||
* Rejects URLs that contain a URL encoded percent. See
|
||||
* {@link #setAllowUrlEncodedPercent(boolean)}
|
||||
* </li>
|
||||
* <li>
|
||||
* Rejects hosts that are not allowed. See
|
||||
* {@link #setAllowedHostnames(Predicate)}
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @see DefaultHttpFirewall
|
||||
* @author Rob Winch
|
||||
* @author Eddú Meléndez
|
||||
* @since 5.0.1
|
||||
*/
|
||||
public class StrictHttpFirewall implements HttpFirewall {
|
||||
@@ -82,6 +88,8 @@ public class StrictHttpFirewall implements HttpFirewall {
|
||||
|
||||
private Set<String> decodedUrlBlacklist = new HashSet<String>();
|
||||
|
||||
private Predicate<String> allowedHostnames = hostname -> true;
|
||||
|
||||
public StrictHttpFirewall() {
|
||||
urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
|
||||
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) {
|
||||
this.encodedUrlBlacklist.addAll(values);
|
||||
this.decodedUrlBlacklist.addAll(values);
|
||||
@@ -243,6 +266,7 @@ public class StrictHttpFirewall implements HttpFirewall {
|
||||
@Override
|
||||
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
|
||||
rejectedBlacklistedUrls(request);
|
||||
rejectedUntrustedHosts(request);
|
||||
|
||||
if (!isNormalized(request)) {
|
||||
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
|
||||
public HttpServletResponse getFirewalledResponse(HttpServletResponse 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -54,6 +54,7 @@ import java.util.Set;
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Parikshit Dutta
|
||||
* @since 5.0
|
||||
*/
|
||||
public class CsrfWebFilter implements WebFilter {
|
||||
@@ -133,7 +134,7 @@ public class CsrfWebFilter implements WebFilter {
|
||||
@Override
|
||||
public Mono<MatchResult> matches(ServerWebExchange exchange) {
|
||||
return Mono.just(exchange.getRequest())
|
||||
.map(r -> r.getMethod())
|
||||
.flatMap(r -> Mono.justOrEmpty(r.getMethod()))
|
||||
.filter(m -> ALLOWED_METHODS.contains(m))
|
||||
.flatMap(m -> MatchResult.notMatch())
|
||||
.switchIfEmpty(MatchResult.match());
|
||||
|
||||
+7
-8
@@ -15,14 +15,12 @@
|
||||
*/
|
||||
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.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.
|
||||
@@ -43,8 +41,9 @@ public class CompositeServerHttpHeadersWriter implements ServerHttpHeadersWriter
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
|
||||
Stream<Mono<Void>> results = writers.stream().map( writer -> writer.writeHttpHeaders(exchange));
|
||||
return Mono.when(results.collect(Collectors.toList()));
|
||||
return Flux.fromIterable(this.writers)
|
||||
.concatMap(w -> w.writeHttpHeaders(exchange))
|
||||
.then();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-6
@@ -42,7 +42,6 @@ import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
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.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -82,7 +81,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
|
||||
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||
private AuthenticationEntryPoint authenticationEntryPoint;
|
||||
private AuthenticationManager authenticationManager;
|
||||
private LogoutHandler logoutHandler;
|
||||
private List<LogoutHandler> logoutHandlers;
|
||||
|
||||
HttpServlet3RequestFactory(String rolePrefix) {
|
||||
this.rolePrefix = rolePrefix;
|
||||
@@ -146,7 +145,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
|
||||
* {@link HttpServletRequest#logout()}.
|
||||
*/
|
||||
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
|
||||
public void logout() throws ServletException {
|
||||
LogoutHandler handler = HttpServlet3RequestFactory.this.logoutHandler;
|
||||
if (handler == null) {
|
||||
List<LogoutHandler> handlers = HttpServlet3RequestFactory.this.logoutHandlers;
|
||||
if (CollectionUtils.isEmpty(handlers)) {
|
||||
HttpServlet3RequestFactory.this.logger.debug(
|
||||
"logoutHandlers is null, so allowing original HttpServletRequest to handle logout");
|
||||
super.logout();
|
||||
@@ -255,7 +254,9 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
|
||||
}
|
||||
Authentication authentication = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
handler.logout(this, this.response, authentication);
|
||||
for (LogoutHandler handler : handlers) {
|
||||
handler.logout(this, this.response, authentication);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAuthenticated() {
|
||||
|
||||
+3
-3
@@ -67,7 +67,7 @@ public final class AntPathRequestMatcher
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -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.
|
||||
*
|
||||
* @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 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 urlPathHelper if non-null, will be used for extracting the path from the HttpServletRequest
|
||||
*/
|
||||
|
||||
+42
-5
@@ -16,11 +16,17 @@
|
||||
|
||||
package org.springframework.security.web.authentication.switchuser;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import java.util.ArrayList;
|
||||
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.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
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.util.matcher.AnyRequestMatcher;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import java.util.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests
|
||||
@@ -75,6 +83,7 @@ public class SwitchUserFilterTests {
|
||||
request.setScheme("http");
|
||||
request.setServerName("localhost");
|
||||
request.setRequestURI("/login/impersonate");
|
||||
request.setMethod("POST");
|
||||
|
||||
return request;
|
||||
}
|
||||
@@ -125,6 +134,20 @@ public class SwitchUserFilterTests {
|
||||
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
|
||||
public void requiresExitUserWhenMatcherThenWorks() {
|
||||
SwitchUserFilter filter = new SwitchUserFilter();
|
||||
@@ -159,6 +182,20 @@ public class SwitchUserFilterTests {
|
||||
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
|
||||
public void requiresSwitchUserWhenMatcherThenWorks() {
|
||||
SwitchUserFilter filter = new SwitchUserFilter();
|
||||
|
||||
+28
-1
@@ -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");
|
||||
* 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 Eddú Meléndez
|
||||
*/
|
||||
public class StrictHttpFirewallTests {
|
||||
public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.",
|
||||
@@ -373,4 +374,30 @@ public class StrictHttpFirewallTests {
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-10
@@ -15,11 +15,6 @@
|
||||
*/
|
||||
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.Test;
|
||||
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.web.server.MockServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
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
|
||||
@@ -55,7 +59,6 @@ public class CompositeServerHttpHeadersWriterTests {
|
||||
@Test
|
||||
public void writeHttpHeadersWhenErrorNoErrorThenError() {
|
||||
when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException()));
|
||||
when(writer2.writeHttpHeaders(exchange)).thenReturn(Mono.empty());
|
||||
|
||||
Mono<Void> result = writer.writeHttpHeaders(exchange);
|
||||
|
||||
@@ -64,13 +67,11 @@ public class CompositeServerHttpHeadersWriterTests {
|
||||
.verify();
|
||||
|
||||
verify(writer1).writeHttpHeaders(exchange);
|
||||
verify(writer2).writeHttpHeaders(exchange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHttpHeadersWhenErrorErrorThenError() {
|
||||
when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException()));
|
||||
when(writer2.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException()));
|
||||
|
||||
Mono<Void> result = writer.writeHttpHeaders(exchange);
|
||||
|
||||
@@ -79,7 +80,6 @@ public class CompositeServerHttpHeadersWriterTests {
|
||||
.verify();
|
||||
|
||||
verify(writer1).writeHttpHeaders(exchange);
|
||||
verify(writer2).writeHttpHeaders(exchange);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -96,4 +96,26 @@ public class CompositeServerHttpHeadersWriterTests {
|
||||
verify(writer1).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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user