Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08e5289829 | |||
| 742e5fd7a9 | |||
| 795c073f48 | |||
| 58c9cbb471 | |||
| e40bc262bb | |||
| b0d865e28f | |||
| 0f7f0042ac | |||
| e0f1121d5b | |||
| ea93ed4165 | |||
| 96acf33019 | |||
| 35dbc0e6a8 | |||
| 0526c5233c | |||
| 60c011a976 | |||
| 916a19ccbd | |||
| 8138fac3f9 | |||
| 62fadfd28a | |||
| 166f48e6ab | |||
| 0f97086c86 | |||
| fe5c6d6621 | |||
| c642de537a | |||
| fd27c07b55 | |||
| e0d95c7c8a | |||
| 9d76c98791 | |||
| e923371724 | |||
| 216512bc54 | |||
| 8445e91b22 | |||
| 13ccb83d6f | |||
| 127d9eece9 | |||
| 7891787a53 | |||
| 2d8b6650db | |||
| 78995ff0a9 | |||
| 7974f3161d | |||
| c35c1c0643 | |||
| 2162221589 | |||
| 040fb6aa3c | |||
| b152218ee0 | |||
| 544e421157 | |||
| 0f612bf637 | |||
| c683bc10bf | |||
| 11ab23f4f4 | |||
| 0065b55a75 | |||
| d6f9d2e34a | |||
| 5dedbb6283 | |||
| 4cad151b57 | |||
| 72080bb5fe | |||
| 5854f00977 | |||
| 93118f4e91 | |||
| 8796850816 | |||
| cee2ea9c60 | |||
| 1159c9f302 | |||
| 6c5ce1237d | |||
| f81b58112b | |||
| 0e02b489c8 | |||
| d1669b909f | |||
| cb8041ba67 | |||
| 6f74162a1f | |||
| 99a0baadfa | |||
| 1f9a0e579f | |||
| 9e7d08c9e7 | |||
| 82168faf9d | |||
| 9d0f8977a9 | |||
| 5ae615f3b4 | |||
| d2b0077392 | |||
| 092c5aecf7 | |||
| a5d56d8724 | |||
| 7c0da854da | |||
| 0f546dcb07 | |||
| cb576d16e1 | |||
| 3b4df40f47 | |||
| 6cbf71bd72 | |||
| cd63329b63 | |||
| cc7f504f96 | |||
| a094563052 | |||
| da19435f21 | |||
| be50cd8ada | |||
| 21efbb6ba7 | |||
| c7cf6fdd73 | |||
| 8129bf2ce0 | |||
| d48079eb19 | |||
| 45f1179b52 | |||
| e11dfa7578 | |||
| 6cc0f6c054 | |||
| 22ea835643 | |||
| 9f51d68e92 | |||
| 32af1884f7 | |||
| a88035196a | |||
| 0db94b1d36 | |||
| 9e8994a2b7 | |||
| 8b2faff7ad | |||
| 469bc20e6d | |||
| 947d11f433 | |||
| b3a60a83f6 | |||
| 5bc7e4171c | |||
| 80e96b0f7b |
+3
-1
@@ -20,4 +20,6 @@ build/
|
||||
.gradle/
|
||||
atlassian-ide-plugin.xml
|
||||
!etc/eclipse/.checkstyle
|
||||
.checkstyle
|
||||
.checkstyle
|
||||
classes/
|
||||
s101plugin.state
|
||||
|
||||
+7
-13
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-acl</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>spring-security-acl</name>
|
||||
<description>spring-security-acl</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -51,7 +51,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -95,14 +95,14 @@
|
||||
<dependency>
|
||||
<groupId>net.sf.ehcache</groupId>
|
||||
<artifactId>ehcache</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>2.10.5</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -120,7 +120,7 @@
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -132,7 +132,7 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -151,12 +151,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -61,14 +61,14 @@ public class AuditLoggerTests {
|
||||
public void nonAuditableAceIsIgnored() {
|
||||
AccessControlEntry ace = mock(AccessControlEntry.class);
|
||||
logger.logIfNeeded(true, ace);
|
||||
assertThat(bytes.size()).isEqualTo(0);
|
||||
assertThat(bytes.size()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successIsNotLoggedIfAceDoesntRequireSuccessAudit() throws Exception {
|
||||
when(ace.isAuditSuccess()).thenReturn(false);
|
||||
logger.logIfNeeded(true, ace);
|
||||
assertThat(bytes.size()).isEqualTo(0);
|
||||
assertThat(bytes.size()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -76,20 +76,20 @@ public class AuditLoggerTests {
|
||||
when(ace.isAuditSuccess()).thenReturn(true);
|
||||
|
||||
logger.logIfNeeded(true, ace);
|
||||
assertThat(bytes.toString().startsWith("GRANTED due to ACE")).isTrue();
|
||||
assertThat(bytes.toString()).startsWith("GRANTED due to ACE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failureIsntLoggedIfAceDoesntRequireFailureAudit() throws Exception {
|
||||
when(ace.isAuditFailure()).thenReturn(false);
|
||||
logger.logIfNeeded(false, ace);
|
||||
assertThat(bytes.size()).isEqualTo(0);
|
||||
assertThat(bytes.size()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failureIsLoggedIfAceRequiresFailureAudit() throws Exception {
|
||||
when(ace.isAuditFailure()).thenReturn(true);
|
||||
logger.logIfNeeded(false, ace);
|
||||
assertThat(bytes.toString().startsWith("DENIED due to ACE")).isTrue();
|
||||
assertThat(bytes.toString()).startsWith("DENIED due to ACE");
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -137,9 +137,9 @@ public class ObjectIdentityImplTests {
|
||||
|
||||
String string = "SOME_STRING";
|
||||
assertThat(string).isNotSameAs(obj);
|
||||
assertThat(obj.equals(null)).isFalse();
|
||||
assertThat(obj.equals("DIFFERENT_OBJECT_TYPE")).isFalse();
|
||||
assertThat(obj.equals(new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(2)))).isFalse();
|
||||
assertThat(obj).isNotNull();
|
||||
assertThat(obj).isNotEqualTo("DIFFERENT_OBJECT_TYPE");
|
||||
assertThat(obj).isNotEqualTo(new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(2)));
|
||||
assertThat(obj).isNotEqualTo(new ObjectIdentityImpl(
|
||||
"org.springframework.security.acls.domain.ObjectIdentityImplTests$MockOtherIdDomainObject",
|
||||
Long.valueOf(1)));
|
||||
@@ -151,7 +151,7 @@ public class ObjectIdentityImplTests {
|
||||
public void hashcodeIsDifferentForDifferentJavaTypes() throws Exception {
|
||||
ObjectIdentity obj = new ObjectIdentityImpl(Object.class, Long.valueOf(1));
|
||||
ObjectIdentity obj2 = new ObjectIdentityImpl(String.class, Long.valueOf(1));
|
||||
assertThat(obj.hashCode() == obj2.hashCode()).isFalse();
|
||||
assertThat(obj.hashCode()).isNotEqualTo(obj2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -175,7 +175,7 @@ public class ObjectIdentityImplTests {
|
||||
public void stringAndNumericIdsAreNotEqual() throws Exception {
|
||||
ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000");
|
||||
ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, Long.valueOf(1000));
|
||||
assertThat(obj.equals(obj2)).isFalse();
|
||||
assertThat(obj).isNotEqualTo(obj2);
|
||||
}
|
||||
|
||||
// ~ Inner Classes
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -104,17 +104,17 @@ public class SpringCacheBasedAclCacheTests {
|
||||
// Try to evict an entry that doesn't exist
|
||||
myCache.evictFromCache(Long.valueOf(3));
|
||||
myCache.evictFromCache(new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102)));
|
||||
assertThat(4).isEqualTo(realCache.size());
|
||||
assertThat(realCache).hasSize(4);
|
||||
|
||||
myCache.evictFromCache(Long.valueOf(1));
|
||||
assertThat(2).isEqualTo(realCache.size());
|
||||
assertThat(realCache).hasSize(2);
|
||||
|
||||
// Check the second object inserted
|
||||
assertThat(acl2).isEqualTo(myCache.getFromCache(Long.valueOf(2)));
|
||||
assertThat(acl2).isEqualTo(myCache.getFromCache(identity2));
|
||||
|
||||
myCache.evictFromCache(identity2);
|
||||
assertThat(0).isEqualTo(realCache.size());
|
||||
assertThat(realCache).isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
||||
+6
-12
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-aspects</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>spring-security-aspects</name>
|
||||
<description>spring-security-aspects</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -45,7 +45,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -79,7 +79,7 @@
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<version>1.8.4</version>
|
||||
<version>1.8.13</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -92,7 +92,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -116,7 +116,7 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -130,12 +130,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
+8
-5
@@ -4,13 +4,14 @@ buildscript {
|
||||
maven { url "https://repo.spring.io/plugins-snapshot" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.github.ben-manes:gradle-versions-plugin:0.17.0"
|
||||
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7")
|
||||
classpath("io.spring.gradle:spring-io-plugin:0.0.6.RELEASE")
|
||||
classpath("com.bmuschko:gradle-tomcat-plugin:2.2.4")
|
||||
classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1')
|
||||
classpath('org.asciidoctor:asciidoctor-gradle-plugin:1.5.1')
|
||||
classpath("io.spring.gradle:docbook-reference-plugin:0.3.1")
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.0.BUILD-SNAPSHOT")
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +26,12 @@ description = 'Spring Security'
|
||||
allprojects {
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: "com.github.ben-manes.versions"
|
||||
|
||||
ext.releaseBuild = version.endsWith('RELEASE')
|
||||
ext.snapshotBuild = version.endsWith('SNAPSHOT')
|
||||
ext.springVersion = '4.3.5.RELEASE'
|
||||
ext.springLdapVersion = '2.2.0.RELEASE'
|
||||
ext.springVersion = '4.3.19.RELEASE'
|
||||
ext.springLdapVersion = '2.3.2.RELEASE'
|
||||
|
||||
group = 'org.springframework.security'
|
||||
|
||||
@@ -106,7 +108,7 @@ configure(coreModuleProjects) {
|
||||
apply plugin: 'emma'
|
||||
apply plugin: 'spring-io'
|
||||
|
||||
ext.springIoVersion = project.hasProperty('platformVersion') ? platformVersion : 'Athens-BUILD-SNAPSHOT'
|
||||
ext.springIoVersion = project.hasProperty('platformVersion') ? platformVersion : 'Brussels-SR9'
|
||||
|
||||
configurations {
|
||||
jacoco //Configuration Group used by Sonar to provide Code Coverage using JaCoCo
|
||||
@@ -117,6 +119,7 @@ configure(coreModuleProjects) {
|
||||
imports {
|
||||
mavenBom("io.spring.platform:platform-bom:${springIoVersion}") {
|
||||
bomProperties([
|
||||
'assertj.version': '2.2.0',
|
||||
'mockito.version': '1.10.19'
|
||||
])
|
||||
}
|
||||
@@ -124,7 +127,7 @@ configure(coreModuleProjects) {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
jacoco "org.jacoco:org.jacoco.agent:0.7.5.201505241946:runtime"
|
||||
jacoco "org.jacoco:org.jacoco.agent:0.7.9:runtime"
|
||||
}
|
||||
test {
|
||||
jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=${project.group}.*"
|
||||
|
||||
+9
-15
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-cas</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>spring-security-cas</name>
|
||||
<description>spring-security-cas</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -51,13 +51,13 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -89,7 +89,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.8.4</version>
|
||||
<version>2.8.11.2</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -103,7 +103,7 @@
|
||||
<dependency>
|
||||
<groupId>net.sf.ehcache</groupId>
|
||||
<artifactId>ehcache</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>2.10.5</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -116,7 +116,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -140,13 +140,13 @@
|
||||
<dependency>
|
||||
<groupId>org.skyscreamer</groupId>
|
||||
<artifactId>jsonassert</artifactId>
|
||||
<version>1.3.0</version>
|
||||
<version>1.4.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -155,12 +155,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
+3
-3
@@ -55,7 +55,7 @@ public class CasAuthenticationTokenMixinTests {
|
||||
|
||||
public static final String AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON + "]]";
|
||||
|
||||
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.ArrayList\", [" + AUTHORITY_JSON + "]]";
|
||||
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", [" + AUTHORITY_JSON + "]]";
|
||||
|
||||
// @formatter:off
|
||||
public static final String USER_JSON = "{"
|
||||
@@ -84,14 +84,14 @@ public class CasAuthenticationTokenMixinTests {
|
||||
+ "\"principal\": {"
|
||||
+ "\"@class\": \"org.jasig.cas.client.authentication.AttributePrincipalImpl\", "
|
||||
+ "\"name\": \"assertName\", "
|
||||
+ "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}, "
|
||||
+ "\"attributes\": {\"@class\": \"java.util.HashMap\"}, "
|
||||
+ "\"proxyGrantingTicket\": null, "
|
||||
+ "\"proxyRetriever\": null"
|
||||
+ "}, "
|
||||
+ "\"validFromDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
|
||||
+ "\"validUntilDate\": [\"java.util.Date\", " + END_DATE.getTime() + "],"
|
||||
+ "\"authenticationDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
|
||||
+ "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}" +
|
||||
+ "\"attributes\": {\"@class\": \"java.util.HashMap\"}" +
|
||||
"}"
|
||||
+ "}";
|
||||
|
||||
|
||||
+3
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -57,10 +57,7 @@ public class GrantedAuthorityFromAssertionAttributesUserDetailsServiceTests {
|
||||
assertion, "ticket");
|
||||
UserDetails user = uds.loadUserDetails(token);
|
||||
Set<String> roles = AuthorityUtils.authorityListToSet(user.getAuthorities());
|
||||
assertThat(roles.size()).isEqualTo(4);
|
||||
assertThat(roles).contains("role_a1");
|
||||
assertThat(roles).contains("role_a2");
|
||||
assertThat(roles).contains("role_b");
|
||||
assertThat(roles).contains("role_c");
|
||||
assertThat(roles).containsOnly(
|
||||
"role_a1", "role_a2", "role_b", "role_c");
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -16,11 +16,6 @@
|
||||
|
||||
package org.springframework.security.cas.web;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
|
||||
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
@@ -35,9 +30,13 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests {@link CasAuthenticationFilter}.
|
||||
*
|
||||
@@ -146,8 +145,7 @@ public class CasAuthenticationFilterTests {
|
||||
.createAuthorityList("ROLE_ANONYMOUS")));
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new TestingAuthenticationToken("un", "principal", AuthorityUtils
|
||||
.createAuthorityList("ROLE_ANONYMOUS")));
|
||||
new TestingAuthenticationToken("un", "principal"));
|
||||
assertThat(filter.requiresAuthentication(request, response)).isTrue();
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new TestingAuthenticationToken("un", "principal", "ROLE_ANONYMOUS"));
|
||||
|
||||
+22
-28
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>spring-security-config</name>
|
||||
<description>spring-security-config</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -51,7 +51,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -90,35 +90,35 @@
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>1.8.4</version>
|
||||
<version>1.8.13</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-ldap</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-messaging</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-openid</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -167,7 +167,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -245,13 +245,13 @@
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-entitymanager</artifactId>
|
||||
<version>5.0.11.Final</version>
|
||||
<version>5.0.12.Final</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -275,7 +275,7 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -287,37 +287,37 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-support</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-core</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4-common</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-reflect</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -347,7 +347,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-jpa</artifactId>
|
||||
<version>1.10.2.RELEASE</version>
|
||||
<version>1.11.12.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -359,19 +359,19 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.ldap</groupId>
|
||||
<artifactId>spring-ldap-core</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.3.2.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-aspects</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-cas</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -390,12 +390,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
+9
-2
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
@@ -42,6 +43,8 @@ import java.util.List;
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;
|
||||
|
||||
private ApplicationContext context;
|
||||
@@ -160,8 +163,12 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
String... mvcPatterns) {
|
||||
boolean isServlet30 = ClassUtils.isPresent("javax.servlet.ServletRegistration", getClass().getClassLoader());
|
||||
ObjectPostProcessor<Object> opp = this.context.getBean(ObjectPostProcessor.class);
|
||||
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector(
|
||||
this.context);
|
||||
if(!this.context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME +" of type " + HandlerMappingIntrospector.class.getName()
|
||||
+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
|
||||
}
|
||||
HandlerMappingIntrospector introspector = this.context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
|
||||
HandlerMappingIntrospector.class);
|
||||
List<MvcRequestMatcher> matchers = new ArrayList<MvcRequestMatcher>(
|
||||
mvcPatterns.length);
|
||||
for (String mvcPattern : mvcPatterns) {
|
||||
|
||||
+6
-2
@@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -47,7 +48,7 @@ import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.debug.DebugFilter;
|
||||
import org.springframework.security.web.firewall.DefaultHttpFirewall;
|
||||
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||
import org.springframework.security.web.firewall.HttpFirewall;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
@@ -159,7 +160,7 @@ public final class WebSecurity extends
|
||||
|
||||
/**
|
||||
* Allows customizing the {@link HttpFirewall}. The default is
|
||||
* {@link DefaultHttpFirewall}.
|
||||
* {@link StrictHttpFirewall}.
|
||||
*
|
||||
* @param httpFirewall the custom {@link HttpFirewall}
|
||||
* @return the {@link WebSecurity} for further customizations
|
||||
@@ -382,5 +383,8 @@ public final class WebSecurity extends
|
||||
this.defaultWebSecurityExpressionHandler
|
||||
.setApplicationContext(applicationContext);
|
||||
this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
|
||||
try {
|
||||
this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
|
||||
} catch(NoSuchBeanDefinitionException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
@@ -98,7 +99,9 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static class MvcCorsFilter {
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
/**
|
||||
* This needs to be isolated into a separate class as Spring MVC is an optional
|
||||
* dependency and will potentially cause ClassLoading issues
|
||||
@@ -106,9 +109,12 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
* @return
|
||||
*/
|
||||
private static CorsFilter getMvcCorsFilter(ApplicationContext context) {
|
||||
HandlerMappingIntrospector mappingIntrospector = new HandlerMappingIntrospector(
|
||||
context);
|
||||
if(!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
throw new NoSuchBeanDefinitionException(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, "A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME +" of type " + HandlerMappingIntrospector.class.getName()
|
||||
+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
|
||||
}
|
||||
HandlerMappingIntrospector mappingIntrospector = context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
|
||||
return new CorsFilter(mappingIntrospector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -15,17 +15,16 @@
|
||||
*/
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* Parser for the {@code CorsFilter}.
|
||||
@@ -71,8 +70,6 @@ public class CorsBeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
BeanDefinitionBuilder configurationSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(HANDLER_MAPPING_INTROSPECTOR);
|
||||
configurationSourceBldr.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
|
||||
return configurationSourceBldr.getBeanDefinition();
|
||||
return new RootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-3
@@ -17,6 +17,8 @@
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
@@ -28,12 +30,22 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
* @author Rob Winch
|
||||
* @since 4.1.1
|
||||
*/
|
||||
class HandlerMappingIntrospectorFactoryBean implements ApplicationContextAware {
|
||||
class HandlerMappingIntrospectorFactoryBean implements FactoryBean<HandlerMappingIntrospector>, ApplicationContextAware {
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
HandlerMappingIntrospector createHandlerMappingIntrospector() {
|
||||
return new HandlerMappingIntrospector(this.context);
|
||||
public HandlerMappingIntrospector getObject() {
|
||||
if(!this.context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
throw new NoSuchBeanDefinitionException(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, "A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME +" of type " + HandlerMappingIntrospector.class.getName()
|
||||
+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
|
||||
}
|
||||
return this.context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return HandlerMappingIntrospector.class;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -48,4 +60,8 @@ class HandlerMappingIntrospectorFactoryBean implements ApplicationContextAware {
|
||||
this.context = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@ public enum MatcherType {
|
||||
ant(AntPathRequestMatcher.class), regex(RegexRequestMatcher.class), ciRegex(
|
||||
RegexRequestMatcher.class), mvc(MvcRequestMatcher.class);
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME = "org.springframework.security.config.http.HandlerMappingIntrospectorFactoryBean";
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private static final String ATT_MATCHER_TYPE = "request-matcher";
|
||||
|
||||
@@ -61,18 +60,7 @@ public enum MatcherType {
|
||||
.rootBeanDefinition(type);
|
||||
|
||||
if (this == mvc) {
|
||||
if (!pc.getRegistry().isBeanNameInUse(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
BeanDefinitionBuilder hmifb = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class);
|
||||
pc.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME,
|
||||
hmifb.getBeanDefinition());
|
||||
|
||||
RootBeanDefinition hmi = new RootBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME);
|
||||
hmi.setFactoryBeanName(HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME);
|
||||
hmi.setFactoryMethodName("createHandlerMappingIntrospector");
|
||||
pc.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, hmi);
|
||||
}
|
||||
matcherBldr.addConstructorArgReference(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME);
|
||||
matcherBldr.addConstructorArgValue(new RootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class));
|
||||
}
|
||||
|
||||
matcherBldr.addConstructorArgValue(path);
|
||||
|
||||
+7
-11
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException
|
||||
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
@@ -36,19 +39,12 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
*/
|
||||
class CorsConfigurerTests extends BaseSpringSpec {
|
||||
|
||||
def "HandlerMappingIntrospector default"() {
|
||||
setup:
|
||||
loadConfig(DefaultCorsConfig)
|
||||
def "No MVC throws meaningful error"() {
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
loadConfig(DefaultCorsConfig)
|
||||
then:
|
||||
responseHeaders == ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'DENY',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block']
|
||||
BeanCreationException success = thrown()
|
||||
success.message.contains("Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext")
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
+25
-1
@@ -13,7 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
package org.springframework.security.config.annotation.web.configurers
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
@@ -89,6 +91,28 @@ public class NamespaceHttpFirewallTests extends BaseSpringSpec {
|
||||
}
|
||||
}
|
||||
|
||||
def "http-firewall bean"() {
|
||||
setup:
|
||||
loadConfig(CustomHttpFirewallBeanConfig)
|
||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
||||
request.setParameter("deny", "true")
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "the custom firewall is used"
|
||||
thrown(RequestRejectedException)
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomHttpFirewallBeanConfig extends BaseWebConfig {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) { }
|
||||
|
||||
@Bean
|
||||
CustomHttpFirewall firewall() {
|
||||
return new CustomHttpFirewall();
|
||||
}
|
||||
}
|
||||
|
||||
static class CustomHttpFirewall extends DefaultHttpFirewall {
|
||||
|
||||
@Override
|
||||
|
||||
+6
-11
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException
|
||||
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
import org.springframework.http.*
|
||||
@@ -38,24 +40,17 @@ class HttpCorsConfigTests extends AbstractHttpConfigTests {
|
||||
chain = new MockFilterChain()
|
||||
}
|
||||
|
||||
def "HandlerMappingIntrospector default"() {
|
||||
setup:
|
||||
def "No MVC throws meaningful error"() {
|
||||
when:
|
||||
xml.http('entry-point-ref' : 'ep') {
|
||||
'cors'()
|
||||
'intercept-url'(pattern:'/**', access: 'authenticated')
|
||||
}
|
||||
bean('ep', Http403ForbiddenEntryPoint)
|
||||
createAppContext()
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then:
|
||||
responseHeaders == ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'DENY',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block']
|
||||
BeanCreationException success = thrown()
|
||||
success.message.contains("Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext")
|
||||
}
|
||||
|
||||
def "HandlerMappingIntrospector explicit"() {
|
||||
|
||||
+1
@@ -252,6 +252,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
||||
'http-basic'()
|
||||
'intercept-url'(pattern: '/user/{un}/**', access: "#un == 'user'")
|
||||
}
|
||||
xml.'mvc:annotation-driven'()
|
||||
createWebAppContext(servletContext)
|
||||
when: 'user can access'
|
||||
request.servletPath = '/user/user/abc'
|
||||
|
||||
+4
@@ -38,6 +38,7 @@ import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.firewall.DefaultHttpFirewall;
|
||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
@@ -80,6 +81,7 @@ public class FilterChainProxyConfigTests {
|
||||
public void normalOperationWithNewConfig() throws Exception {
|
||||
FilterChainProxy filterChainProxy = appCtx.getBean("newFilterChainProxy",
|
||||
FilterChainProxy.class);
|
||||
filterChainProxy.setFirewall(new DefaultHttpFirewall());
|
||||
checkPathAndFilterOrder(filterChainProxy);
|
||||
doNormalOperation(filterChainProxy);
|
||||
}
|
||||
@@ -88,6 +90,7 @@ public class FilterChainProxyConfigTests {
|
||||
public void normalOperationWithNewConfigRegex() throws Exception {
|
||||
FilterChainProxy filterChainProxy = appCtx.getBean("newFilterChainProxyRegex",
|
||||
FilterChainProxy.class);
|
||||
filterChainProxy.setFirewall(new DefaultHttpFirewall());
|
||||
checkPathAndFilterOrder(filterChainProxy);
|
||||
doNormalOperation(filterChainProxy);
|
||||
}
|
||||
@@ -96,6 +99,7 @@ public class FilterChainProxyConfigTests {
|
||||
public void normalOperationWithNewConfigNonNamespace() throws Exception {
|
||||
FilterChainProxy filterChainProxy = appCtx.getBean(
|
||||
"newFilterChainProxyNonNamespace", FilterChainProxy.class);
|
||||
filterChainProxy.setFirewall(new DefaultHttpFirewall());
|
||||
checkPathAndFilterOrder(filterChainProxy);
|
||||
doNormalOperation(filterChainProxy);
|
||||
}
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ public class HttpSecurityHeadersTests {
|
||||
mockMvc.perform(get("/resources/file.js"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL,"max-age=12345"))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, ""))
|
||||
.andExpect(header().string(HttpHeaders.EXPIRES, ""));
|
||||
.andExpect(header().doesNotExist(HttpHeaders.PRAGMA))
|
||||
.andExpect(header().doesNotExist(HttpHeaders.EXPIRES));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -299,7 +299,7 @@ public class MessageSecurityMetadataSourceRegistryTests {
|
||||
if (attrs == null) {
|
||||
return null;
|
||||
}
|
||||
assertThat(attrs.size()).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
return attrs.iterator().next().toString();
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -65,8 +65,7 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
|
||||
.getBean("fids");
|
||||
Collection<ConfigAttribute> cad = fids
|
||||
.getAttributes(createFilterInvocation("/anything", "GET"));
|
||||
assertThat(cad).isNotNull();
|
||||
assertThat(cad.contains(new SecurityConfig("ROLE_A"))).isTrue();
|
||||
assertThat(cad).contains(new SecurityConfig("ROLE_A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,7 +79,7 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
|
||||
ConfigAttribute[] cad = fids
|
||||
.getAttributes(createFilterInvocation("/anything", "GET"))
|
||||
.toArray(new ConfigAttribute[0]);
|
||||
assertThat(cad.length).isEqualTo(1);
|
||||
assertThat(cad).hasSize(1);
|
||||
assertThat(cad[0].toString()).isEqualTo("hasRole('ROLE_A')");
|
||||
}
|
||||
|
||||
@@ -98,9 +97,7 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
|
||||
.getBean("fids");
|
||||
Collection<ConfigAttribute> cad = fids
|
||||
.getAttributes(createFilterInvocation("/secure", "GET"));
|
||||
assertThat(cad).isNotNull();
|
||||
assertThat(cad).hasSize(1);
|
||||
assertThat(cad.contains(new SecurityConfig("ROLE_A"))).isTrue();
|
||||
assertThat(cad).containsExactly(new SecurityConfig("ROLE_A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -108,7 +108,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
|
||||
|
||||
// SEC-1213. Check the order
|
||||
Advisor[] advisors = ((Advised) target).getAdvisors();
|
||||
assertThat(advisors.length).isEqualTo(1);
|
||||
assertThat(advisors).hasSize(1);
|
||||
assertThat(((MethodSecurityMetadataSourceAdvisor) advisors[0]).getOrder()).isEqualTo(1001);
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
|
||||
target = (BusinessService) appContext.getBean("target");
|
||||
Object[] arg = new String[] { "joe", "bob", "sam" };
|
||||
Object[] result = target.methodReturningAnArray(arg);
|
||||
assertThat(result.length).isEqualTo(1);
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result[0]).isEqualTo("bob");
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -66,11 +66,11 @@ public class InterceptMethodsBeanDefinitionDecoratorTests implements
|
||||
@Test
|
||||
public void targetDoesntLoseApplicationListenerInterface() {
|
||||
assertThat(appContext.getBeansOfType(ApplicationListener.class)).hasSize(1);
|
||||
assertThat(appContext.getBeanNamesForType(ApplicationListener.class).length).isEqualTo(1);
|
||||
assertThat(appContext.getBeanNamesForType(ApplicationListener.class)).hasSize(1);
|
||||
appContext.publishEvent(new AuthenticationSuccessEvent(
|
||||
new TestingAuthenticationToken("user", "")));
|
||||
|
||||
assertThat(target instanceof ApplicationListener<?>).isTrue();
|
||||
assertThat(target).isInstanceOf(ApplicationListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+15
-21
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>spring-security-core</name>
|
||||
<description>spring-security-core</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -82,7 +82,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.8.4</version>
|
||||
<version>2.8.11.2</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -103,14 +103,14 @@
|
||||
<dependency>
|
||||
<groupId>net.sf.ehcache</groupId>
|
||||
<artifactId>ehcache</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>2.10.5</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<version>1.8.4</version>
|
||||
<version>1.8.13</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -129,7 +129,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -171,7 +171,7 @@
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -189,7 +189,7 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -201,7 +201,7 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-support</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -213,7 +213,7 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-core</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -225,7 +225,7 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -237,7 +237,7 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4-common</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -249,7 +249,7 @@
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-reflect</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<version>1.6.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -261,7 +261,7 @@
|
||||
<dependency>
|
||||
<groupId>org.skyscreamer</groupId>
|
||||
<artifactId>jsonassert</artifactId>
|
||||
<version>1.3.0</version>
|
||||
<version>1.4.0</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -273,7 +273,7 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -294,12 +294,6 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
+3
-3
@@ -16,11 +16,11 @@
|
||||
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} implementation that is
|
||||
* designed for use whilst unit testing.
|
||||
@@ -49,7 +49,6 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
|
||||
public TestingAuthenticationToken(Object principal, Object credentials,
|
||||
String... authorities) {
|
||||
this(principal, credentials, AuthorityUtils.createAuthorityList(authorities));
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
public TestingAuthenticationToken(Object principal, Object credentials,
|
||||
@@ -57,6 +56,7 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
// ~ Methods
|
||||
|
||||
+3
@@ -22,7 +22,10 @@ package org.springframework.security.authentication.encoding;
|
||||
* </p>
|
||||
*
|
||||
* @author colin sampaleanu
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.PasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BaseDigestPasswordEncoder extends BasePasswordEncoder {
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
|
||||
+3
@@ -22,7 +22,10 @@ package org.springframework.security.authentication.encoding;
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.PasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BasePasswordEncoder implements PasswordEncoder {
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
+2
@@ -32,6 +32,8 @@ import org.springframework.util.Assert;
|
||||
* and non-encoded passwords are in use or when a null implementation is required.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @deprecated @deprecated This is deprecated and marked for deletion. Replace with
|
||||
* of {@link org.springframework.security.crypto.password.LdapShaPasswordEncoder}
|
||||
*/
|
||||
public class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||
// ~ Static fields/initializers
|
||||
|
||||
+3
@@ -31,7 +31,10 @@ import org.springframework.security.crypto.codec.Utf8;
|
||||
* legacy applications, it's not secure, don't use it for anything new!
|
||||
*
|
||||
* @author Alan Stewart
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.Md4PasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public class Md4PasswordEncoder extends BaseDigestPasswordEncoder {
|
||||
|
||||
// ~ Methods
|
||||
|
||||
+4
@@ -33,7 +33,11 @@ package org.springframework.security.authentication.encoding;
|
||||
* @author Ray Krueger
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.MessageDigestPasswordEncoder}
|
||||
* with an algorithm of "MD5"
|
||||
*/
|
||||
@Deprecated
|
||||
public class Md5PasswordEncoder extends MessageDigestPasswordEncoder {
|
||||
|
||||
public Md5PasswordEncoder() {
|
||||
|
||||
+3
@@ -51,7 +51,10 @@ import org.springframework.util.Assert;
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
* @since 1.0.1
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.MessageDigestPasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
|
||||
|
||||
private final String algorithm;
|
||||
|
||||
+3
@@ -29,7 +29,10 @@ import java.util.Locale;
|
||||
*
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @deprecated This class will be removed in Spring Security 5. For passivity switch to
|
||||
* {@link org.springframework.security.crypto.password.NoOpPasswordEncoder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class PlaintextPasswordEncoder extends BasePasswordEncoder {
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
|
||||
+4
@@ -40,7 +40,11 @@ package org.springframework.security.authentication.encoding;
|
||||
* @author Ray Krueger
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.MessageDigestPasswordEncoder}
|
||||
* with an algorithm "SHA-$strength" (i.e. "SHA-1" or "SHA-256").
|
||||
*/
|
||||
@Deprecated
|
||||
public class ShaPasswordEncoder extends MessageDigestPasswordEncoder {
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.SmartApplicationListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Used for delegating to a number of SmartApplicationListener instances. This is useful
|
||||
@@ -32,7 +32,7 @@ import java.util.List;
|
||||
*/
|
||||
public final class DelegatingApplicationListener implements
|
||||
ApplicationListener<ApplicationEvent> {
|
||||
private List<SmartApplicationListener> listeners = new ArrayList<SmartApplicationListener>();
|
||||
private List<SmartApplicationListener> listeners = new CopyOnWriteArrayList<SmartApplicationListener>();
|
||||
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event == null) {
|
||||
|
||||
@@ -40,7 +40,7 @@ public class SpringSecurityCoreVersion {
|
||||
*/
|
||||
public static final long SERIAL_VERSION_UID = 420L;
|
||||
|
||||
static final String MIN_SPRING_VERSION = "4.3.5.RELEASE";
|
||||
static final String MIN_SPRING_VERSION = "4.3.19.RELEASE";
|
||||
|
||||
static {
|
||||
performVersionChecks();
|
||||
|
||||
+12
-2
@@ -48,13 +48,23 @@ public class SessionRegistryImpl implements SessionRegistry,
|
||||
protected final Log logger = LogFactory.getLog(SessionRegistryImpl.class);
|
||||
|
||||
/** <principal:Object,SessionIdSet> */
|
||||
private final ConcurrentMap<Object, Set<String>> principals = new ConcurrentHashMap<Object, Set<String>>();
|
||||
private final ConcurrentMap<Object, Set<String>> principals;
|
||||
/** <sessionId:Object,SessionInformation> */
|
||||
private final Map<String, SessionInformation> sessionIds = new ConcurrentHashMap<String, SessionInformation>();
|
||||
private final Map<String, SessionInformation> sessionIds;
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
public SessionRegistryImpl() {
|
||||
this.principals = new ConcurrentHashMap<Object, Set<String>>();
|
||||
this.sessionIds = new ConcurrentHashMap<String, SessionInformation>();
|
||||
}
|
||||
|
||||
public SessionRegistryImpl(ConcurrentMap<Object, Set<String>> principals,Map<String, SessionInformation> sessionIds) {
|
||||
this.principals=principals;
|
||||
this.sessionIds=sessionIds;
|
||||
}
|
||||
|
||||
public List<Object> getAllPrincipals() {
|
||||
return new ArrayList<Object>(principals.keySet());
|
||||
}
|
||||
|
||||
@@ -27,11 +27,16 @@ import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.CredentialsContainer;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -58,6 +63,8 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private static final Log logger = LogFactory.getLog(User.class);
|
||||
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
private String password;
|
||||
@@ -244,8 +251,99 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UserBuilder with a specified user name
|
||||
*
|
||||
* @param username the username to use
|
||||
* @return the UserBuilder
|
||||
*/
|
||||
public static UserBuilder withUsername(String username) {
|
||||
return new UserBuilder().username(username);
|
||||
return builder().username(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UserBuilder
|
||||
*
|
||||
* @return the UserBuilder
|
||||
*/
|
||||
public static UserBuilder builder() {
|
||||
return new UserBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* <b>WARNING:</b> This method is considered unsafe for production and is only intended
|
||||
* for sample applications.
|
||||
* </p>
|
||||
* <p>
|
||||
* Creates a user and automatically encodes the provided password using
|
||||
* {@code PasswordEncoderFactories.createDelegatingPasswordEncoder()}. For example:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* UserDetails user = User.withDefaultPasswordEncoder()
|
||||
* .username("user")
|
||||
* .password("password")
|
||||
* .roles("USER")
|
||||
* .build();
|
||||
* // outputs {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
||||
* System.out.println(user.getPassword());
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* This is not safe for production (it is intended for getting started experience)
|
||||
* because the password "password" is compiled into the source code and then is
|
||||
* included in memory at the time of creation. This means there are still ways to
|
||||
* recover the plain text password making it unsafe. It does provide a slight
|
||||
* improvement to using plain text passwords since the UserDetails password is
|
||||
* securely hashed. This means if the UserDetails password is accidentally exposed,
|
||||
* the password is securely stored.
|
||||
*
|
||||
* In a production setting, it is recommended to hash the password ahead of time.
|
||||
* For example:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
* // outputs {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
||||
* // remember the password that is printed out and use in the next step
|
||||
* System.out.println(encoder.encode("password"));
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* UserDetails user = User.withUsername("user")
|
||||
* .password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG")
|
||||
* .roles("USER")
|
||||
* .build();
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @return a UserBuilder that automatically encodes the password with the default
|
||||
* PasswordEncoder
|
||||
* @deprecated Using this method is not considered safe for production, but is
|
||||
* acceptable for demos and getting started. For production purposes, ensure the
|
||||
* password is encoded externally. See the method Javadoc for additional details.
|
||||
* There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is considered insecure for production purposes.
|
||||
*/
|
||||
@Deprecated
|
||||
public static UserBuilder withDefaultPasswordEncoder() {
|
||||
logger.warn("User.withDefaultPasswordEncoder() is considered unsafe for production and is only intended for sample applications.");
|
||||
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
return builder().passwordEncoder(encoder);
|
||||
}
|
||||
|
||||
public static UserBuilder withUserDetails(UserDetails userDetails) {
|
||||
return withUsername(userDetails.getUsername())
|
||||
.password(userDetails.getPassword())
|
||||
.accountExpired(!userDetails.isAccountNonExpired())
|
||||
.accountLocked(!userDetails.isAccountNonLocked())
|
||||
.authorities(userDetails.getAuthorities())
|
||||
.credentialsExpired(!userDetails.isCredentialsNonExpired())
|
||||
.disabled(!userDetails.isEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,6 +358,7 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
private boolean accountLocked;
|
||||
private boolean credentialsExpired;
|
||||
private boolean disabled;
|
||||
private PasswordEncoder passwordEncoder = NoOpPasswordEncoder.getInstance();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
@@ -274,7 +373,7 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
* @return the {@link UserBuilder} for method chaining (i.e. to populate
|
||||
* additional attributes for this user)
|
||||
*/
|
||||
private UserBuilder username(String username) {
|
||||
public UserBuilder username(String username) {
|
||||
Assert.notNull(username, "username cannot be null");
|
||||
this.username = username;
|
||||
return this;
|
||||
@@ -293,6 +392,20 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the current password (if non-null) and any future passwords supplied
|
||||
* to {@link #password(String)}.
|
||||
*
|
||||
* @param encoder the encoder to use
|
||||
* @return the {@link UserBuilder} for method chaining (i.e. to populate
|
||||
* additional attributes for this user)
|
||||
*/
|
||||
private UserBuilder passwordEncoder(PasswordEncoder encoder) {
|
||||
Assert.notNull(encoder, "encoder cannot be null");
|
||||
this.passwordEncoder = encoder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the roles. This method is a shortcut for calling
|
||||
* {@link #authorities(String...)}, but automatically prefixes each entry with
|
||||
@@ -351,7 +464,7 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
* additional attributes for this user)
|
||||
* @see #roles(String...)
|
||||
*/
|
||||
public UserBuilder authorities(List<? extends GrantedAuthority> authorities) {
|
||||
public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
|
||||
this.authorities = new ArrayList<GrantedAuthority>(authorities);
|
||||
return this;
|
||||
}
|
||||
@@ -418,7 +531,8 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
}
|
||||
|
||||
public UserDetails build() {
|
||||
return new User(username, password, !disabled, !accountExpired,
|
||||
String encodedPassword = this.passwordEncoder.encode(password);
|
||||
return new User(username, encodedPassword, !disabled, !accountExpired,
|
||||
!credentialsExpired, !accountLocked, authorities);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -302,7 +302,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport
|
||||
* Allows the default query string used to retrieve authorities based on username to
|
||||
* be overridden, if default table or column names need to be changed. The default
|
||||
* query is {@link #DEF_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query,
|
||||
* ensure that all returned columns are mapped back to the same column names as in the
|
||||
* ensure that all returned columns are mapped back to the same column positions as in the
|
||||
* default query.
|
||||
*
|
||||
* @param queryString The SQL query string to set
|
||||
@@ -320,7 +320,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport
|
||||
* username to be overridden, if default table or column names need to be changed. The
|
||||
* default query is {@link #DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY}; when modifying
|
||||
* this query, ensure that all returned columns are mapped back to the same column
|
||||
* names as in the default query.
|
||||
* positions as in the default query.
|
||||
*
|
||||
* @param queryString The SQL query string to set
|
||||
*/
|
||||
@@ -370,7 +370,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport
|
||||
* Allows the default query string used to retrieve users based on username to be
|
||||
* overridden, if default table or column names need to be changed. The default query
|
||||
* is {@link #DEF_USERS_BY_USERNAME_QUERY}; when modifying this query, ensure that all
|
||||
* returned columns are mapped back to the same column names as in the default query.
|
||||
* returned columns are mapped back to the same column positions as in the default query.
|
||||
* If the 'enabled' column does not exist in the source database, a permanent true
|
||||
* value for this column may be returned by using a query similar to
|
||||
*
|
||||
|
||||
@@ -58,6 +58,7 @@ public class CoreJackson2Module extends SimpleModule {
|
||||
context.setMixInAnnotations(RememberMeAuthenticationToken.class, RememberMeAuthenticationTokenMixin.class);
|
||||
context.setMixInAnnotations(SimpleGrantedAuthority.class, SimpleGrantedAuthorityMixin.class);
|
||||
context.setMixInAnnotations(Collections.<Object>unmodifiableSet(Collections.emptySet()).getClass(), UnmodifiableSetMixin.class);
|
||||
context.setMixInAnnotations(Collections.<Object>unmodifiableList(Collections.emptyList()).getClass(), UnmodifiableListMixin.class);
|
||||
context.setMixInAnnotations(User.class, UserMixin.class);
|
||||
context.setMixInAnnotations(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class);
|
||||
}
|
||||
|
||||
+118
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,17 +16,18 @@
|
||||
|
||||
package org.springframework.security.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotation;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.jsontype.*;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This utility class will find all the SecurityModules in classpath.
|
||||
@@ -65,7 +66,7 @@ public final class SecurityJackson2Modules {
|
||||
if(mapper != null) {
|
||||
TypeResolverBuilder<?> typeBuilder = mapper.getDeserializationConfig().getDefaultTyper(null);
|
||||
if (typeBuilder == null) {
|
||||
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
||||
mapper.setDefaultTyping(createWhitelistedDefaultTyping());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,4 +104,113 @@ public final class SecurityJackson2Modules {
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TypeResolverBuilder that performs whitelisting.
|
||||
* @return a TypeResolverBuilder that performs whitelisting.
|
||||
*/
|
||||
private static TypeResolverBuilder<? extends TypeResolverBuilder> createWhitelistedDefaultTyping() {
|
||||
TypeResolverBuilder<? extends TypeResolverBuilder> result = new WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
result = result.init(JsonTypeInfo.Id.CLASS, null);
|
||||
result = result.inclusion(JsonTypeInfo.As.PROPERTY);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of {@link ObjectMapper.DefaultTypeResolverBuilder} that overrides the {@link TypeIdResolver}
|
||||
* with {@link WhitelistTypeIdResolver}.
|
||||
* @author Rob Winch
|
||||
*/
|
||||
static class WhitelistTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
|
||||
|
||||
public WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
|
||||
super(defaultTyping);
|
||||
}
|
||||
|
||||
protected TypeIdResolver idResolver(MapperConfig<?> config,
|
||||
JavaType baseType, Collection<NamedType> subtypes, boolean forSer, boolean forDeser) {
|
||||
TypeIdResolver result = super.idResolver(config, baseType, subtypes, forSer, forDeser);
|
||||
return new WhitelistTypeIdResolver(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link TypeIdResolver} that delegates to an existing implementation and throws an IllegalStateException if the
|
||||
* class being looked up is not whitelisted, does not provide an explicit mixin, and is not annotated with Jackson
|
||||
* mappings. See https://github.com/spring-projects/spring-security/issues/4370
|
||||
*/
|
||||
static class WhitelistTypeIdResolver implements TypeIdResolver {
|
||||
private static final Set<String> WHITELIST_CLASS_NAMES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
|
||||
"java.util.ArrayList",
|
||||
"java.util.Collections$EmptyMap",
|
||||
"java.util.Collections$UnmodifiableRandomAccessList",
|
||||
"java.util.Date",
|
||||
"java.util.TreeMap",
|
||||
"java.util.HashMap",
|
||||
"org.springframework.security.core.context.SecurityContextImpl"
|
||||
)));
|
||||
|
||||
private final TypeIdResolver delegate;
|
||||
|
||||
WhitelistTypeIdResolver(TypeIdResolver delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(JavaType baseType) {
|
||||
delegate.init(baseType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idFromValue(Object value) {
|
||||
return delegate.idFromValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idFromValueAndType(Object value, Class<?> suggestedType) {
|
||||
return delegate.idFromValueAndType(value, suggestedType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idFromBaseType() {
|
||||
return delegate.idFromBaseType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
|
||||
DeserializationConfig config = (DeserializationConfig) context.getConfig();
|
||||
JavaType result = delegate.typeFromId(context, id);
|
||||
String className = result.getRawClass().getName();
|
||||
if(isWhitelisted(className)) {
|
||||
return delegate.typeFromId(context, id);
|
||||
}
|
||||
boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
|
||||
if(isExplicitMixin) {
|
||||
return result;
|
||||
}
|
||||
JacksonAnnotation jacksonAnnotation = AnnotationUtils.findAnnotation(result.getRawClass(), JacksonAnnotation.class);
|
||||
if(jacksonAnnotation != null) {
|
||||
return result;
|
||||
}
|
||||
throw new IllegalArgumentException("The class with " + id + " and name of " + className + " is not whitelisted. " +
|
||||
"If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. " +
|
||||
"If the serialization is only done by a trusted source, you can also enable default typing. " +
|
||||
"See https://github.com/spring-projects/spring-security/issues/4370 for details");
|
||||
}
|
||||
|
||||
private boolean isWhitelisted(String id) {
|
||||
return WHITELIST_CLASS_NAMES.contains(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescForKnownTypeIds() {
|
||||
return delegate.getDescForKnownTypeIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonTypeInfo.Id getMechanism() {
|
||||
return delegate.getMechanism();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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
|
||||
*
|
||||
* http://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.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Custom deserializer for {@link UnmodifiableListMixin}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @see UnmodifiableSetMixin
|
||||
* @since 4.2
|
||||
*/
|
||||
class UnmodifiableListDeserializer extends JsonDeserializer<List> {
|
||||
|
||||
@Override
|
||||
public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
||||
JsonNode node = mapper.readTree(jp);
|
||||
List<Object> result = new ArrayList<Object>();
|
||||
if (node != null) {
|
||||
if (node instanceof ArrayNode) {
|
||||
ArrayNode arrayNode = (ArrayNode) node;
|
||||
Iterator<JsonNode> nodeIterator = arrayNode.iterator();
|
||||
while (nodeIterator.hasNext()) {
|
||||
JsonNode elementNode = nodeIterator.next();
|
||||
result.add(mapper.readValue(elementNode.traverse(mapper), Object.class));
|
||||
}
|
||||
} else {
|
||||
result.add(mapper.readValue(node.traverse(mapper), Object.class));
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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
|
||||
*
|
||||
* http://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.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This mixin class used to deserialize java.util.Collections$UnmodifiableRandomAccessList
|
||||
* and used with various AuthenticationToken implementation's mixin classes.
|
||||
*
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new CoreJackson2Module());
|
||||
* </pre>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @see UnmodifiableListDeserializer
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
* @since 4.2
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonDeserialize(using = UnmodifiableListDeserializer.class)
|
||||
class UnmodifiableListMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor
|
||||
* @param s the Set
|
||||
*/
|
||||
@JsonCreator
|
||||
UnmodifiableListMixin(Set<?> s) {}
|
||||
}
|
||||
+2
-2
@@ -50,10 +50,10 @@ class UnmodifiableSetDeserializer extends JsonDeserializer<Set> {
|
||||
Iterator<JsonNode> nodeIterator = arrayNode.iterator();
|
||||
while (nodeIterator.hasNext()) {
|
||||
JsonNode elementNode = nodeIterator.next();
|
||||
resultSet.add(mapper.readValue(elementNode.toString(), Object.class));
|
||||
resultSet.add(mapper.readValue(elementNode.traverse(mapper), Object.class));
|
||||
}
|
||||
} else {
|
||||
resultSet.add(mapper.readValue(node.toString(), Object.class));
|
||||
resultSet.add(mapper.readValue(node.traverse(mapper), Object.class));
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(resultSet);
|
||||
|
||||
+20
-8
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.security.jackson2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
@@ -24,12 +27,9 @@ import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.MissingNode;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Custom deserializer for {@link UsernamePasswordAuthenticationToken}. At the time of deserialization
|
||||
@@ -40,6 +40,7 @@ import java.util.List;
|
||||
* you can also registered it with your own mixin class.
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @author Greg Turnquist
|
||||
* @see UsernamePasswordAuthenticationTokenMixin
|
||||
* @since 4.2
|
||||
*/
|
||||
@@ -62,20 +63,31 @@ class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<U
|
||||
JsonNode principalNode = readJsonNode(jsonNode, "principal");
|
||||
Object principal = null;
|
||||
if(principalNode.isObject()) {
|
||||
principal = mapper.readValue(principalNode.toString(), new TypeReference<User>() {});
|
||||
principal = mapper.readValue(principalNode.traverse(mapper), Object.class);
|
||||
} else {
|
||||
principal = principalNode.asText();
|
||||
}
|
||||
Object credentials = readJsonNode(jsonNode, "credentials").asText();
|
||||
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
|
||||
Object credentials;
|
||||
if (credentialsNode.isNull()) {
|
||||
credentials = null;
|
||||
} else {
|
||||
credentials = credentialsNode.asText();
|
||||
}
|
||||
List<GrantedAuthority> authorities = mapper.readValue(
|
||||
readJsonNode(jsonNode, "authorities").toString(), new TypeReference<List<GrantedAuthority>>() {
|
||||
readJsonNode(jsonNode, "authorities").traverse(mapper), new TypeReference<List<GrantedAuthority>>() {
|
||||
});
|
||||
if (authenticated) {
|
||||
token = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
|
||||
} else {
|
||||
token = new UsernamePasswordAuthenticationToken(principal, credentials);
|
||||
}
|
||||
token.setDetails(readJsonNode(jsonNode, "details"));
|
||||
JsonNode detailsNode = readJsonNode(jsonNode, "details");
|
||||
if (detailsNode.isNull()) {
|
||||
token.setDetails(null);
|
||||
} else {
|
||||
token.setDetails(detailsNode);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -61,6 +61,12 @@ public class InMemoryUserDetailsManager implements UserDetailsManager {
|
||||
}
|
||||
}
|
||||
|
||||
public InMemoryUserDetailsManager(UserDetails... users) {
|
||||
for (UserDetails user : users) {
|
||||
createUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
public InMemoryUserDetailsManager(Properties users) {
|
||||
Enumeration<?> names = users.propertyNames();
|
||||
UserAttributeEditor editor = new UserAttributeEditor();
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -56,7 +56,7 @@ public class Jsr250MethodSecurityMetadataSourceTests {
|
||||
@Test
|
||||
public void methodWithRolesAllowedHasCorrectAttribute() throws Exception {
|
||||
ConfigAttribute[] accessAttributes = findAttributes("adminMethod");
|
||||
assertThat(accessAttributes.length).isEqualTo(1);
|
||||
assertThat(accessAttributes).hasSize(1);
|
||||
assertThat(accessAttributes[0].toString()).isEqualTo("ROLE_ADMIN");
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class Jsr250MethodSecurityMetadataSourceTests {
|
||||
this.mds.setDefaultRolePrefix("CUSTOMPREFIX_");
|
||||
|
||||
ConfigAttribute[] accessAttributes = findAttributes("adminMethod");
|
||||
assertThat(accessAttributes.length).isEqualTo(1);
|
||||
assertThat(accessAttributes).hasSize(1);
|
||||
assertThat(accessAttributes[0].toString()).isEqualTo("CUSTOMPREFIX_ADMIN");
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public class Jsr250MethodSecurityMetadataSourceTests {
|
||||
this.mds.setDefaultRolePrefix("");
|
||||
|
||||
ConfigAttribute[] accessAttributes = findAttributes("adminMethod");
|
||||
assertThat(accessAttributes.length).isEqualTo(1);
|
||||
assertThat(accessAttributes).hasSize(1);
|
||||
assertThat(accessAttributes[0].toString()).isEqualTo("ADMIN");
|
||||
}
|
||||
|
||||
@@ -113,14 +113,14 @@ public class Jsr250MethodSecurityMetadataSourceTests {
|
||||
this.mds.setDefaultRolePrefix(null);
|
||||
|
||||
ConfigAttribute[] accessAttributes = findAttributes("adminMethod");
|
||||
assertThat(accessAttributes.length).isEqualTo(1);
|
||||
assertThat(accessAttributes).hasSize(1);
|
||||
assertThat(accessAttributes[0].toString()).isEqualTo("ADMIN");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alreadyHasDefaultPrefix() throws Exception {
|
||||
ConfigAttribute[] accessAttributes = findAttributes("roleAdminMethod");
|
||||
assertThat(accessAttributes.length).isEqualTo(1);
|
||||
assertThat(accessAttributes).hasSize(1);
|
||||
assertThat(accessAttributes[0].toString()).isEqualTo("ROLE_ADMIN");
|
||||
}
|
||||
|
||||
|
||||
+10
-13
@@ -137,8 +137,6 @@ public class SecuredAnnotationSecurityMetadataSourceTests {
|
||||
Collection<ConfigAttribute> attrs = this.mds.findAttributes(method,
|
||||
BusinessService.class);
|
||||
|
||||
assertThat(attrs).isNotNull();
|
||||
|
||||
// expect 2 attributes
|
||||
assertThat(attrs).hasSize(2);
|
||||
|
||||
@@ -147,7 +145,7 @@ public class SecuredAnnotationSecurityMetadataSourceTests {
|
||||
|
||||
// should have 2 SecurityConfigs
|
||||
for (ConfigAttribute sc : attrs) {
|
||||
assertThat(sc instanceof SecurityConfig).isTrue();
|
||||
assertThat(sc).isInstanceOf(SecurityConfig.class);
|
||||
|
||||
if (sc.getAttribute().equals("ROLE_USER")) {
|
||||
user = true;
|
||||
@@ -158,7 +156,7 @@ public class SecuredAnnotationSecurityMetadataSourceTests {
|
||||
}
|
||||
|
||||
// expect to have ROLE_USER and ROLE_ADMIN
|
||||
assertThat(user && admin).isTrue();
|
||||
assertThat(user).isEqualTo(admin).isTrue();
|
||||
}
|
||||
|
||||
// SEC-1491
|
||||
@@ -168,8 +166,7 @@ public class SecuredAnnotationSecurityMetadataSourceTests {
|
||||
new CustomSecurityAnnotationMetadataExtractor());
|
||||
Collection<ConfigAttribute> attrs = mds.findAttributes(
|
||||
CustomAnnotatedService.class);
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs.toArray()[0]).isEqualTo(SecurityEnum.ADMIN);
|
||||
assertThat(attrs).containsOnly(SecurityEnum.ADMIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -181,8 +178,8 @@ public class SecuredAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtClassLevel).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs[0].getAttribute()).isEqualTo("CUSTOM");
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs).extracting("attribute").containsOnly("CUSTOM");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -194,8 +191,8 @@ public class SecuredAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtInterfaceLevel).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs[0].getAttribute()).isEqualTo("CUSTOM");
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs).extracting("attribute").containsOnly("CUSTOM");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -206,15 +203,15 @@ public class SecuredAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtMethodLevel).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs[0].getAttribute()).isEqualTo("CUSTOM");
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs).extracting("attribute").containsOnly("CUSTOM");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void proxyFactoryInterfaceAttributesFound() throws Exception {
|
||||
MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation();
|
||||
Collection<ConfigAttribute> attributes = mds.getAttributes(mi);
|
||||
assertThat(attributes.size()).isEqualTo(1);
|
||||
assertThat(attributes).hasSize(1);
|
||||
assertThat(attributes).extracting("attribute").containsOnly("ROLE_PERSON");
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -89,7 +89,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(voidImpl1).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
|
||||
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
|
||||
assertThat(pre.getAuthorizeExpression()).isNotNull();
|
||||
@@ -102,7 +102,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(voidImpl2).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
|
||||
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
|
||||
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("someExpression");
|
||||
@@ -115,7 +115,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(voidImpl3).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
|
||||
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
|
||||
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("permitAll");
|
||||
@@ -128,7 +128,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(listImpl1).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(2);
|
||||
assertThat(attrs).hasSize(2);
|
||||
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
|
||||
assertThat(attrs[1] instanceof PostInvocationExpressionAttribute).isTrue();
|
||||
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
|
||||
@@ -143,7 +143,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(notherListImpl1).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
|
||||
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
|
||||
assertThat(pre.getFilterExpression()).isNotNull();
|
||||
@@ -157,7 +157,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(notherListImpl2).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
|
||||
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
|
||||
assertThat(pre.getFilterExpression()).isNotNull();
|
||||
@@ -171,7 +171,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtClassLevel).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,7 +179,7 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtInterfaceLevel).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -187,14 +187,14 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
|
||||
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtMethodLevel).toArray(
|
||||
new ConfigAttribute[0]);
|
||||
|
||||
assertThat(attrs.length).isEqualTo(1);
|
||||
assertThat(attrs).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void proxyFactoryInterfaceAttributesFound() throws Exception {
|
||||
MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation();
|
||||
Collection<ConfigAttribute> attributes = mds.getAttributes(mi);
|
||||
assertThat(attributes.size()).isEqualTo(1);
|
||||
assertThat(attributes).hasSize(1);
|
||||
Expression expression = (Expression) ReflectionTestUtils.getField(attributes
|
||||
.iterator().next(), "authorizeExpression");
|
||||
assertThat(expression.getExpressionString()).isEqualTo("hasRole('ROLE_PERSON')");
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -181,12 +181,12 @@ public class TestHelperTests {
|
||||
public void testCreateAuthorityList() {
|
||||
List<GrantedAuthority> authorities1 = HierarchicalRolesTestHelper
|
||||
.createAuthorityList("ROLE_A");
|
||||
assertThat(1).isEqualTo(authorities1.size());
|
||||
assertThat(authorities1).hasSize(1);
|
||||
assertThat(authorities1.get(0).getAuthority()).isEqualTo("ROLE_A");
|
||||
|
||||
List<GrantedAuthority> authorities2 = HierarchicalRolesTestHelper
|
||||
.createAuthorityList("ROLE_A", "ROLE_C");
|
||||
assertThat(2).isEqualTo(authorities2.size());
|
||||
assertThat(authorities2).hasSize(2);
|
||||
assertThat(authorities2.get(0).getAuthority()).isEqualTo("ROLE_A");
|
||||
assertThat(authorities2.get(1).getAuthority()).isEqualTo("ROLE_C");
|
||||
}
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ public class AbstractAccessDecisionManagerTests {
|
||||
list.add(voter);
|
||||
list.add(denyVoter);
|
||||
MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list);
|
||||
assertThat(mock.getDecisionVoters().size()).isEqualTo(list.size());
|
||||
assertThat(mock.getDecisionVoters()).hasSize(list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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
|
||||
*
|
||||
* http://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.authentication;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
public class TestingAuthenticationTokenTests {
|
||||
@Test
|
||||
public void constructorWhenNoAuthoritiesThenUnauthenticated() {
|
||||
TestingAuthenticationToken unauthenticated =
|
||||
new TestingAuthenticationToken("principal", "credentials");
|
||||
|
||||
assertThat(unauthenticated.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenArityAuthoritiesThenAuthenticated() {
|
||||
TestingAuthenticationToken authenticated =
|
||||
new TestingAuthenticationToken("principal", "credentials", "authority");
|
||||
|
||||
assertThat(authenticated.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenCollectionAuthoritiesThenAuthenticated() {
|
||||
TestingAuthenticationToken authenticated =
|
||||
new TestingAuthenticationToken("principal", "credentials",
|
||||
Arrays.<GrantedAuthority>asList(new SimpleGrantedAuthority("authority")));
|
||||
|
||||
assertThat(authenticated.isAuthenticated()).isTrue();
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -48,7 +48,7 @@ public class DefaultSecurityParameterNameDiscovererTests {
|
||||
List<ParameterNameDiscoverer> discoverers = (List<ParameterNameDiscoverer>) ReflectionTestUtils
|
||||
.getField(discoverer, "parameterNameDiscoverers");
|
||||
|
||||
assertThat(discoverers.size()).isEqualTo(2);
|
||||
assertThat(discoverers).hasSize(2);
|
||||
|
||||
ParameterNameDiscoverer annotationDisc = discoverers.get(0);
|
||||
assertThat(annotationDisc).isInstanceOf(AnnotationParameterNameDiscoverer.class);
|
||||
@@ -68,7 +68,7 @@ public class DefaultSecurityParameterNameDiscovererTests {
|
||||
List<ParameterNameDiscoverer> discoverers = (List<ParameterNameDiscoverer>) ReflectionTestUtils
|
||||
.getField(discoverer, "parameterNameDiscoverers");
|
||||
|
||||
assertThat(discoverers.size()).isEqualTo(3);
|
||||
assertThat(discoverers).hasSize(3);
|
||||
assertThat(discoverers.get(0)).isInstanceOf(
|
||||
LocalVariableTableParameterNameDiscoverer.class);
|
||||
|
||||
@@ -78,7 +78,7 @@ public class DefaultSecurityParameterNameDiscovererTests {
|
||||
annotationDisc, "annotationClassesToUse");
|
||||
assertThat(annotationsToUse).containsOnly(P.class.getName());
|
||||
|
||||
assertThat(discoverers.get(2).getClass()).isEqualTo(
|
||||
assertThat(discoverers.get(2)).isInstanceOf(
|
||||
DefaultParameterNameDiscoverer.class);
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2015-2017 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
|
||||
*
|
||||
* http://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.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreType;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SecurityJackson2ModulesTests {
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mapper = new ObjectMapper();
|
||||
SecurityJackson2Modules.enableDefaultTyping(mapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readValueWhenNotWhitelistedOrMappedThenThrowsException() throws Exception {
|
||||
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
|
||||
try {
|
||||
mapper.readValue(content, Object.class);
|
||||
fail("Expected Exception");
|
||||
} catch(RuntimeException e) {
|
||||
assertThat(e).hasMessageContaining("whitelisted");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readValueWhenExplicitDefaultTypingAfterSecuritySetupThenReadsAsSpecificType() throws Exception {
|
||||
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
||||
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
|
||||
|
||||
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readValueWhenExplicitDefaultTypingBeforeSecuritySetupThenReadsAsSpecificType() throws Exception {
|
||||
mapper = new ObjectMapper();
|
||||
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
||||
SecurityJackson2Modules.enableDefaultTyping(mapper);
|
||||
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
|
||||
|
||||
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readValueWhenAnnotatedThenReadsAsSpecificType() throws Exception {
|
||||
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelistedButAnnotated\",\"property\":\"bar\"}";
|
||||
|
||||
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelistedButAnnotated.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readValueWhenMixinProvidedThenReadsAsSpecificType() throws Exception {
|
||||
mapper.addMixIn(NotWhitelisted.class, NotWhitelistedMixin.class);
|
||||
String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
|
||||
|
||||
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readValueWhenHashMapThenReadsAsSpecificType() throws Exception {
|
||||
mapper.addMixIn(NotWhitelisted.class, NotWhitelistedMixin.class);
|
||||
String content = "{\"@class\":\"java.util.HashMap\"}";
|
||||
|
||||
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(HashMap.class);
|
||||
}
|
||||
|
||||
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface NotJacksonAnnotation {}
|
||||
|
||||
@NotJacksonAnnotation
|
||||
static class NotWhitelisted {
|
||||
private String property = "bar";
|
||||
|
||||
public String getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
public void setProperty(String property) {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreType(false)
|
||||
static class NotWhitelistedButAnnotated {
|
||||
private String property = "bar";
|
||||
|
||||
public String getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
public void setProperty(String property) {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
abstract class NotWhitelistedMixin {
|
||||
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -36,11 +36,13 @@ public class SimpleGrantedAuthorityMixinTests extends AbstractMixinTests {
|
||||
// @formatter:off
|
||||
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"authority\": \"ROLE_USER\"}";
|
||||
|
||||
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.ArrayList\", [" + AUTHORITY_JSON + "]]";
|
||||
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", [" + AUTHORITY_JSON + "]]";
|
||||
|
||||
public static final String AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON + "]]";
|
||||
|
||||
public static final String NO_AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.ArrayList\", []]";
|
||||
public static final String NO_AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", []]";
|
||||
|
||||
public static final String EMPTY_AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.ArrayList\", []]";
|
||||
|
||||
public static final String NO_AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", []]";
|
||||
// @formatter:on
|
||||
|
||||
+65
-2
@@ -17,20 +17,24 @@
|
||||
package org.springframework.security.jackson2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonClassDescription;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.json.JSONException;
|
||||
import org.junit.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* @author Jitendra Singh
|
||||
* @author Greg Turnquist
|
||||
* @since 4.2
|
||||
*/
|
||||
public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixinTests {
|
||||
@@ -49,10 +53,24 @@ public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixin
|
||||
public static final String AUTHENTICATED_STRINGPRINCIPAL_JSON = AUTHENTICATED_JSON.replace( UserDeserializerTests.USER_JSON, "\"admin\"");
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
private static final String NON_USER_PRINCIPAL_JSON = "{"
|
||||
+ "\"@class\": \"org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixinTests$NonUserPrincipal\", "
|
||||
+ "\"username\": \"admin\""
|
||||
+ "}";
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
private static final String AUTHENTICATED_NON_USER_PRINCIPAL_JSON = AUTHENTICATED_JSON
|
||||
.replace(UserDeserializerTests.USER_JSON, NON_USER_PRINCIPAL_JSON)
|
||||
.replaceAll(UserDeserializerTests.USER_PASSWORD, "null")
|
||||
.replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON, SimpleGrantedAuthorityMixinTests.NO_AUTHORITIES_ARRAYLIST_JSON);
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
private static final String UNAUTHENTICATED_STRINGPRINCIPAL_JSON = AUTHENTICATED_STRINGPRINCIPAL_JSON
|
||||
.replace("\"authenticated\": true, ", "\"authenticated\": false, ")
|
||||
.replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON, SimpleGrantedAuthorityMixinTests.NO_AUTHORITIES_ARRAYLIST_JSON);
|
||||
.replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON, SimpleGrantedAuthorityMixinTests.EMPTY_AUTHORITIES_ARRAYLIST_JSON);
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
@@ -115,9 +133,54 @@ public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixin
|
||||
JSONAssert.assertEquals(AUTHENTICATED_JSON.replaceAll(UserDeserializerTests.USER_PASSWORD, "null"), actualJson, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeAuthenticatedUsernamePasswordAuthenticationTokenMixinWithNonUserPrincipalTest() throws JsonProcessingException, JSONException {
|
||||
NonUserPrincipal principal = new NonUserPrincipal();
|
||||
principal.setUsername("admin");
|
||||
UsernamePasswordAuthenticationToken token =
|
||||
new UsernamePasswordAuthenticationToken(principal, null, new ArrayList<GrantedAuthority>());
|
||||
String actualJson = mapper.writeValueAsString(token);
|
||||
JSONAssert.assertEquals(AUTHENTICATED_NON_USER_PRINCIPAL_JSON, actualJson, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenWithNonUserPrincipalTest() throws IOException {
|
||||
UsernamePasswordAuthenticationToken token = mapper
|
||||
.readValue(AUTHENTICATED_NON_USER_PRINCIPAL_JSON, UsernamePasswordAuthenticationToken.class);
|
||||
assertThat(token).isNotNull();
|
||||
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(NonUserPrincipal.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializingThenDeserializingWithNoCredentialsOrDetailsShouldWork() throws IOException {
|
||||
// given
|
||||
UsernamePasswordAuthenticationToken original = new UsernamePasswordAuthenticationToken("Frodo", null);
|
||||
|
||||
// when
|
||||
String serialized = this.mapper.writeValueAsString(original);
|
||||
UsernamePasswordAuthenticationToken deserialized = this.mapper.readValue(serialized, UsernamePasswordAuthenticationToken.class);
|
||||
|
||||
// then
|
||||
assertThat(deserialized).isEqualTo(original);
|
||||
}
|
||||
|
||||
|
||||
private UsernamePasswordAuthenticationToken createToken() {
|
||||
User user = createDefaultUser();
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
|
||||
return token;
|
||||
}
|
||||
|
||||
@JsonClassDescription
|
||||
public static class NonUserPrincipal {
|
||||
private String username;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -262,7 +262,7 @@ public class JdbcUserDetailsManagerTests {
|
||||
manager.renameGroup("GROUP_0", "GROUP_X");
|
||||
|
||||
assertThat(template.queryForObject("select id from groups where group_name = 'GROUP_X'",
|
||||
Integer.class)).isEqualTo(0);
|
||||
Integer.class)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
dependencies {
|
||||
optional 'org.bouncycastle:bcpkix-jdk15on:1.54'
|
||||
}
|
||||
optional 'org.bouncycastle:bcpkix-jdk15on:1.60'
|
||||
}
|
||||
|
||||
+5
-11
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>spring-security-crypto</name>
|
||||
<description>spring-security-crypto</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -52,14 +52,14 @@
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
<version>1.54</version>
|
||||
<version>1.60</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -83,7 +83,7 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -92,12 +92,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.factory;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.Md4PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.StandardPasswordEncoder;
|
||||
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Used for creating {@link PasswordEncoder} instances
|
||||
* @author Rob Winch
|
||||
* @since 4.2.6
|
||||
*/
|
||||
public class PasswordEncoderFactories {
|
||||
|
||||
/**
|
||||
* Creates a {@link DelegatingPasswordEncoder} with default mappings. Additional
|
||||
* mappings may be added and the encoding will be updated to conform with best
|
||||
* practices. However, due to the nature of {@link DelegatingPasswordEncoder} the
|
||||
* updates should not impact users. The mappings current are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>bcrypt - {@link BCryptPasswordEncoder} (Also used for encoding)</li>
|
||||
* <li>ldap - {@link LdapShaPasswordEncoder}</li>
|
||||
* <li>MD4 - {@link Md4PasswordEncoder}</li>
|
||||
* <li>MD5 - {@code new MessageDigestPasswordEncoder("MD5")}</li>
|
||||
* <li>noop - {@link NoOpPasswordEncoder}</li>
|
||||
* <li>pbkdf2 - {@link Pbkdf2PasswordEncoder}</li>
|
||||
* <li>scrypt - {@link SCryptPasswordEncoder}</li>
|
||||
* <li>SHA-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}</li>
|
||||
* <li>SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}</li>
|
||||
* <li>sha256 - {@link StandardPasswordEncoder}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the {@link PasswordEncoder} to use
|
||||
*/
|
||||
public static PasswordEncoder createDelegatingPasswordEncoder() {
|
||||
String encodingId = "bcrypt";
|
||||
Map<String, PasswordEncoder> encoders = new HashMap<String, PasswordEncoder>();
|
||||
encoders.put(encodingId, new BCryptPasswordEncoder());
|
||||
encoders.put("ldap", new LdapShaPasswordEncoder());
|
||||
encoders.put("MD4", new Md4PasswordEncoder());
|
||||
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
|
||||
encoders.put("noop", NoOpPasswordEncoder.getInstance());
|
||||
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
|
||||
encoders.put("scrypt", new SCryptPasswordEncoder());
|
||||
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
|
||||
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
|
||||
encoders.put("sha256", new StandardPasswordEncoder());
|
||||
|
||||
return new DelegatingPasswordEncoder(encodingId, encoders);
|
||||
}
|
||||
|
||||
private PasswordEncoderFactories() {}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.keygen;
|
||||
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
|
||||
/**
|
||||
* A StringKeyGenerator that generates base64-encoded String keys. Delegates to a
|
||||
* {@link BytesKeyGenerator} for the actual key generation.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rob Winch
|
||||
* @since 4.2.6
|
||||
*/
|
||||
public class Base64StringKeyGenerator implements StringKeyGenerator {
|
||||
private static final int DEFAULT_KEY_LENGTH = 32;
|
||||
private final BytesKeyGenerator keyGenerator;
|
||||
|
||||
/**
|
||||
* Creates an instance with keyLength of 32 bytes and standard Base64 encoding.
|
||||
*/
|
||||
public Base64StringKeyGenerator() {
|
||||
this(DEFAULT_KEY_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with the provided key length in bytes and standard Base64
|
||||
* encoding.
|
||||
* @param keyLength the key length in bytes
|
||||
*/
|
||||
public Base64StringKeyGenerator(int keyLength) {
|
||||
if(keyLength < DEFAULT_KEY_LENGTH) {
|
||||
throw new IllegalArgumentException("keyLength must be greater than or equal to" + DEFAULT_KEY_LENGTH);
|
||||
}
|
||||
this.keyGenerator = KeyGenerators.secureRandom(keyLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateKey() {
|
||||
byte[] key = this.keyGenerator.generateKey();
|
||||
byte[] base64EncodedKey = Base64.encode(key);
|
||||
return new String(base64EncodedKey);
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A password encoder that delegates to another PasswordEncoder based upon a prefixed
|
||||
* identifier.
|
||||
*
|
||||
* <h2>Constructing an instance</h2>
|
||||
*
|
||||
* You can easily construct an instance using
|
||||
* {@link org.springframework.security.crypto.factory.PasswordEncoderFactories}.
|
||||
* Alternatively, you may create your own custom instance. For example:
|
||||
*
|
||||
* <pre>
|
||||
* String idForEncode = "bcrypt";
|
||||
* Map<String,PasswordEncoder> encoders = new HashMap<>();
|
||||
* encoders.put(idForEncode, new BCryptPasswordEncoder());
|
||||
* encoders.put("noop", NoOpPasswordEncoder.getInstance());
|
||||
* encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
|
||||
* encoders.put("scrypt", new SCryptPasswordEncoder());
|
||||
* encoders.put("sha256", new StandardPasswordEncoder());
|
||||
*
|
||||
* PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <h2>Password Storage Format</h2>
|
||||
*
|
||||
* The general format for a password is:
|
||||
*
|
||||
* <pre>
|
||||
* {id}encodedPassword
|
||||
* </pre>
|
||||
*
|
||||
* Such that "id" is an identifier used to look up which {@link PasswordEncoder} should
|
||||
* be used and "encodedPassword" is the original encoded password for the selected
|
||||
* {@link PasswordEncoder}. The "id" must be at the beginning of the password, start with
|
||||
* "{" and end with "}". If the "id" cannot be found, the "id" will be null.
|
||||
*
|
||||
* For example, the following might be a list of passwords encoded using different "id".
|
||||
* All of the original passwords are "password".
|
||||
*
|
||||
* <pre>
|
||||
* {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
||||
* {noop}password
|
||||
* {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
|
||||
* {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
|
||||
* {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
|
||||
* </pre>
|
||||
*
|
||||
* For the DelegatingPasswordEncoder that we constructed above:
|
||||
*
|
||||
* <ol>
|
||||
* <li>The first password would have a {@code PasswordEncoder} id of "bcrypt" and
|
||||
* encodedPassword of "$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG".
|
||||
* When matching it would delegate to
|
||||
* {@link org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder}</li>
|
||||
* <li>The second password would have a {@code PasswordEncoder} id of "noop" and
|
||||
* encodedPassword of "password". When matching it would delegate to
|
||||
* {@link NoOpPasswordEncoder}</li>
|
||||
* <li>The third password would have a {@code PasswordEncoder} id of "pbkdf2" and
|
||||
* encodedPassword of
|
||||
* "5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc".
|
||||
* When matching it would delegate to {@link Pbkdf2PasswordEncoder}</li>
|
||||
* <li>The fourth password would have a {@code PasswordEncoder} id of "scrypt" and
|
||||
* encodedPassword of
|
||||
* "$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc="
|
||||
* When matching it would delegate to
|
||||
* {@link org.springframework.security.crypto.scrypt.SCryptPasswordEncoder}</li>
|
||||
* <li>The final password would have a {@code PasswordEncoder} id of "sha256" and
|
||||
* encodedPassword of
|
||||
* "97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0".
|
||||
* When matching it would delegate to {@link StandardPasswordEncoder}</li>
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Password Encoding</h2>
|
||||
*
|
||||
* The {@code idForEncode} passed into the constructor determines which
|
||||
* {@link PasswordEncoder} will be used for encoding passwords. In the
|
||||
* {@code DelegatingPasswordEncoder} we constructed above, that means that the result of
|
||||
* encoding "password" would be delegated to {@code BCryptPasswordEncoder} and be prefixed
|
||||
* with "{bcrypt}". The end result would look like:
|
||||
*
|
||||
* <pre>
|
||||
* {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Password Matching</h2>
|
||||
*
|
||||
* Matching is done based upon the "id" and the mapping of the "id" to the
|
||||
* {@link PasswordEncoder} provided in the constructor. Our example in "Password Storage
|
||||
* Format" provides a working example of how this is done.
|
||||
*
|
||||
* By default the result of invoking {@link #matches(CharSequence, String)} with a
|
||||
* password with an "id" that is not mapped (including a null id) will result in an
|
||||
* {@link IllegalArgumentException}. This behavior can be customized using
|
||||
* {@link #setDefaultPasswordEncoderForMatches(PasswordEncoder)}.
|
||||
*
|
||||
* @see org.springframework.security.crypto.factory.PasswordEncoderFactories
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Michael Simons
|
||||
* @since 4.2.6
|
||||
*/
|
||||
public class DelegatingPasswordEncoder implements PasswordEncoder {
|
||||
private static final String PREFIX = "{";
|
||||
private static final String SUFFIX = "}";
|
||||
private final String idForEncode;
|
||||
private final PasswordEncoder passwordEncoderForEncode;
|
||||
private final Map<String, PasswordEncoder> idToPasswordEncoder;
|
||||
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param idForEncode the id used to lookup which {@link PasswordEncoder} should be
|
||||
* used for {@link #encode(CharSequence)}
|
||||
* @param idToPasswordEncoder a Map of id to {@link PasswordEncoder} used to determine
|
||||
* which {@link PasswordEncoder} should be used for {@link #matches(CharSequence, String)}
|
||||
*/
|
||||
public DelegatingPasswordEncoder(String idForEncode,
|
||||
Map<String, PasswordEncoder> idToPasswordEncoder) {
|
||||
if(idForEncode == null) {
|
||||
throw new IllegalArgumentException("idForEncode cannot be null");
|
||||
}
|
||||
if(!idToPasswordEncoder.containsKey(idForEncode)) {
|
||||
throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
|
||||
}
|
||||
for(String id : idToPasswordEncoder.keySet()) {
|
||||
if(id == null) {
|
||||
continue;
|
||||
}
|
||||
if(id.contains(PREFIX)) {
|
||||
throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);
|
||||
}
|
||||
if(id.contains(SUFFIX)) {
|
||||
throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX);
|
||||
}
|
||||
}
|
||||
this.idForEncode = idForEncode;
|
||||
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
|
||||
this.idToPasswordEncoder = new HashMap<String, PasswordEncoder>(idToPasswordEncoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link PasswordEncoder} to delegate to for
|
||||
* {@link #matches(CharSequence, String)} if the id is not mapped to a
|
||||
* {@link PasswordEncoder}.
|
||||
*
|
||||
* <p>
|
||||
The encodedPassword provided will be the full password
|
||||
* passed in including the {"id"} portion.* For example, if the password of
|
||||
* "{notmapped}foobar" was used, the "id" would be "notmapped" and the encodedPassword
|
||||
* passed into the {@link PasswordEncoder} would be "{notmapped}foobar".
|
||||
* </p>
|
||||
* @param defaultPasswordEncoderForMatches the encoder to use. The default is to
|
||||
* throw an {@link IllegalArgumentException}
|
||||
*/
|
||||
public void setDefaultPasswordEncoderForMatches(
|
||||
PasswordEncoder defaultPasswordEncoderForMatches) {
|
||||
if(defaultPasswordEncoderForMatches == null) {
|
||||
throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
|
||||
}
|
||||
this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
|
||||
if(rawPassword == null && prefixEncodedPassword == null) {
|
||||
return true;
|
||||
}
|
||||
String id = extractId(prefixEncodedPassword);
|
||||
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
|
||||
if(delegate == null) {
|
||||
return this.defaultPasswordEncoderForMatches
|
||||
.matches(rawPassword, prefixEncodedPassword);
|
||||
}
|
||||
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
|
||||
return delegate.matches(rawPassword, encodedPassword);
|
||||
}
|
||||
|
||||
private String extractId(String prefixEncodedPassword) {
|
||||
if (prefixEncodedPassword == null) {
|
||||
return null;
|
||||
}
|
||||
int start = prefixEncodedPassword.indexOf(PREFIX);
|
||||
if(start != 0) {
|
||||
return null;
|
||||
}
|
||||
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
|
||||
if(end < 0) {
|
||||
return null;
|
||||
}
|
||||
return prefixEncodedPassword.substring(start + 1, end);
|
||||
}
|
||||
|
||||
private String extractEncodedPassword(String prefixEncodedPassword) {
|
||||
int start = prefixEncodedPassword.indexOf(SUFFIX);
|
||||
return prefixEncodedPassword.substring(start + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default {@link PasswordEncoder} that throws an exception that a id could
|
||||
*/
|
||||
private class UnmappedIdPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
throw new UnsupportedOperationException("encode is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword,
|
||||
String prefixEncodedPassword) {
|
||||
String id = extractId(prefixEncodedPassword);
|
||||
throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ final class Digester {
|
||||
|
||||
private final String algorithm;
|
||||
|
||||
private final int iterations;
|
||||
private int iterations;
|
||||
|
||||
/**
|
||||
* Create a new Digester.
|
||||
@@ -42,7 +42,7 @@ final class Digester {
|
||||
// eagerly validate the algorithm
|
||||
createDigest(algorithm);
|
||||
this.algorithm = algorithm;
|
||||
this.iterations = iterations;
|
||||
setIterations(iterations);
|
||||
}
|
||||
|
||||
public byte[] digest(byte[] value) {
|
||||
@@ -53,6 +53,13 @@ final class Digester {
|
||||
return value;
|
||||
}
|
||||
|
||||
final void setIterations(int iterations) {
|
||||
if(iterations <= 0) {
|
||||
throw new IllegalArgumentException("Iterations value must be greater than zero");
|
||||
}
|
||||
this.iterations = iterations;
|
||||
}
|
||||
|
||||
private static MessageDigest createDigest(String algorithm) {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm);
|
||||
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered
|
||||
* secure.
|
||||
*
|
||||
* A version of {@link PasswordEncoder} which supports Ldap SHA and SSHA (salted-SHA)
|
||||
* encodings. The values are base-64 encoded and have the label "{SHA}" (or "{SSHA}")
|
||||
* prepended to the encoded hash. These can be made lower-case in the encoded password, if
|
||||
* required, by setting the <tt>forceLowerCasePrefix</tt> property to true.
|
||||
*
|
||||
* Also supports plain text passwords, so can safely be used in cases when both encoded
|
||||
* and non-encoded passwords are in use or when a null implementation is required.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 4.2.6
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* 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 class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||
// ~ Static fields/initializers
|
||||
// =====================================================================================
|
||||
|
||||
/** The number of bytes in a SHA hash */
|
||||
private static final int SHA_LENGTH = 20;
|
||||
private static final String SSHA_PREFIX = "{SSHA}";
|
||||
private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase();
|
||||
private static final String SHA_PREFIX = "{SHA}";
|
||||
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase();
|
||||
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
private BytesKeyGenerator saltGenerator;
|
||||
|
||||
private boolean forceLowerCasePrefix;
|
||||
|
||||
// ~ Constructors
|
||||
// ===================================================================================================
|
||||
|
||||
public LdapShaPasswordEncoder() {
|
||||
this(KeyGenerators.secureRandom());
|
||||
}
|
||||
|
||||
public LdapShaPasswordEncoder(BytesKeyGenerator saltGenerator) {
|
||||
if(saltGenerator == null) {
|
||||
throw new IllegalArgumentException("saltGenerator cannot be null");
|
||||
}
|
||||
this.saltGenerator = saltGenerator;
|
||||
}
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
private byte[] combineHashAndSalt(byte[] hash, byte[] salt) {
|
||||
if (salt == null) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
byte[] hashAndSalt = new byte[hash.length + salt.length];
|
||||
System.arraycopy(hash, 0, hashAndSalt, 0, hash.length);
|
||||
System.arraycopy(salt, 0, hashAndSalt, hash.length, salt.length);
|
||||
|
||||
return hashAndSalt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash of password (and salt bytes, if supplied) and returns a base64
|
||||
* encoded concatenation of the hash and salt, prefixed with {SHA} (or {SSHA} if salt
|
||||
* was used).
|
||||
*
|
||||
* @param rawPass the password to be encoded.
|
||||
*
|
||||
* @return the encoded password in the specified format
|
||||
*
|
||||
*/
|
||||
public String encode(CharSequence rawPass) {
|
||||
byte[] salt = this.saltGenerator.generateKey();
|
||||
return encode(rawPass, salt);
|
||||
}
|
||||
|
||||
|
||||
private String encode(CharSequence rawPassword, byte[] salt) {
|
||||
MessageDigest sha;
|
||||
|
||||
try {
|
||||
sha = MessageDigest.getInstance("SHA");
|
||||
sha.update(Utf8.encode(rawPassword));
|
||||
}
|
||||
catch (java.security.NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("No SHA implementation available!");
|
||||
}
|
||||
|
||||
if (salt != null) {
|
||||
sha.update(salt);
|
||||
}
|
||||
|
||||
byte[] hash = combineHashAndSalt(sha.digest(), (byte[]) salt);
|
||||
|
||||
String prefix;
|
||||
|
||||
if (salt == null || salt.length == 0) {
|
||||
prefix = forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX;
|
||||
}
|
||||
else {
|
||||
prefix = forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX;
|
||||
}
|
||||
|
||||
return prefix + Utf8.decode(Base64.encode(hash));
|
||||
}
|
||||
|
||||
private byte[] extractSalt(String encPass) {
|
||||
String encPassNoLabel = encPass.substring(6);
|
||||
|
||||
byte[] hashAndSalt = Base64.decode(encPassNoLabel.getBytes());
|
||||
int saltLength = hashAndSalt.length - SHA_LENGTH;
|
||||
byte[] salt = new byte[saltLength];
|
||||
System.arraycopy(hashAndSalt, SHA_LENGTH, salt, 0, saltLength);
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the validity of an unencoded password against an encoded one in the form
|
||||
* "{SSHA}sQuQF8vj8Eg2Y1hPdh3bkQhCKQBgjhQI".
|
||||
*
|
||||
* @param rawPassword unencoded password to be verified.
|
||||
* @param encodedPassword the actual SSHA or SHA encoded password
|
||||
*
|
||||
* @return true if they match (independent of the case of the prefix).
|
||||
*/
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
return matches(rawPassword == null ? null : rawPassword.toString(), encodedPassword);
|
||||
}
|
||||
|
||||
private boolean matches(String rawPassword, String encodedPassword) {
|
||||
String prefix = extractPrefix(encodedPassword);
|
||||
|
||||
if (prefix == null) {
|
||||
return PasswordEncoderUtils.equals(encodedPassword, rawPassword);
|
||||
}
|
||||
|
||||
byte[] salt;
|
||||
if (prefix.equals(SSHA_PREFIX) || prefix.equals(SSHA_PREFIX_LC)) {
|
||||
salt = extractSalt(encodedPassword);
|
||||
}
|
||||
else if (!prefix.equals(SHA_PREFIX) && !prefix.equals(SHA_PREFIX_LC)) {
|
||||
throw new IllegalArgumentException("Unsupported password prefix '" + prefix
|
||||
+ "'");
|
||||
}
|
||||
else {
|
||||
// Standard SHA
|
||||
salt = null;
|
||||
}
|
||||
|
||||
int startOfHash = prefix.length();
|
||||
|
||||
String encodedRawPass = encode(rawPassword, salt).substring(startOfHash);
|
||||
|
||||
return PasswordEncoderUtils
|
||||
.equals(encodedRawPass, encodedPassword.substring(startOfHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash prefix or null if there isn't one.
|
||||
*/
|
||||
private String extractPrefix(String encPass) {
|
||||
if (!encPass.startsWith("{")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int secondBrace = encPass.lastIndexOf('}');
|
||||
|
||||
if (secondBrace < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Couldn't find closing brace for SHA prefix");
|
||||
}
|
||||
|
||||
return encPass.substring(0, secondBrace + 1);
|
||||
}
|
||||
|
||||
public void setForceLowerCasePrefix(boolean forceLowerCasePrefix) {
|
||||
this.forceLowerCasePrefix = forceLowerCasePrefix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
/**
|
||||
* Implementation of the MD4 message digest derived from the RSA Data Security, Inc, MD4
|
||||
* Message-Digest Algorithm.
|
||||
*
|
||||
* @author Alan Stewart
|
||||
*/
|
||||
class Md4 {
|
||||
private static final int BLOCK_SIZE = 64;
|
||||
private static final int HASH_SIZE = 16;
|
||||
private final byte[] buffer = new byte[BLOCK_SIZE];
|
||||
private int bufferOffset;
|
||||
private long byteCount;
|
||||
private final int[] state = new int[4];
|
||||
private final int[] tmp = new int[16];
|
||||
|
||||
Md4() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
bufferOffset = 0;
|
||||
byteCount = 0;
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xEFCDAB89;
|
||||
state[2] = 0x98BADCFE;
|
||||
state[3] = 0x10325476;
|
||||
}
|
||||
|
||||
public byte[] digest() {
|
||||
byte[] resBuf = new byte[HASH_SIZE];
|
||||
digest(resBuf, 0, HASH_SIZE);
|
||||
return resBuf;
|
||||
}
|
||||
|
||||
private void digest(byte[] buffer, int off) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
buffer[off + (i * 4 + j)] = (byte) (state[i] >>> (8 * j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void digest(byte[] buffer, int offset, int len) {
|
||||
this.buffer[this.bufferOffset++] = (byte) 0x80;
|
||||
int lenOfBitLen = 8;
|
||||
int C = BLOCK_SIZE - lenOfBitLen;
|
||||
if (this.bufferOffset > C) {
|
||||
while (this.bufferOffset < BLOCK_SIZE) {
|
||||
this.buffer[this.bufferOffset++] = (byte) 0x00;
|
||||
}
|
||||
update(this.buffer, 0);
|
||||
this.bufferOffset = 0;
|
||||
}
|
||||
|
||||
while (this.bufferOffset < C) {
|
||||
this.buffer[this.bufferOffset++] = (byte) 0x00;
|
||||
}
|
||||
|
||||
long bitCount = byteCount * 8;
|
||||
for (int i = 0; i < 64; i += 8) {
|
||||
this.buffer[this.bufferOffset++] = (byte) (bitCount >>> (i));
|
||||
}
|
||||
|
||||
update(this.buffer, 0);
|
||||
digest(buffer, offset);
|
||||
}
|
||||
|
||||
public void update(byte[] input, int offset, int length) {
|
||||
byteCount += length;
|
||||
int todo;
|
||||
while (length >= (todo = BLOCK_SIZE - this.bufferOffset)) {
|
||||
System.arraycopy(input, offset, this.buffer, this.bufferOffset, todo);
|
||||
update(this.buffer, 0);
|
||||
length -= todo;
|
||||
offset += todo;
|
||||
this.bufferOffset = 0;
|
||||
}
|
||||
|
||||
System.arraycopy(input, offset, this.buffer, this.bufferOffset, length);
|
||||
bufferOffset += length;
|
||||
}
|
||||
|
||||
private void update(byte[] block, int offset) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
tmp[i] = (block[offset++] & 0xFF) | (block[offset++] & 0xFF) << 8
|
||||
| (block[offset++] & 0xFF) << 16 | (block[offset++] & 0xFF) << 24;
|
||||
}
|
||||
|
||||
int A = state[0];
|
||||
int B = state[1];
|
||||
int C = state[2];
|
||||
int D = state[3];
|
||||
|
||||
A = FF(A, B, C, D, tmp[0], 3);
|
||||
D = FF(D, A, B, C, tmp[1], 7);
|
||||
C = FF(C, D, A, B, tmp[2], 11);
|
||||
B = FF(B, C, D, A, tmp[3], 19);
|
||||
A = FF(A, B, C, D, tmp[4], 3);
|
||||
D = FF(D, A, B, C, tmp[5], 7);
|
||||
C = FF(C, D, A, B, tmp[6], 11);
|
||||
B = FF(B, C, D, A, tmp[7], 19);
|
||||
A = FF(A, B, C, D, tmp[8], 3);
|
||||
D = FF(D, A, B, C, tmp[9], 7);
|
||||
C = FF(C, D, A, B, tmp[10], 11);
|
||||
B = FF(B, C, D, A, tmp[11], 19);
|
||||
A = FF(A, B, C, D, tmp[12], 3);
|
||||
D = FF(D, A, B, C, tmp[13], 7);
|
||||
C = FF(C, D, A, B, tmp[14], 11);
|
||||
B = FF(B, C, D, A, tmp[15], 19);
|
||||
|
||||
A = GG(A, B, C, D, tmp[0], 3);
|
||||
D = GG(D, A, B, C, tmp[4], 5);
|
||||
C = GG(C, D, A, B, tmp[8], 9);
|
||||
B = GG(B, C, D, A, tmp[12], 13);
|
||||
A = GG(A, B, C, D, tmp[1], 3);
|
||||
D = GG(D, A, B, C, tmp[5], 5);
|
||||
C = GG(C, D, A, B, tmp[9], 9);
|
||||
B = GG(B, C, D, A, tmp[13], 13);
|
||||
A = GG(A, B, C, D, tmp[2], 3);
|
||||
D = GG(D, A, B, C, tmp[6], 5);
|
||||
C = GG(C, D, A, B, tmp[10], 9);
|
||||
B = GG(B, C, D, A, tmp[14], 13);
|
||||
A = GG(A, B, C, D, tmp[3], 3);
|
||||
D = GG(D, A, B, C, tmp[7], 5);
|
||||
C = GG(C, D, A, B, tmp[11], 9);
|
||||
B = GG(B, C, D, A, tmp[15], 13);
|
||||
|
||||
A = HH(A, B, C, D, tmp[0], 3);
|
||||
D = HH(D, A, B, C, tmp[8], 9);
|
||||
C = HH(C, D, A, B, tmp[4], 11);
|
||||
B = HH(B, C, D, A, tmp[12], 15);
|
||||
A = HH(A, B, C, D, tmp[2], 3);
|
||||
D = HH(D, A, B, C, tmp[10], 9);
|
||||
C = HH(C, D, A, B, tmp[6], 11);
|
||||
B = HH(B, C, D, A, tmp[14], 15);
|
||||
A = HH(A, B, C, D, tmp[1], 3);
|
||||
D = HH(D, A, B, C, tmp[9], 9);
|
||||
C = HH(C, D, A, B, tmp[5], 11);
|
||||
B = HH(B, C, D, A, tmp[13], 15);
|
||||
A = HH(A, B, C, D, tmp[3], 3);
|
||||
D = HH(D, A, B, C, tmp[11], 9);
|
||||
C = HH(C, D, A, B, tmp[7], 11);
|
||||
B = HH(B, C, D, A, tmp[15], 15);
|
||||
|
||||
state[0] += A;
|
||||
state[1] += B;
|
||||
state[2] += C;
|
||||
state[3] += D;
|
||||
}
|
||||
|
||||
private int FF(int a, int b, int c, int d, int x, int s) {
|
||||
int t = a + ((b & c) | (~b & d)) + x;
|
||||
return t << s | t >>> (32 - s);
|
||||
}
|
||||
|
||||
private int GG(int a, int b, int c, int d, int x, int s) {
|
||||
int t = a + ((b & (c | d)) | (c & d)) + x + 0x5A827999;
|
||||
return t << s | t >>> (32 - s);
|
||||
}
|
||||
|
||||
private int HH(int a, int b, int c, int d, int x, int s) {
|
||||
int t = a + (b ^ c ^ d) + x + 0x6ED9EBA1;
|
||||
return t << s | t >>> (32 - s);
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered secure.
|
||||
*
|
||||
* Encodes passwords using MD4. The general format of the password is:
|
||||
*
|
||||
* <pre>
|
||||
* s = salt == null ? "" : "{" + salt + "}"
|
||||
* s + md4(password + s)
|
||||
* </pre>
|
||||
*
|
||||
* Such that "salt" is the salt, md4 is the digest method, and password is the actual
|
||||
* password. For example with a password of "password", and a salt of
|
||||
* "thisissalt":
|
||||
*
|
||||
* <pre>
|
||||
* String s = salt == null ? "" : "{" + salt + "}";
|
||||
* s + md4(password + s)
|
||||
* "{thisissalt}" + md4(password + "{thisissalt}")
|
||||
* "{thisissalt}6cc7924dad12ade79dfb99e424f25260"
|
||||
* </pre>
|
||||
*
|
||||
* If the salt does not exist, then omit "{salt}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* md4(password)
|
||||
* </pre>
|
||||
*
|
||||
* If the salt is an empty String, then only use "{}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* "{}" + md4(password + "{}")
|
||||
* </pre>
|
||||
*
|
||||
* The format is intended to work with the Md4PasswordEncoder that was found in the
|
||||
* Spring Security core module. However, the passwords will need to be migrated to include
|
||||
* any salt with the password since this API provides Salt internally vs making it the
|
||||
* responsibility of the user. To migrate passwords from the SaltSource use the following:
|
||||
*
|
||||
* <pre>
|
||||
* String salt = saltSource.getSalt(user);
|
||||
* String s = salt == null ? null : "{" + salt + "}";
|
||||
* String migratedPassword = s + user.getPassword();
|
||||
* </pre>
|
||||
*
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
* @author Rob winch
|
||||
* @since 5.1
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* 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 class Md4PasswordEncoder implements PasswordEncoder {
|
||||
private static final String PREFIX = "{";
|
||||
private static final String SUFFIX = "}";
|
||||
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
|
||||
private boolean encodeHashAsBase64;
|
||||
|
||||
private Digester digester;
|
||||
|
||||
|
||||
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
|
||||
this.encodeHashAsBase64 = encodeHashAsBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the rawPass using a MessageDigest. If a salt is specified it will be merged
|
||||
* with the password before encoding.
|
||||
*
|
||||
* @param rawPassword The plain text password
|
||||
* @return Hex string of password digest (or base64 encoded string if
|
||||
* encodeHashAsBase64 is enabled.
|
||||
*/
|
||||
public String encode(CharSequence rawPassword) {
|
||||
String salt = PREFIX + this.saltGenerator.generateKey() + SUFFIX;
|
||||
return digest(salt, rawPassword);
|
||||
}
|
||||
|
||||
private String digest(String salt, CharSequence rawPassword) {
|
||||
if(rawPassword == null) {
|
||||
rawPassword = "";
|
||||
}
|
||||
String saltedPassword = rawPassword + salt;
|
||||
byte[] saltedPasswordBytes = Utf8.encode(saltedPassword);
|
||||
|
||||
Md4 md4 = new Md4();
|
||||
md4.update(saltedPasswordBytes, 0, saltedPasswordBytes.length);
|
||||
|
||||
byte[] digest = md4.digest();
|
||||
String encoded = encode(digest);
|
||||
return salt + encoded;
|
||||
}
|
||||
|
||||
private String encode(byte[] digest) {
|
||||
if (this.encodeHashAsBase64) {
|
||||
return Utf8.decode(Base64.encode(digest));
|
||||
}
|
||||
else {
|
||||
return new String(Hex.encode(digest));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a previously encoded password and compares it with a rawpassword after mixing
|
||||
* in the salt and encoding that value
|
||||
*
|
||||
* @param rawPassword plain text password
|
||||
* @param encodedPassword previously encoded password
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
String salt = extractSalt(encodedPassword);
|
||||
String rawPasswordEncoded = digest(salt, rawPassword);
|
||||
return PasswordEncoderUtils.equals(encodedPassword.toString(), rawPasswordEncoded);
|
||||
}
|
||||
|
||||
private String extractSalt(String prefixEncodedPassword) {
|
||||
int start = prefixEncodedPassword.indexOf(PREFIX);
|
||||
if(start != 0) {
|
||||
return "";
|
||||
}
|
||||
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
|
||||
if(end < 0) {
|
||||
return "";
|
||||
}
|
||||
return prefixEncodedPassword.substring(start, end + 1);
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered secure.
|
||||
*
|
||||
* Encodes passwords using the passed in {@link MessageDigest}.
|
||||
*
|
||||
* The general format of the password is:
|
||||
*
|
||||
* <pre>
|
||||
* s = salt == null ? "" : "{" + salt + "}"
|
||||
* s + digest(password + s)
|
||||
* </pre>
|
||||
*
|
||||
* Such that "salt" is the salt, digest is the digest method, and password is the actual
|
||||
* password. For example when using MD5, a password of "password", and a salt of
|
||||
* "thisissalt":
|
||||
*
|
||||
* <pre>
|
||||
* String s = salt == null ? "" : "{" + salt + "}";
|
||||
* s + md5(password + s)
|
||||
* "{thisissalt}" + md5(password + "{thisissalt}")
|
||||
* "{thisissalt}2a4e7104c2780098f50ed5a84bb2323d"
|
||||
* </pre>
|
||||
*
|
||||
* If the salt does not exist, then omit "{salt}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* digest(password)
|
||||
* </pre>
|
||||
*
|
||||
* If the salt is an empty String, then only use "{}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* "{}" + digest(password + "{}")
|
||||
* </pre>
|
||||
*
|
||||
* The format is intended to work with the DigestPasswordEncoder that was found in the
|
||||
* Spring Security core module. However, the passwords will need to be migrated to include
|
||||
* any salt with the password since this API provides Salt internally vs making it the
|
||||
* responsibility of the user. To migrate passwords from the SaltSource use the following:
|
||||
*
|
||||
* <pre>
|
||||
* String salt = saltSource.getSalt(user);
|
||||
* String s = salt == null ? null : "{" + salt + "}";
|
||||
* String migratedPassword = s + user.getPassword();
|
||||
* </pre>
|
||||
*
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
* @author Rob Winch
|
||||
* @since 4.2.6
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* 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 class MessageDigestPasswordEncoder implements PasswordEncoder {
|
||||
private static final String PREFIX = "{";
|
||||
private static final String SUFFIX = "}";
|
||||
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
|
||||
private boolean encodeHashAsBase64;
|
||||
|
||||
private Digester digester;
|
||||
|
||||
/**
|
||||
* The digest algorithm to use Supports the named
|
||||
* <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppA">
|
||||
* Message Digest Algorithms</a> in the Java environment.
|
||||
*
|
||||
* @param algorithm
|
||||
*/
|
||||
public MessageDigestPasswordEncoder(String algorithm) {
|
||||
this.digester = new Digester(algorithm, 1);
|
||||
}
|
||||
|
||||
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
|
||||
this.encodeHashAsBase64 = encodeHashAsBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the rawPass using a MessageDigest. If a salt is specified it will be merged
|
||||
* with the password before encoding.
|
||||
*
|
||||
* @param rawPassword The plain text password
|
||||
* @return Hex string of password digest (or base64 encoded string if
|
||||
* encodeHashAsBase64 is enabled.
|
||||
*/
|
||||
public String encode(CharSequence rawPassword) {
|
||||
String salt = PREFIX + this.saltGenerator.generateKey() + SUFFIX;
|
||||
return digest(salt, rawPassword);
|
||||
}
|
||||
|
||||
private String digest(String salt, CharSequence rawPassword) {
|
||||
String saltedPassword = rawPassword + salt;
|
||||
|
||||
byte[] digest = this.digester.digest(Utf8.encode(saltedPassword));
|
||||
String encoded = encode(digest);
|
||||
return salt + encoded;
|
||||
}
|
||||
|
||||
private String encode(byte[] digest) {
|
||||
if (this.encodeHashAsBase64) {
|
||||
return Utf8.decode(Base64.encode(digest));
|
||||
}
|
||||
else {
|
||||
return new String(Hex.encode(digest));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a previously encoded password and compares it with a rawpassword after mixing
|
||||
* in the salt and encoding that value
|
||||
*
|
||||
* @param rawPassword plain text password
|
||||
* @param encodedPassword previously encoded password
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
String salt = extractSalt(encodedPassword);
|
||||
String rawPasswordEncoded = digest(salt, rawPassword);
|
||||
return PasswordEncoderUtils.equals(encodedPassword.toString(), rawPasswordEncoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of iterations for which the calculated hash value should be
|
||||
* "stretched". If this is greater than one, the initial digest is calculated, the
|
||||
* digest function will be called repeatedly on the result for the additional number
|
||||
* of iterations.
|
||||
*
|
||||
* @param iterations the number of iterations which will be executed on the hashed
|
||||
* password/salt value. Defaults to 1.
|
||||
*/
|
||||
public void setIterations(int iterations) {
|
||||
this.digester.setIterations(iterations);
|
||||
}
|
||||
|
||||
private String extractSalt(String prefixEncodedPassword) {
|
||||
int start = prefixEncodedPassword.indexOf(PREFIX);
|
||||
if(start != 0) {
|
||||
return "";
|
||||
}
|
||||
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
|
||||
if(end < 0) {
|
||||
return "";
|
||||
}
|
||||
return prefixEncodedPassword.substring(start, end + 1);
|
||||
}
|
||||
}
|
||||
+8
@@ -16,11 +16,19 @@
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy and testing purposes only and is
|
||||
* not considered secure.
|
||||
*
|
||||
* A password encoder that does nothing. Useful for testing where working with plain text
|
||||
* passwords may be preferred.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @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.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class NoOpPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
public String encode(CharSequence rawPassword) {
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
|
||||
/**
|
||||
* Utility for constant time comparison to prevent against timing attacks.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class PasswordEncoderUtils {
|
||||
|
||||
/**
|
||||
* Constant time comparison to prevent against timing attacks.
|
||||
* @param expected
|
||||
* @param actual
|
||||
* @return
|
||||
*/
|
||||
static boolean equals(String expected, String actual) {
|
||||
byte[] expectedBytes = bytesUtf8(expected);
|
||||
byte[] actualBytes = bytesUtf8(actual);
|
||||
int expectedLength = expectedBytes == null ? -1 : expectedBytes.length;
|
||||
int actualLength = actualBytes == null ? -1 : actualBytes.length;
|
||||
|
||||
int result = expectedLength == actualLength ? 0 : 1;
|
||||
for (int i = 0; i < actualLength; i++) {
|
||||
byte expectedByte = expectedLength <= 0 ? 0 : expectedBytes[i % expectedLength];
|
||||
byte actualByte = actualBytes[i % actualLength];
|
||||
result |= expectedByte ^ actualByte;
|
||||
}
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
private static byte[] bytesUtf8(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Utf8.encode(s); // need to check if Utf8.encode() runs in constant time (probably not). This may leak length of string.
|
||||
}
|
||||
|
||||
private PasswordEncoderUtils() {
|
||||
}
|
||||
}
|
||||
+70
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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,10 +16,12 @@
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
@@ -41,7 +43,7 @@ import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||
* @since 4.1
|
||||
*/
|
||||
public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
|
||||
|
||||
private static final int DEFAULT_HASH_WIDTH = 256;
|
||||
private static final int DEFAULT_ITERATIONS = 185000;
|
||||
|
||||
@@ -50,10 +52,12 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
private final byte[] secret;
|
||||
private final int hashWidth;
|
||||
private final int iterations;
|
||||
private String algorithm = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name();
|
||||
private boolean encodeHashAsBase64;
|
||||
|
||||
/**
|
||||
* Constructs a PBKDF2 password encoder with no additional secret value. There will be
|
||||
* 360000 iterations and a hash width of 160. The default is based upon aiming for .5
|
||||
* {@value DEFAULT_ITERATIONS} iterations and a hash width of {@value DEFAULT_HASH_WIDTH}. The default is based upon aiming for .5
|
||||
* seconds to validate the password when this class was added.. Users should tune
|
||||
* password verification to their own systems.
|
||||
*/
|
||||
@@ -63,7 +67,7 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
|
||||
/**
|
||||
* Constructs a standard password encoder with a secret value which is also included
|
||||
* in the password hash. There will be 1024 iterations and a hash width of 160.
|
||||
* in the password hash. There will be {@value DEFAULT_ITERATIONS} iterations and a hash width of {@value DEFAULT_HASH_WIDTH}.
|
||||
*
|
||||
* @param secret the secret key used in the encoding process (should not be shared)
|
||||
*/
|
||||
@@ -86,16 +90,56 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
this.hashWidth = hashWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the algorithm to use. See
|
||||
* <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecretKeyFactory">SecretKeyFactory Algorithms</a>
|
||||
* @param secretKeyFactoryAlgorithm the algorithm to use (i.e.
|
||||
* {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1},
|
||||
* {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256},
|
||||
* {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512})
|
||||
* @since 5.0
|
||||
*/
|
||||
public void setAlgorithm(SecretKeyFactoryAlgorithm secretKeyFactoryAlgorithm) {
|
||||
if(secretKeyFactoryAlgorithm == null) {
|
||||
throw new IllegalArgumentException("secretKeyFactoryAlgorithm cannot be null");
|
||||
}
|
||||
String algorithmName = secretKeyFactoryAlgorithm.name();
|
||||
try {
|
||||
SecretKeyFactory.getInstance(algorithmName);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("Invalid algorithm '" + algorithmName + "'.", e);
|
||||
}
|
||||
this.algorithm = algorithmName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the resulting hash should be encoded as Base64. The default is false which
|
||||
* means it will be encoded in Hex.
|
||||
* @param encodeHashAsBase64 true if encode as Base64, false if should use Hex
|
||||
* (default)
|
||||
*/
|
||||
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
|
||||
this.encodeHashAsBase64 = encodeHashAsBase64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
byte[] salt = this.saltGenerator.generateKey();
|
||||
byte[] encoded = encode(rawPassword, salt);
|
||||
return String.valueOf(Hex.encode(encoded));
|
||||
return encode(encoded);
|
||||
}
|
||||
|
||||
private String encode(byte[] bytes) {
|
||||
if(this.encodeHashAsBase64) {
|
||||
return Utf8.decode(Base64.encode(bytes));
|
||||
}
|
||||
return String.valueOf(Hex.encode(bytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
byte[] digested = Hex.decode(encodedPassword);
|
||||
byte[] digested = decode(encodedPassword);
|
||||
byte[] salt = subArray(digested, 0, this.saltGenerator.getKeyLength());
|
||||
return matches(digested, encode(rawPassword, salt));
|
||||
}
|
||||
@@ -115,15 +159,33 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
private byte[] decode(String encodedBytes) {
|
||||
if(this.encodeHashAsBase64) {
|
||||
return Base64.decode(Utf8.encode(encodedBytes));
|
||||
}
|
||||
return Hex.decode(encodedBytes);
|
||||
}
|
||||
|
||||
private byte[] encode(CharSequence rawPassword, byte[] salt) {
|
||||
try {
|
||||
PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
|
||||
concatenate(salt, this.secret), this.iterations, this.hashWidth);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
|
||||
return concatenate(salt, skf.generateSecret(spec).getEncoded());
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
throw new IllegalStateException("Could not create hash", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Algorithm used for creating the {@link SecretKeyFactory}
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public enum SecretKeyFactoryAlgorithm {
|
||||
PBKDF2WithHmacSHA1,
|
||||
PBKDF2WithHmacSHA256,
|
||||
PBKDF2WithHmacSHA512
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -24,6 +24,9 @@ import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered
|
||||
* secure.
|
||||
*
|
||||
* A standard {@code PasswordEncoder} implementation that uses SHA-256 hashing with 1024
|
||||
* iterations and a random 8-byte random salt value. It uses an additional system-wide
|
||||
* secret value to provide additional protection.
|
||||
@@ -37,7 +40,13 @@ import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Luke Taylor
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* 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 StandardPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
private final Digester digester;
|
||||
|
||||
@@ -218,21 +218,21 @@ public class BCryptTests {
|
||||
@Test
|
||||
public void decodingCharsOutsideAsciiGivesNoResults() {
|
||||
byte[] ba = BCrypt.decode_base64("ππππππππ", 1);
|
||||
assertThat(ba.length).isEqualTo(0);
|
||||
assertThat(ba).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodingStopsWithFirstInvalidCharacter() {
|
||||
assertThat(BCrypt.decode_base64("....", 1).length).isEqualTo(1);
|
||||
assertThat(BCrypt.decode_base64(" ....", 1).length).isEqualTo(0);
|
||||
assertThat(BCrypt.decode_base64("....", 1)).hasSize(1);
|
||||
assertThat(BCrypt.decode_base64(" ....", 1)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodingOnlyProvidesAvailableBytes() {
|
||||
assertThat(BCrypt.decode_base64("", 1).length).isEqualTo(0);
|
||||
assertThat(BCrypt.decode_base64("......", 3).length).isEqualTo(3);
|
||||
assertThat(BCrypt.decode_base64("......", 4).length).isEqualTo(4);
|
||||
assertThat(BCrypt.decode_base64("......", 5).length).isEqualTo(4);
|
||||
assertThat(BCrypt.decode_base64("", 1)).isEmpty();
|
||||
assertThat(BCrypt.decode_base64("......", 3)).hasSize(3);
|
||||
assertThat(BCrypt.decode_base64("......", 4)).hasSize(4);
|
||||
assertThat(BCrypt.decode_base64("......", 5)).hasSize(4);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,8 +268,8 @@ public class BCryptTests {
|
||||
|
||||
@Test
|
||||
public void genSaltGeneratesCorrectSaltPrefix() {
|
||||
assertThat(BCrypt.gensalt(4).startsWith("$2a$04$")).isTrue();
|
||||
assertThat(BCrypt.gensalt(31).startsWith("$2a$31$")).isTrue();
|
||||
assertThat(BCrypt.gensalt(4)).startsWith("$2a$04$");
|
||||
assertThat(BCrypt.gensalt(31)).startsWith("$2a$31$");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
|
||||
@@ -34,7 +34,7 @@ public class HexTests {
|
||||
@Test
|
||||
public void encode() {
|
||||
assertThat(Hex.encode(new byte[] { (byte) 'A', (byte) 'B', (byte) 'C',
|
||||
(byte) 'D' })).isEqualTo(new char[] {'4','1','4','2','4','3','4','4'});
|
||||
(byte) 'D' })).isEqualTo(new char[] {'4', '1', '4', '2', '4', '3', '4', '4'});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -30,7 +30,7 @@ public class Utf8Tests {
|
||||
@Test
|
||||
public void utf8EncodesAndDecodesCorrectly() throws Exception {
|
||||
byte[] bytes = Utf8.encode("6048b75ed560785c");
|
||||
assertThat(bytes.length).isEqualTo(16);
|
||||
assertThat(bytes).hasSize(16);
|
||||
assertThat(Arrays.equals("6048b75ed560785c".getBytes("UTF-8"), bytes)).isTrue();
|
||||
|
||||
String decoded = Utf8.decode(bytes);
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.factory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class PasswordEncoderFactoriesTests {
|
||||
private PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
|
||||
private String rawPassword = "password";
|
||||
|
||||
@Test
|
||||
public void encodeWhenDefaultThenBCryptUsed() {
|
||||
String encodedPassword = this.encoder.encode(this.rawPassword);
|
||||
|
||||
assertThat(encodedPassword).startsWith("{bcrypt}");
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenBCryptThenWorks() {
|
||||
String encodedPassword = "{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenLdapThenWorks() {
|
||||
String encodedPassword = "{ldap}{SSHA}igvD9lOiTXm16dmOw0YWRb9OjK2ThZvdQku2EQ==";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenMd4ThenWorks() {
|
||||
String encodedPassword = "{MD4}{KYp8/QErWyQemYazZQ8UnWWfbGbkYkVC8qMi0duoA84=}152ce09d3261d2b53cac55b2ea4d1c7a";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenMd5ThenWorks() {
|
||||
String encodedPassword = "{MD5}{aRYR+Yp2xSqtgF+vtjH6jNda6M083iEbP+zCFjLt9IA=}905e382a25eed53e22224223b3581092";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoopThenWorks() {
|
||||
String encodedPassword = "{noop}password";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenPbkdf2ThenWorks() {
|
||||
String encodedPassword = "{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSCryptThenWorks() {
|
||||
String encodedPassword = "{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSHA1ThenWorks() {
|
||||
String encodedPassword = "{SHA-1}{6581QepZz2qd8jVrT2QYPVtK8DuM2n45dVslmc3UTWc=}4f31573948ddbfb8ac9dd80107dfad13fd8f2454";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSHA256ThenWorks() {
|
||||
String encodedPassword = "{SHA-256}{UisHp3pFSMqcqrhQsrhR+hspIG0SyMDyDW/XtY+t6nA=}a98efbaf59277bfd1837c33fd4fde67de5bcfd2205bcba0992f6fc32b03a8f88";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSha256ThenWorks() {
|
||||
String encodedPassword = "{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.junit;
|
||||
|
||||
import org.junit.Assume;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 4.2.7
|
||||
*/
|
||||
public final class Assumptions {
|
||||
|
||||
public static void assumeMinimumJdk8() {
|
||||
int majorVersion = JdkVersion.getMajorJavaVersion();
|
||||
Assume.assumeTrue(majorVersion >= 8);
|
||||
}
|
||||
|
||||
private Assumptions() {}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.junit;
|
||||
|
||||
/**
|
||||
* Internal helper class used to find the Java/JVM version that Spring is
|
||||
* operating on, to allow for automatically adapting to the present platform's
|
||||
* capabilities.
|
||||
*
|
||||
* <p>Note that Spring requires JVM 1.6 or higher, as of Spring 4.0.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Rick Evans
|
||||
* @author Sam Brannen
|
||||
* @deprecated as of Spring 4.2.1, in favor of direct checks for the desired
|
||||
* JDK API variants via reflection
|
||||
*/
|
||||
public abstract class JdkVersion {
|
||||
|
||||
/**
|
||||
* Constant identifying the 1.3.x JVM (JDK 1.3).
|
||||
*/
|
||||
public static final int JAVA_13 = 0;
|
||||
|
||||
/**
|
||||
* Constant identifying the 1.4.x JVM (J2SE 1.4).
|
||||
*/
|
||||
public static final int JAVA_14 = 1;
|
||||
|
||||
/**
|
||||
* Constant identifying the 1.5 JVM (Java 5).
|
||||
*/
|
||||
public static final int JAVA_15 = 2;
|
||||
|
||||
/**
|
||||
* Constant identifying the 1.6 JVM (Java 6).
|
||||
*/
|
||||
public static final int JAVA_16 = 3;
|
||||
|
||||
/**
|
||||
* Constant identifying the 1.7 JVM (Java 7).
|
||||
*/
|
||||
public static final int JAVA_17 = 4;
|
||||
|
||||
/**
|
||||
* Constant identifying the 1.8 JVM (Java 8).
|
||||
*/
|
||||
public static final int JAVA_18 = 5;
|
||||
|
||||
/**
|
||||
* Constant identifying the 1.9 JVM (Java 9).
|
||||
*/
|
||||
public static final int JAVA_19 = 6;
|
||||
|
||||
|
||||
private static final String javaVersion;
|
||||
|
||||
private static final int majorJavaVersion;
|
||||
|
||||
static {
|
||||
javaVersion = System.getProperty("java.version");
|
||||
// version String should look like "1.4.2_10"
|
||||
if (javaVersion.contains("1.9.")) {
|
||||
majorJavaVersion = JAVA_19;
|
||||
}
|
||||
else if (javaVersion.contains("1.8.")) {
|
||||
majorJavaVersion = JAVA_18;
|
||||
}
|
||||
else if (javaVersion.contains("1.7.")) {
|
||||
majorJavaVersion = JAVA_17;
|
||||
}
|
||||
else {
|
||||
// else leave 1.6 as default (it's either 1.6 or unknown)
|
||||
majorJavaVersion = JAVA_16;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the full Java version string, as returned by
|
||||
* {@code System.getProperty("java.version")}.
|
||||
* @return the full Java version string
|
||||
* @see System#getProperty(String)
|
||||
*/
|
||||
public static String getJavaVersion() {
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the major version code. This means we can do things like
|
||||
* {@code if (getMajorJavaVersion() >= JAVA_17)}.
|
||||
* @return a code comparable to the {@code JAVA_XX} codes in this class
|
||||
* @see #JAVA_16
|
||||
* @see #JAVA_17
|
||||
* @see #JAVA_18
|
||||
* @see #JAVA_19
|
||||
*/
|
||||
public static int getMajorJavaVersion() {
|
||||
return majorJavaVersion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.keygen;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 4.2.6
|
||||
*/
|
||||
public class Base64StringKeyGeneratorTests {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorIntWhenLessThan32ThenIllegalArgumentException() {
|
||||
new Base64StringKeyGenerator(31);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateKeyWhenDefaultConstructorThen32Bytes() {
|
||||
String result = new Base64StringKeyGenerator().generateKey();
|
||||
assertThat(Base64.decode(result.getBytes())).hasSize(32);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateKeyWhenCustomKeySizeThen32Bytes() {
|
||||
int size = 40;
|
||||
String result = new Base64StringKeyGenerator(size).generateKey();
|
||||
assertThat(Base64.decode(result.getBytes())).hasSize(size);
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -29,7 +29,7 @@ public class KeyGeneratorsTests {
|
||||
BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom();
|
||||
assertThat(keyGenerator.getKeyLength()).isEqualTo(8);
|
||||
byte[] key = keyGenerator.generateKey();
|
||||
assertThat(key.length).isEqualTo(8);
|
||||
assertThat(key).hasSize(8);
|
||||
byte[] key2 = keyGenerator.generateKey();
|
||||
assertThat(Arrays.equals(key, key2)).isFalse();
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class KeyGeneratorsTests {
|
||||
BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom(21);
|
||||
assertThat(keyGenerator.getKeyLength()).isEqualTo(21);
|
||||
byte[] key = keyGenerator.generateKey();
|
||||
assertThat(key.length).isEqualTo(21);
|
||||
assertThat(key).hasSize(21);
|
||||
byte[] key2 = keyGenerator.generateKey();
|
||||
assertThat(Arrays.equals(key, key2)).isFalse();
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class KeyGeneratorsTests {
|
||||
BytesKeyGenerator keyGenerator = KeyGenerators.shared(21);
|
||||
assertThat(keyGenerator.getKeyLength()).isEqualTo(21);
|
||||
byte[] key = keyGenerator.generateKey();
|
||||
assertThat(key.length).isEqualTo(21);
|
||||
assertThat(key).hasSize(21);
|
||||
byte[] key2 = keyGenerator.generateKey();
|
||||
assertThat(Arrays.equals(key, key2)).isTrue();
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class KeyGeneratorsTests {
|
||||
StringKeyGenerator keyGenerator = KeyGenerators.string();
|
||||
String hexStringKey = keyGenerator.generateKey();
|
||||
assertThat(hexStringKey.length()).isEqualTo(16);
|
||||
assertThat(Hex.decode(hexStringKey).length).isEqualTo(8);
|
||||
assertThat(Hex.decode(hexStringKey)).hasSize(8);
|
||||
String hexStringKey2 = keyGenerator.generateKey();
|
||||
assertThat(hexStringKey.equals(hexStringKey2)).isFalse();
|
||||
}
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Michael Simons
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DelegatingPasswordEncoderTests {
|
||||
@Mock
|
||||
private PasswordEncoder bcrypt;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder noop;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder invalidId;
|
||||
|
||||
private String bcryptId = "bcrypt";
|
||||
|
||||
private String rawPassword = "password";
|
||||
|
||||
private String encodedPassword = "ENCODED-PASSWORD";
|
||||
|
||||
private String bcryptEncodedPassword = "{bcrypt}" + this.encodedPassword;
|
||||
|
||||
private String noopEncodedPassword = "{noop}" + this.encodedPassword;
|
||||
|
||||
private Map<String, PasswordEncoder> delegates;
|
||||
|
||||
private DelegatingPasswordEncoder passwordEncoder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.delegates = new HashMap<String, PasswordEncoder>();
|
||||
this.delegates.put(this.bcryptId, this.bcrypt);
|
||||
this.delegates.put("noop", this.noop);
|
||||
|
||||
this.passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIdForEncodeNullThenIllegalArgumentException() {
|
||||
new DelegatingPasswordEncoder(null, this.delegates);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIdForEncodeDoesNotExistThenIllegalArgumentException() {
|
||||
new DelegatingPasswordEncoder(this.bcryptId + "INVALID", this.delegates);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setDefaultPasswordEncoderForMatchesWhenNullThenIllegalArgumentException() {
|
||||
this.passwordEncoder.setDefaultPasswordEncoderForMatches(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenCustomDefaultPasswordEncoderForMatchesThenDelegates() {
|
||||
String encodedPassword = "{unmapped}" + this.rawPassword;
|
||||
this.passwordEncoder.setDefaultPasswordEncoderForMatches(this.invalidId);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, encodedPassword)).isFalse();
|
||||
|
||||
verify(this.invalidId).matches(this.rawPassword, encodedPassword);
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeWhenValidThenUsesIdForEncode() {
|
||||
when(this.bcrypt.encode(this.rawPassword)).thenReturn(this.encodedPassword);
|
||||
|
||||
assertThat(this.passwordEncoder.encode(this.rawPassword)).isEqualTo(this.bcryptEncodedPassword);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenBCryptThenDelegatesToBCrypt() {
|
||||
when(this.bcrypt.matches(this.rawPassword, this.encodedPassword)).thenReturn(true);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, this.bcryptEncodedPassword)).isTrue();
|
||||
|
||||
verify(this.bcrypt).matches(this.rawPassword, this.encodedPassword);
|
||||
verifyZeroInteractions(this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoopThenDelegatesToNoop() {
|
||||
when(this.noop.matches(this.rawPassword, this.encodedPassword)).thenReturn(true);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, this.noopEncodedPassword)).isTrue();
|
||||
|
||||
verify(this.noop).matches(this.rawPassword, this.encodedPassword);
|
||||
verifyZeroInteractions(this.bcrypt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenUnMappedThenIllegalArgumentException() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "{unmapped}" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"unmapped\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentExcetion() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoStartingPrefixStringThenFalse() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "bcrypt}" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoIdStringThenFalse() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "{}" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenPrefixInMiddleThenFalse() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "invalid" + this.bcryptEncodedPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenIdIsNullThenFalse() {
|
||||
this.delegates = new Hashtable<String, PasswordEncoder>(this.delegates);
|
||||
|
||||
DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
||||
|
||||
try {
|
||||
passwordEncoder.matches(this.rawPassword, this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNullIdThenDelegatesToInvalidId() {
|
||||
this.delegates.put(null, this.invalidId);
|
||||
this.passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
||||
when(this.invalidId.matches(this.rawPassword, this.encodedPassword)).thenReturn(true);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, this.encodedPassword)).isTrue();
|
||||
|
||||
verify(this.invalidId).matches(this.rawPassword, this.encodedPassword);
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void matchesWhenRawPasswordNotNullAndEncodedPasswordNullThenThrowsIllegalArgumentException() {
|
||||
this.passwordEncoder.matches(this.rawPassword, null);
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests {@link LdapShaPasswordEncoder}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
public class LdapShaPasswordEncoderTests {
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
|
||||
LdapShaPasswordEncoder sha = new LdapShaPasswordEncoder();
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
@Test
|
||||
public void invalidPasswordFails() {
|
||||
assertThat(this.sha.matches("wrongpassword", "{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidSaltedPasswordFails() {
|
||||
assertThat(this.sha.matches("wrongpassword", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isFalse();
|
||||
assertThat(this.sha.matches("wrongpassword", "{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd")).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
|
||||
*/
|
||||
@Test
|
||||
public void validPasswordSucceeds() {
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test values generated by 'slappasswd -s boabspasswurd'
|
||||
*/
|
||||
@Test
|
||||
public void validSaltedPasswordSucceeds() {
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd")).isTrue();
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
// SEC-1031
|
||||
public void fullLengthOfHashIsUsedInComparison() throws Exception {
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isTrue();
|
||||
// Change the first hash character from '2' to '3'
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}35ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isFalse();
|
||||
// Change the last hash character from 'X' to 'Y'
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgY")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctPrefixCaseIsUsed() {
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{SSHA}"));
|
||||
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{ssha}"));
|
||||
|
||||
this.sha = new LdapShaPasswordEncoder(KeyGenerators.shared(0));
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{SHA}"));
|
||||
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{SSHA}"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void invalidPrefixIsRejected() {
|
||||
this.sha.matches("somepassword", "{MD9}xxxxxxxxxx");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void malformedPrefixIsRejected() {
|
||||
// No right brace
|
||||
this.sha.matches("somepassword", "{SSHA25ro4PKC8jhQZ26jVsozhX/xaP0suHgX");
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class Md4PasswordEncoderTests {
|
||||
|
||||
@Test
|
||||
public void testEncodeUnsaltedPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches("ww_uni123", "8zobtq72iAt0W6KNqavGwg==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSaltedPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches("ww_uni123", "{Alan K Stewart}ZplT6P5Kv6Rlu6W4FIoYNA==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeNullPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches(null, "MdbP4NFq6TG3PFnX4MCJwA==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeEmptyPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches(null, "MdbP4NFq6TG3PFnX4MCJwA==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonAsciiPasswordHasCorrectHash() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
assertThat(md4.matches("\u4F60\u597d", "a7f1196539fd1f85f754ffd185b16e6e")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodedMatches() {
|
||||
String rawPassword = "password";
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
String encodedPassword = md4.encode(rawPassword);
|
||||
|
||||
assertThat(md4.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javadocWhenHasSaltThenMatches() {
|
||||
Md4PasswordEncoder encoder = new Md4PasswordEncoder();
|
||||
assertThat(encoder.matches("password", "{thisissalt}6cc7924dad12ade79dfb99e424f25260"));
|
||||
}
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* TestCase for Md5PasswordEncoder.
|
||||
* </p>
|
||||
*
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
public class MessageDigestPasswordEncoderTests {
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
@Test
|
||||
public void md5BasicFunctionality() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches( raw, "{THIS_IS_A_SALT}a68aafd90299d0b137de28fb4bb68573")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5NonAsciiPasswordHasCorrectHash() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
// $ echo -n "??" | md5
|
||||
// 7eca689f0d3389d9dea66ae112e5cfd7
|
||||
assertThat(pe.matches("\u4F60\u597d", "7eca689f0d3389d9dea66ae112e5cfd7")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5Base64() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
pe.setEncodeHashAsBase64(true);
|
||||
assertThat(pe.matches("abc123", "{THIS_IS_A_SALT}poqv2QKZ0LE33ij7S7aFcw==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5StretchFactorIsProcessedCorrectly() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
pe.setIterations(2);
|
||||
// Calculate value using:
|
||||
// echo -n password{salt} | openssl md5 -binary | openssl md5
|
||||
assertThat(pe.matches("password", "{salt}eb753fb0c370582b4ee01b30f304b9fc")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5MatchesWhenNullSalt() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
assertThat(pe.matches("password", "5f4dcc3b5aa765d61d8327deb882cf99")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5MatchesWhenEmptySalt() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
assertThat(pe.matches("password", "{}f1026a66095fc2058c1f8771ed05d6da")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5MatchesWhenHasSalt() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
assertThat(pe.matches("password", "{salt}ce421738b1c5540836bdc8ff707f1572")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5EncodeThenMatches() {
|
||||
String rawPassword = "password";
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
String encode = pe.encode(rawPassword);
|
||||
assertThat(pe.matches(rawPassword, encode)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicFunctionality() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("SHA-1");
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches(raw, "{THIS_IS_A_SALT}b2f50ffcbd3407fe9415c062d55f54731f340d32"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("SHA-1");
|
||||
pe.setEncodeHashAsBase64(true);
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches(raw, "{THIS_IS_A_SALT}b2f50ffcbd3407fe9415c062d55f54731f340d32"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test256() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("SHA-1");
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches(raw, "{THIS_IS_A_SALT}4b79b7de23eb23b78cc5ede227d532b8a51f89b2ec166f808af76b0dbedc47d7"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testInvalidStrength() throws Exception {
|
||||
new MessageDigestPasswordEncoder("SHA-666");
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.crypto.password;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class PasswordEncoderUtilsTests {
|
||||
|
||||
@Test
|
||||
public void equalsWhenDifferentLengthThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals("abc", "a")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("a", "abc")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNullAndNotEmtpyThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals(null, "a")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("a", null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNullAndNullThenTrue() {
|
||||
assertThat(PasswordEncoderUtils.equals(null, null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNullAndEmptyThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals(null, "")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("", null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNotEmptyAndEmptyThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals("abc", "")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("", "abc")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenEmtpyAndEmptyThenTrue() {
|
||||
assertThat(PasswordEncoderUtils.equals("", "")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenDifferentCaseThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals("aBc", "abc")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenSameThenTrue() {
|
||||
assertThat(PasswordEncoderUtils.equals("abcdef", "abcdef")).isTrue();
|
||||
}
|
||||
}
|
||||
+42
-1
@@ -19,7 +19,10 @@ import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.junit.Assumptions;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -75,6 +78,44 @@ public class Pbkdf2PasswordEncoderTests {
|
||||
assertThat(fixedHex).isEqualTo(encodedPassword);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndMatchWhenBase64ThenSuccess() {
|
||||
this.encoder.setEncodeHashAsBase64(true);
|
||||
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = this.encoder.encode(rawPassword);
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchWhenBase64ThenSuccess() {
|
||||
this.encoder.setEncodeHashAsBase64(true);
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = "3FOwOMcDgxP+z1x/sv184LFY2WVD+ZGMgYP3LPOSmCcDmk1XPYvcCQ==";
|
||||
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
Base64.decode(Utf8.encode(encodedPassword)); // validate can decode as Base64
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndMatchWhenSha256ThenSuccess() {
|
||||
Assumptions.assumeMinimumJdk8();
|
||||
this.encoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256);
|
||||
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = this.encoder.encode(rawPassword);
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchWhenSha256ThenSuccess() {
|
||||
Assumptions.assumeMinimumJdk8();
|
||||
this.encoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256);
|
||||
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = "821447f994e2b04c5014e31fa9fca4ae1cc9f2188c4ed53d3ddb5ba7980982b51a0ecebfc0b81a79";
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
/**
|
||||
* Used to find the iteration count that takes .5 seconds.
|
||||
*/
|
||||
@@ -105,4 +146,4 @@ public class Pbkdf2PasswordEncoderTests {
|
||||
}
|
||||
System.out.println("Iterations " + iterations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ public class EncodingUtilsTests {
|
||||
(byte) 67, (byte) 0xC0, (byte) 0xC1, (byte) 0xC2 };
|
||||
byte[] two = new byte[] { (byte) 0xFF, (byte) 65, (byte) 66 };
|
||||
byte[] subArray = EncodingUtils.subArray(bytes, 1, 4);
|
||||
assertThat(subArray.length).isEqualTo(3);
|
||||
assertThat(subArray).hasSize(3);
|
||||
assertThat(Arrays.equals(two, subArray)).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.security" level="${sec.log.level:-WARN}"/>
|
||||
|
||||
|
||||
<root level="${root.level:-WARN}">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
+6
-12
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-data</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>spring-security-data</name>
|
||||
<description>spring-security-data</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -45,13 +45,13 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
<version>1.12.2.RELEASE</version>
|
||||
<version>1.13.14.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -75,7 +75,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -99,7 +99,7 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -108,12 +108,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
@@ -944,7 +944,7 @@ public BCryptPasswordEncoder passwordEncoder() {
|
||||
|
||||
=== Multiple HttpSecurity
|
||||
|
||||
We can configure multiple HttpSecurity instances just as we can have multiple `<http>` blocks. The key is to extend the `WebSecurityConfigurationAdapter` multiple times. For example, the following is an example of having a different configuration for URL's that start with `/api/`.
|
||||
We can configure multiple HttpSecurity instances just as we can have multiple `<http>` blocks. The key is to extend the `WebSecurityConfigurerAdapter` multiple times. For example, the following is an example of having a different configuration for URL's that start with `/api/`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -2265,6 +2265,32 @@ Granted Authorities: ROLE_USER
|
||||
|
||||
Note that you don't normally need to write any code like this. The process will normally occur internally, in a web authentication filter for example. We've just included the code here to show that the question of what actually constitutes authentication in Spring Security has quite a simple answer. A user is authenticated when the `SecurityContextHolder` contains a fully populated `Authentication` object.
|
||||
|
||||
By default the `StrictHttpFirewall` is used.
|
||||
This implementation rejects requests that appear to be malicious.
|
||||
If it is too strict for your needs, then you can customize what types of requests are rejected.
|
||||
However, it is important that you do so knowing that this can open your application up to attacks.
|
||||
For example, if you wish to leverage Spring MVC's Matrix Variables, the following configuration could be used in XML:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<b:bean id="httpFirewall"
|
||||
class="org.springframework.security.web.firewall.StrictHttpFirewall"
|
||||
p:allowSemicolon="true"/>
|
||||
|
||||
<http-firewall ref="httpFirewall"/>
|
||||
----
|
||||
|
||||
The same thing can be achieved with Java Configuration by exposing a `StrictHttpFirewall` bean.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public StrictHttpFirewall httpFirewall() {
|
||||
StrictHttpFirewall firewall = new StrictHttpFirewall();
|
||||
firewall.setAllowSemicolon(true);
|
||||
return firewall;
|
||||
}
|
||||
----
|
||||
|
||||
==== Setting the SecurityContextHolder Contents Directly
|
||||
In fact, Spring Security doesn't mind how you put the `Authentication` object inside the `SecurityContextHolder`. The only critical requirement is that the `SecurityContextHolder` contains an `Authentication` which represents a principal before the `AbstractSecurityInterceptor` (which we'll see more about later) needs to authorize a user operation.
|
||||
@@ -2643,20 +2669,71 @@ The order that filters are defined in the chain is very important. Irrespective
|
||||
|
||||
[[request-matching]]
|
||||
=== Request Matching and HttpFirewall
|
||||
Spring Security has several areas where patterns you have defined are tested against incoming requests in order to decide how the request should be handled. This occurs when the `FilterChainProxy` decides which filter chain a request should be passed through and also when the `FilterSecurityInterceptor` decides which security constraints apply to a request. It's important to understand what the mechanism is and what URL value is used when testing against the patterns that you define.
|
||||
Spring Security has several areas where patterns you have defined are tested against incoming requests in order to decide how the request should be handled.
|
||||
This occurs when the `FilterChainProxy` decides which filter chain a request should be passed through and also when the `FilterSecurityInterceptor` decides which security constraints apply to a request.
|
||||
It's important to understand what the mechanism is and what URL value is used when testing against the patterns that you define.
|
||||
|
||||
The Servlet Specification defines several properties for the `HttpServletRequest` which are accessible via getter methods, and which we might want to match against. These are the `contextPath`, `servletPath`, `pathInfo` and `queryString`. Spring Security is only interested in securing paths within the application, so the `contextPath` is ignored. Unfortunately, the servlet spec does not define exactly what the values of `servletPath` and `pathInfo` will contain for a particular request URI. For example, each path segment of a URL may contain parameters, as defined in http://www.ietf.org/rfc/rfc2396.txt[RFC 2396] footnote:[You have probably seen this when a browser doesn't support cookies and the `jsessionid` parameter is appended to the URL after a semi-colon. However the RFC allows the presence of these parameters in any path segment of the URL]. The Specification does not clearly state whether these should be included in the `servletPath` and `pathInfo` values and the behaviour varies between different servlet containers. There is a danger that when an application is deployed in a container which does not strip path parameters from these values, an attacker could add them to the requested URL in order to cause a pattern match to succeed or fail unexpectedly. footnote:[The original values will be returned once the request leaves the `FilterChainProxy`, so will still be available to the application.]. Other variations in the incoming URL are also possible. For example, it could contain path-traversal sequences (like `/../`) or multiple forward slashes (`//`) which could also cause pattern-matches to fail. Some containers normalize these out before performing the servlet mapping, but others don't. To protect against issues like these, `FilterChainProxy` uses an `HttpFirewall` strategy to check and wrap the request. Un-normalized requests are automatically rejected by default, and path parameters and duplicate slashes are removed for matching purposes. footnote:[So, for example, an original request path `/secure;hack=1/somefile.html;hack=2` will be returned as `/secure/somefile.html`.]. It is therefore essential that a `FilterChainProxy` is used to manage the security filter chain. Note that the `servletPath` and `pathInfo` values are decoded by the container, so your application should not have any valid paths which contain semi-colons, as these parts will be removed for matching purposes.
|
||||
The Servlet Specification defines several properties for the `HttpServletRequest` which are accessible via getter methods, and which we might want to match against.
|
||||
These are the `contextPath`, `servletPath`, `pathInfo` and `queryString`.
|
||||
Spring Security is only interested in securing paths within the application, so the `contextPath` is ignored.
|
||||
Unfortunately, the servlet spec does not define exactly what the values of `servletPath` and `pathInfo` will contain for a particular request URI.
|
||||
For example, each path segment of a URL may contain parameters, as defined in http://www.ietf.org/rfc/rfc2396.txt[RFC 2396]
|
||||
footnote:[You have probably seen this when a browser doesn't support cookies and the `jsessionid` parameter is appended to the URL after a semi-colon.
|
||||
However the RFC allows the presence of these parameters in any path segment of the URL].
|
||||
The Specification does not clearly state whether these should be included in the `servletPath` and `pathInfo` values and the behaviour varies between different servlet containers.
|
||||
There is a danger that when an application is deployed in a container which does not strip path parameters from these values, an attacker could add them to the requested URL in order to cause a pattern match to succeed or fail unexpectedly.
|
||||
footnote:[The original values will be returned once the request leaves the `FilterChainProxy`, so will still be available to the application.].
|
||||
Other variations in the incoming URL are also possible.
|
||||
For example, it could contain path-traversal sequences (like `/../`) or multiple forward slashes (`//`) which could also cause pattern-matches to fail.
|
||||
Some containers normalize these out before performing the servlet mapping, but others don't.
|
||||
To protect against issues like these, `FilterChainProxy` uses an `HttpFirewall` strategy to check and wrap the request.
|
||||
Un-normalized requests are automatically rejected by default, and path parameters and duplicate slashes are removed for matching purposes.
|
||||
footnote:[So, for example, an original request path `/secure;hack=1/somefile.html;hack=2` will be returned as `/secure/somefile.html`.].
|
||||
It is therefore essential that a `FilterChainProxy` is used to manage the security filter chain.
|
||||
Note that the `servletPath` and `pathInfo` values are decoded by the container, so your application should not have any valid paths which contain semi-colons, as these parts will be removed for matching purposes.
|
||||
|
||||
As mentioned above, the default strategy is to use Ant-style paths for matching and this is likely to be the best choice for most users. The strategy is implemented in the class `AntPathRequestMatcher` which uses Spring's `AntPathMatcher` to perform a case-insensitive match of the pattern against the concatenated `servletPath` and `pathInfo`, ignoring the `queryString`.
|
||||
As mentioned above, the default strategy is to use Ant-style paths for matching and this is likely to be the best choice for most users.
|
||||
The strategy is implemented in the class `AntPathRequestMatcher` which uses Spring's `AntPathMatcher` to perform a case-insensitive match of the pattern against the concatenated `servletPath` and `pathInfo`, ignoring the `queryString`.
|
||||
|
||||
If for some reason, you need a more powerful matching strategy, you can use regular expressions. The strategy implementation is then `RegexRequestMatcher`. See the Javadoc for this class for more information.
|
||||
If for some reason, you need a more powerful matching strategy, you can use regular expressions.
|
||||
The strategy implementation is then `RegexRequestMatcher`.
|
||||
See the Javadoc for this class for more information.
|
||||
|
||||
In practice we recommend that you use method security at your service layer, to control access to your application, and do not rely entirely on the use of security constraints defined at the web-application level. URLs change and it is difficult to take account of all the possible URLs that an application might support and how requests might be manipulated. You should try and restrict yourself to using a few simple ant paths which are simple to understand. Always try to use a "deny-by-default" approach where you have a catch-all wildcard ( /** or **) defined last and denying access.
|
||||
In practice we recommend that you use method security at your service layer, to control access to your application, and do not rely entirely on the use of security constraints defined at the web-application level.
|
||||
URLs change and it is difficult to take account of all the possible URLs that an application might support and how requests might be manipulated.
|
||||
You should try and restrict yourself to using a few simple ant paths which are simple to understand.
|
||||
Always try to use a "deny-by-default" approach where you have a catch-all wildcard ( /** or **) defined last and denying access.
|
||||
|
||||
Security defined at the service layer is much more robust and harder to bypass, so you should always take advantage of Spring Security's method security options.
|
||||
|
||||
The `HttpFirewall` also prevents https://www.owasp.org/index.php/HTTP_Response_Splitting[HTTP Response Splitting] by rejecting new line characters in the HTTP Response headers.
|
||||
|
||||
By default the `StrictHttpFirewall` is used.
|
||||
This implementation rejects requests that appear to be malicious.
|
||||
If it is too strict for your needs, then you can customize what types of requests are rejected.
|
||||
However, it is important that you do so knowing that this can open your application up to attacks.
|
||||
For example, if you wish to leverage Spring MVC's Matrix Variables, the following configuration could be used in XML:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<b:bean id="httpFirewall"
|
||||
class="org.springframework.security.web.firewall.StrictHttpFirewall"
|
||||
p:allowSemicolon="true"/>
|
||||
|
||||
<http-firewall ref="httpFirewall"/>
|
||||
----
|
||||
|
||||
The same thing can be achieved with Java Configuration by exposing a `StrictHttpFirewall` bean.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public StrictHttpFirewall httpFirewall() {
|
||||
StrictHttpFirewall firewall = new StrictHttpFirewall();
|
||||
firewall.setAllowSemicolon(true);
|
||||
return firewall;
|
||||
}
|
||||
----
|
||||
=== Use with other Filter-Based Frameworks
|
||||
If you're using some other framework that is also filter-based, then you need to make sure that the Spring Security filters come first. This enables the `SecurityContextHolder` to be populated in time for use by the other filters. Examples are the use of SiteMesh to decorate your web pages or a web framework like Wicket which uses a filter to handle its requests.
|
||||
|
||||
@@ -2914,8 +2991,8 @@ The following section describes the Servlet 3 methods that Spring Security integ
|
||||
|
||||
|
||||
[[servletapi-authenticate]]
|
||||
==== HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
|
||||
The http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#authenticate%28javax.servlet.http.HttpServletResponse%29[HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)] method can be used to ensure that a user is authenticated. If they are not authenticated, the configured AuthenticationEntryPoint will be used to request the user to authenticate (i.e. redirect to the login page).
|
||||
==== HttpServletRequest.authenticate(HttpServletResponse)
|
||||
The http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#authenticate%28javax.servlet.http.HttpServletResponse%29[HttpServletRequest.authenticate(HttpServletResponse)] method can be used to ensure that a user is authenticated. If they are not authenticated, the configured AuthenticationEntryPoint will be used to request the user to authenticate (i.e. redirect to the login page).
|
||||
|
||||
|
||||
[[servletapi-login]]
|
||||
@@ -3084,7 +3161,7 @@ expirationTime: The date and time when the nonce expires, expressed in millise
|
||||
key: A private key to prevent modification of the nonce token
|
||||
----
|
||||
|
||||
The `DigestAuthenticatonEntryPoint` has a property specifying the `key` used for generating the nonce tokens, along with a `nonceValiditySeconds` property for determining the expiration time (default 300, which equals five minutes). Whist ever the nonce is valid, the digest is computed by concatenating various strings including the username, password, nonce, URI being requested, a client-generated nonce (merely a random value which the user agent generates each request), the realm name etc, then performing an MD5 hash. Both the server and user agent perform this digest computation, resulting in different hash codes if they disagree on an included value (eg password). In Spring Security implementation, if the server-generated nonce has merely expired (but the digest was otherwise valid), the `DigestAuthenticationEntryPoint` will send a `"stale=true"` header. This tells the user agent there is no need to disturb the user (as the password and username etc is correct), but simply to try again using a new nonce.
|
||||
The `DigestAuthenticationEntryPoint` has a property specifying the `key` used for generating the nonce tokens, along with a `nonceValiditySeconds` property for determining the expiration time (default 300, which equals five minutes). Whist ever the nonce is valid, the digest is computed by concatenating various strings including the username, password, nonce, URI being requested, a client-generated nonce (merely a random value which the user agent generates each request), the realm name etc, then performing an MD5 hash. Both the server and user agent perform this digest computation, resulting in different hash codes if they disagree on an included value (eg password). In Spring Security implementation, if the server-generated nonce has merely expired (but the digest was otherwise valid), the `DigestAuthenticationEntryPoint` will send a `"stale=true"` header. This tells the user agent there is no need to disturb the user (as the password and username etc is correct), but simply to try again using a new nonce.
|
||||
|
||||
An appropriate value for the `nonceValiditySeconds` parameter of `DigestAuthenticationEntryPoint` depends on your application. Extremely secure applications should note that an intercepted authentication header can be used to impersonate the principal until the `expirationTime` contained in the nonce is reached. This is the key principle when selecting an appropriate setting, but it would be unusual for immensely secure applications to not be running over TLS/HTTPS in the first instance.
|
||||
|
||||
@@ -5522,16 +5599,16 @@ There is an additional stage where the roles (or attributes) are mapped to Sprin
|
||||
|
||||
|
||||
==== PreAuthenticatedAuthenticationProvider
|
||||
The pre-authenticated provider has little more to do than load the `UserDetails` object for the user. It does this by delegating to a `AuthenticationUserDetailsService`. The latter is similar to the standard `UserDetailsService` but takes an `Authentication` object rather than just user name:
|
||||
The pre-authenticated provider has little more to do than load the `UserDetails` object for the user. It does this by delegating to an `AuthenticationUserDetailsService`. The latter is similar to the standard `UserDetailsService` but takes an `Authentication` object rather than just user name:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface AuthenticationUserDetailsService {
|
||||
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
|
||||
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
|
||||
}
|
||||
----
|
||||
|
||||
This interface may have also other uses but with pre-authentication it allows access to the authorities which were packaged in the `Authentication` object, as we saw in the previous section. the `PreAuthenticatedGrantedAuthoritiesUserDetailsService` class does this. Alternatively, it may delegate to a standard `UserDetailsService` via the `UserDetailsByNameServiceWrapper` implementation.
|
||||
This interface may have also other uses but with pre-authentication it allows access to the authorities which were packaged in the `Authentication` object, as we saw in the previous section. The `PreAuthenticatedGrantedAuthoritiesUserDetailsService` class does this. Alternatively, it may delegate to a standard `UserDetailsService` via the `UserDetailsByNameServiceWrapper` implementation.
|
||||
|
||||
==== Http403ForbiddenEntryPoint
|
||||
The `AuthenticationEntryPoint` was discussed in the <<tech-intro-auth-entry-point,technical overview>> chapter. Normally it is responsible for kick-starting the authentication process for an unauthenticated user (when they try to access a protected resource), but in the pre-authenticated case this doesn't apply. You would only configure the `ExceptionTranslationFilter` with an instance of this class if you aren't using pre-authentication in combination with other authentication mechanisms. It will be called if the user is rejected by the `AbstractPreAuthenticatedProcessingFilter` resulting in a null authentication. It always returns a `403`-forbidden response code if called.
|
||||
@@ -6640,7 +6717,7 @@ Use the KeyGenerators.secureRandom factory methods to generate a BytesKeyGenerat
|
||||
|
||||
[source,java]
|
||||
----
|
||||
KeyGenerator generator = KeyGenerators.secureRandom();
|
||||
BytesKeyGenerator generator = KeyGenerators.secureRandom();
|
||||
byte[] key = generator.generateKey();
|
||||
----
|
||||
|
||||
@@ -6865,6 +6942,64 @@ NOTE: Spring Security provides the configuration using Spring MVC's http://docs.
|
||||
Spring Security provides deep integration with how Spring MVC matches on URLs with `MvcRequestMatcher`.
|
||||
This is helpful to ensure your Security rules match the logic used to handle your requests.
|
||||
|
||||
In order to use `MvcRequestMatcher` you must place the Spring Security Configuration in the same `ApplicationContext` as your `DispatcherServlet`.
|
||||
This is necessary because Spring Security's `MvcRequestMatcher` expects a `HandlerMappingIntrospector` bean with the name of `mvcHandlerMappingIntrospector` to be registered by your Spring MVC configuration that is used to perform the matching.
|
||||
|
||||
For a `web.xml` this means that you should place your configuration in the `DispatcherServlet.xml`.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ -->
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>/WEB-INF/spring/*.xml</param-value>
|
||||
</context-param>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<!-- Load from the ContextLoaderListener -->
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value></param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
----
|
||||
|
||||
Below `WebSecurityConfiguration` in placed in the ``DispatcherServlet``s `ApplicationContext`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class SecurityInitializer extends
|
||||
AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getRootConfigClasses() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getServletConfigClasses() {
|
||||
return new Class[] { RootConfiguration.class,
|
||||
WebMvcConfiguration.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getServletMappings() {
|
||||
return new String[] { "/" };
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
version=4.2.3.BUILD-SNAPSHOT
|
||||
version=4.2.8.RELEASE
|
||||
|
||||
+17
-17
@@ -9,36 +9,36 @@ sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
|
||||
ext.apacheDsVersion = '1.5.5'
|
||||
ext.aspectjVersion = '1.8.4'
|
||||
ext.aspectjVersion = '1.8.13'
|
||||
ext.casClientVersion = '3.4.1'
|
||||
ext.cglibVersion = '3.1'
|
||||
ext.commonsCodecVersion = '1.10'
|
||||
ext.commonsCollectionsVersion = '3.2.2'
|
||||
ext.commonsLoggingVersion = '1.2'
|
||||
ext.ehcacheVersion = '2.9.0'
|
||||
ext.ehcacheVersion = '2.10.5'
|
||||
ext.gebVersion = '0.10.0'
|
||||
ext.groovyVersion = '2.4.4'
|
||||
ext.hsqlVersion = '2.3.2'
|
||||
ext.hibernateVersion = '5.0.11.Final'
|
||||
ext.hibernateValidatorVersion = '5.2.4.Final'
|
||||
ext.jacksonDatabindVersion = '2.8.4'
|
||||
ext.hsqlVersion = '2.3.6'
|
||||
ext.hibernateVersion = '5.0.12.Final'
|
||||
ext.hibernateValidatorVersion = '5.3.6.Final'
|
||||
ext.jacksonDatabindVersion = '2.8.11.2'
|
||||
ext.javaPersistenceVersion = '2.1.1'
|
||||
ext.jettyVersion = '6.1.26'
|
||||
ext.jstlVersion = '1.2.1'
|
||||
ext.jstlVersion = '1.2.2'
|
||||
ext.junitVersion = '4.12'
|
||||
ext.logbackVersion = '1.1.2'
|
||||
ext.powerMockVersion = '1.6.2'
|
||||
ext.logbackVersion = '1.1.11'
|
||||
ext.powerMockVersion = '1.6.5'
|
||||
ext.seleniumVersion = '2.44.0'
|
||||
ext.servletApiVersion = '3.1.0'
|
||||
ext.slf4jVersion = '1.7.7'
|
||||
ext.slf4jVersion = '1.7.25'
|
||||
ext.spockVersion = '0.7-groovy-2.0'
|
||||
ext.springDataCommonsVersion = '1.12.2.RELEASE'
|
||||
ext.springDataJpaVersion = '1.10.2.RELEASE'
|
||||
ext.springDataRedisVersion = '1.7.2.RELEASE'
|
||||
ext.springSessionVersion = '1.2.1.RELEASE'
|
||||
ext.springBootVersion = '1.5.0.BUILD-SNAPSHOT'
|
||||
ext.thymeleafVersion = '3.0.2.RELEASE'
|
||||
ext.jsonassertVersion = '1.3.0'
|
||||
ext.springDataCommonsVersion = '1.13.14.RELEASE'
|
||||
ext.springDataJpaVersion = '1.11.12.RELEASE'
|
||||
ext.springDataRedisVersion = '1.8.12.RELEASE'
|
||||
ext.springSessionVersion = '1.3.2.RELEASE'
|
||||
ext.springBootVersion = '1.5.15.RELEASE'
|
||||
ext.thymeleafVersion = '3.0.9.RELEASE'
|
||||
ext.jsonassertVersion = '1.4.0'
|
||||
ext.validationApiVersion = '1.1.0.Final'
|
||||
|
||||
ext.spockDependencies = [
|
||||
|
||||
@@ -10,7 +10,7 @@ buildscript {
|
||||
apply plugin: 'com.bmuschko.tomcat'
|
||||
|
||||
dependencies {
|
||||
def tomcatVersion = '7.0.54'
|
||||
def tomcatVersion = '7.0.90'
|
||||
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
|
||||
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
|
||||
"org.apache.tomcat.embed:tomcat-embed-websocket:${tomcatVersion}",
|
||||
@@ -62,4 +62,4 @@ def reservePorts(int count) {
|
||||
def result = sockets*.localPort
|
||||
sockets*.close()
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
+8
-14
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>itest-context</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>itest-context</name>
|
||||
<description>itest-context</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -57,7 +57,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -90,7 +90,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -108,7 +108,7 @@
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>1.8.4</version>
|
||||
<version>1.8.13</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -126,19 +126,19 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -152,12 +152,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
+9
-7
@@ -28,6 +28,7 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.firewall.RequestRejectedException;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -40,7 +41,7 @@ public class HttpPathParameterStrippingTests {
|
||||
@Autowired
|
||||
private FilterChainProxy fcp;
|
||||
|
||||
@Test
|
||||
@Test(expected = RequestRejectedException.class)
|
||||
public void securedFilterChainCannotBeBypassedByAddingPathParameters()
|
||||
throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
@@ -48,24 +49,25 @@ public class HttpPathParameterStrippingTests {
|
||||
request.setSession(createAuthenticatedSession("ROLE_USER"));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
fcp.doFilter(request, response, new MockFilterChain());
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expected = RequestRejectedException.class)
|
||||
public void adminFilePatternCannotBeBypassedByAddingPathParameters() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setServletPath("/secured/admin.html;x=user.html");
|
||||
request.setSession(createAuthenticatedSession("ROLE_USER"));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
fcp.doFilter(request, response, new MockFilterChain());
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
}
|
||||
|
||||
// Try with pathInfo
|
||||
request = new MockHttpServletRequest();
|
||||
|
||||
@Test(expected = RequestRejectedException.class)
|
||||
public void adminFilePatternCannotBeBypassedByAddingPathParametersWithPathInfo() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setServletPath("/secured");
|
||||
request.setPathInfo("/admin.html;x=user.html");
|
||||
request.setSession(createAuthenticatedSession("ROLE_USER"));
|
||||
response = new MockHttpServletResponse();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
fcp.doFilter(request, response, new MockFilterChain());
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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,14 +45,14 @@ public class SecurityContextHolderMTTests extends TestCase{
|
||||
assertThat(new SecurityContextHolder().toString().isTrue()
|
||||
.lastIndexOf("SecurityContextHolder[strategy='org.springframework.security.context.InheritableThreadLocalSecurityContextHolderStrategy'") != -1);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false, true);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isEqualTo(0);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isZero();
|
||||
}
|
||||
|
||||
public void testSynchronizationGlobal() throws Exception {
|
||||
SecurityContextHolder.clearContext();
|
||||
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, true, false);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isEqualTo(0);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isZero();
|
||||
}
|
||||
|
||||
public void testSynchronizationInheritableThreadLocal()
|
||||
@@ -60,14 +60,14 @@ public class SecurityContextHolderMTTests extends TestCase{
|
||||
SecurityContextHolder.clearContext();
|
||||
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false, true);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isEqualTo(0);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isZero();
|
||||
}
|
||||
|
||||
public void testSynchronizationThreadLocal() throws Exception {
|
||||
SecurityContextHolder.clearContext();
|
||||
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false, false);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isEqualTo(0);
|
||||
assertThat(errors).as("Thread errors detected; review log output for details").isZero();
|
||||
}
|
||||
|
||||
private void startAndRun(Thread[] threads) {
|
||||
|
||||
+9
-15
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>itest-web</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<name>itest-web</name>
|
||||
<description>itest-web</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<version>4.3.19.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -68,7 +68,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<version>1.1.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -92,37 +92,37 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.25</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-ldap</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>4.2.3.BUILD-SNAPSHOT</version>
|
||||
<version>4.2.8.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -141,12 +141,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user